1use crate::config::Config;
2use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
3use rust_i18n::t;
4use std::collections::HashMap;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7fn normalize_key(code: KeyCode, modifiers: KeyModifiers) -> (KeyCode, KeyModifiers) {
18 if code == KeyCode::BackTab {
19 return (code, modifiers.difference(KeyModifiers::SHIFT));
20 }
21 if let KeyCode::Char(c) = code {
22 if c.is_ascii_uppercase() {
23 return (KeyCode::Char(c.to_ascii_lowercase()), modifiers);
24 }
25 }
26 (code, modifiers)
27}
28
29static FORCE_LINUX_KEYBINDINGS: AtomicBool = AtomicBool::new(false);
32
33pub fn set_force_linux_keybindings(force: bool) {
36 FORCE_LINUX_KEYBINDINGS.store(force, Ordering::SeqCst);
37}
38
39fn use_macos_symbols() -> bool {
41 if FORCE_LINUX_KEYBINDINGS.load(Ordering::SeqCst) {
42 return false;
43 }
44 cfg!(target_os = "macos")
45}
46
47fn is_text_input_modifier(modifiers: KeyModifiers) -> bool {
58 if modifiers.is_empty() || modifiers == KeyModifiers::SHIFT {
59 return true;
60 }
61
62 #[cfg(windows)]
66 if modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT)
67 || modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT)
68 {
69 return true;
70 }
71
72 false
73}
74
75pub fn format_keybinding(keycode: &KeyCode, modifiers: &KeyModifiers) -> String {
79 let mut result = String::new();
80
81 let (ctrl_label, alt_label, shift_label, super_label) = if use_macos_symbols() {
83 ("⌃", "⌥", "⇧", "⌘")
84 } else {
85 ("Ctrl", "Alt", "Shift", "Super")
86 };
87
88 let use_plus = !use_macos_symbols();
89
90 if modifiers.contains(KeyModifiers::SUPER) {
91 result.push_str(super_label);
92 if use_plus {
93 result.push('+');
94 }
95 }
96 if modifiers.contains(KeyModifiers::CONTROL) {
97 result.push_str(ctrl_label);
98 if use_plus {
99 result.push('+');
100 }
101 }
102 if modifiers.contains(KeyModifiers::ALT) {
103 result.push_str(alt_label);
104 if use_plus {
105 result.push('+');
106 }
107 }
108 if modifiers.contains(KeyModifiers::SHIFT) {
109 result.push_str(shift_label);
110 if use_plus {
111 result.push('+');
112 }
113 }
114
115 match keycode {
116 KeyCode::Enter => result.push_str("Enter"),
117 KeyCode::Backspace => result.push_str("Backspace"),
118 KeyCode::Delete => result.push_str("Del"),
119 KeyCode::Tab => result.push_str("Tab"),
120 KeyCode::Esc => result.push_str("Esc"),
121 KeyCode::Left => result.push('←'),
122 KeyCode::Right => result.push('→'),
123 KeyCode::Up => result.push('↑'),
124 KeyCode::Down => result.push('↓'),
125 KeyCode::Home => result.push_str("Home"),
126 KeyCode::End => result.push_str("End"),
127 KeyCode::PageUp => result.push_str("PgUp"),
128 KeyCode::PageDown => result.push_str("PgDn"),
129 KeyCode::Char(' ') => result.push_str("Space"),
130 KeyCode::Char(c) => result.push_str(&c.to_uppercase().to_string()),
131 KeyCode::F(n) => result.push_str(&format!("F{}", n)),
132 _ => return String::new(),
133 }
134
135 result
136}
137
138fn keybinding_priority_score(key: &KeyCode) -> u32 {
142 match key {
143 KeyCode::Char('@') => 100, KeyCode::Char('7') => 100, KeyCode::Char('_') => 100, _ => 0,
150 }
151}
152
153pub fn terminal_key_equivalents(
164 key: KeyCode,
165 modifiers: KeyModifiers,
166) -> Vec<(KeyCode, KeyModifiers)> {
167 let mut equivalents = Vec::new();
168
169 if modifiers.contains(KeyModifiers::CONTROL) {
171 let base_modifiers = modifiers; match key {
174 KeyCode::Char('/') => {
176 equivalents.push((KeyCode::Char('7'), base_modifiers));
177 }
178 KeyCode::Char('7') => {
179 equivalents.push((KeyCode::Char('/'), base_modifiers));
180 }
181
182 KeyCode::Backspace => {
184 equivalents.push((KeyCode::Char('h'), base_modifiers));
185 }
186 KeyCode::Char('h') if modifiers == KeyModifiers::CONTROL => {
187 equivalents.push((KeyCode::Backspace, base_modifiers));
189 }
190
191 KeyCode::Char(' ') => {
193 equivalents.push((KeyCode::Char('@'), base_modifiers));
194 }
195 KeyCode::Char('@') => {
196 equivalents.push((KeyCode::Char(' '), base_modifiers));
197 }
198
199 KeyCode::Char('-') => {
201 equivalents.push((KeyCode::Char('_'), base_modifiers));
202 }
203 KeyCode::Char('_') => {
204 equivalents.push((KeyCode::Char('-'), base_modifiers));
205 }
206
207 _ => {}
208 }
209 }
210
211 equivalents
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
216pub enum KeyContext {
217 Global,
219 Normal,
221 Prompt,
223 Popup,
225 FileExplorer,
227 Menu,
229 Terminal,
231 Settings,
233 Mode(String),
235}
236
237impl KeyContext {
238 pub fn allows_text_input(&self) -> bool {
240 matches!(self, Self::Normal | Self::Prompt | Self::FileExplorer)
241 }
242
243 pub fn from_when_clause(when: &str) -> Option<Self> {
245 let trimmed = when.trim();
246 if let Some(mode_name) = trimmed.strip_prefix("mode:") {
247 return Some(Self::Mode(mode_name.to_string()));
248 }
249 Some(match trimmed {
250 "global" => Self::Global,
251 "prompt" => Self::Prompt,
252 "popup" => Self::Popup,
253 "fileExplorer" | "file_explorer" => Self::FileExplorer,
254 "normal" => Self::Normal,
255 "menu" => Self::Menu,
256 "terminal" => Self::Terminal,
257 "settings" => Self::Settings,
258 _ => return None,
259 })
260 }
261
262 pub fn to_when_clause(&self) -> String {
264 match self {
265 Self::Global => "global".to_string(),
266 Self::Normal => "normal".to_string(),
267 Self::Prompt => "prompt".to_string(),
268 Self::Popup => "popup".to_string(),
269 Self::FileExplorer => "fileExplorer".to_string(),
270 Self::Menu => "menu".to_string(),
271 Self::Terminal => "terminal".to_string(),
272 Self::Settings => "settings".to_string(),
273 Self::Mode(name) => format!("mode:{}", name),
274 }
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
280pub enum Action {
281 InsertChar(char),
283 InsertNewline,
284 InsertTab,
285
286 MoveLeft,
288 MoveRight,
289 MoveUp,
290 MoveDown,
291 MoveWordLeft,
292 MoveWordRight,
293 MoveWordEnd, ViMoveWordEnd, MoveLeftInLine, MoveRightInLine, MoveLineStart,
298 MoveLineEnd,
299 MoveLineUp,
300 MoveLineDown,
301 MovePageUp,
302 MovePageDown,
303 MoveDocumentStart,
304 MoveDocumentEnd,
305
306 SelectLeft,
308 SelectRight,
309 SelectUp,
310 SelectDown,
311 SelectToParagraphUp, SelectToParagraphDown, SelectWordLeft,
314 SelectWordRight,
315 SelectWordEnd, ViSelectWordEnd, SelectLineStart,
318 SelectLineEnd,
319 SelectDocumentStart,
320 SelectDocumentEnd,
321 SelectPageUp,
322 SelectPageDown,
323 SelectAll,
324 SelectWord,
325 SelectLine,
326 ExpandSelection,
327
328 BlockSelectLeft,
330 BlockSelectRight,
331 BlockSelectUp,
332 BlockSelectDown,
333
334 DeleteBackward,
336 DeleteForward,
337 DeleteWordBackward,
338 DeleteWordForward,
339 DeleteLine,
340 DeleteToLineEnd,
341 DeleteToLineStart,
342 DeleteViWordEnd, TransposeChars,
344 OpenLine,
345 DuplicateLine,
346
347 Recenter,
349
350 SetMark,
352
353 Copy,
355 CopyWithTheme(String),
356 Cut,
357 Paste,
358
359 YankWordForward,
361 YankWordBackward,
362 YankToLineEnd,
363 YankToLineStart,
364 YankViWordEnd, AddCursorAbove,
368 AddCursorBelow,
369 AddCursorNextMatch,
370 RemoveSecondaryCursors,
371
372 Save,
374 SaveAs,
375 Open,
376 SwitchProject,
377 New,
378 Close,
379 CloseTab,
380 Quit,
381 ForceQuit,
382 Detach,
383 Revert,
384 ToggleAutoRevert,
385 FormatBuffer,
386 TrimTrailingWhitespace,
387 EnsureFinalNewline,
388
389 GotoLine,
391 ScanLineIndex,
392 GoToMatchingBracket,
393 JumpToNextError,
394 JumpToPreviousError,
395
396 SmartHome,
398 DedentSelection,
399 ToggleComment,
400 DabbrevExpand,
401 ToggleFold,
402
403 SetBookmark(char),
405 JumpToBookmark(char),
406 ClearBookmark(char),
407 ListBookmarks,
408
409 ToggleSearchCaseSensitive,
411 ToggleSearchWholeWord,
412 ToggleSearchRegex,
413 ToggleSearchConfirmEach,
414
415 StartMacroRecording,
417 StopMacroRecording,
418 PlayMacro(char),
419 ToggleMacroRecording(char),
420 ShowMacro(char),
421 ListMacros,
422 PromptRecordMacro,
423 PromptPlayMacro,
424 PlayLastMacro,
425
426 PromptSetBookmark,
428 PromptJumpToBookmark,
429
430 Undo,
432 Redo,
433
434 ScrollUp,
436 ScrollDown,
437 ShowHelp,
438 ShowKeyboardShortcuts,
439 ShowWarnings,
440 ShowStatusLog,
441 ShowLspStatus,
442 ClearWarnings,
443 CommandPalette, QuickOpen,
446 ToggleLineWrap,
447 ToggleCurrentLineHighlight,
448 ToggleReadOnly,
449 TogglePageView,
450 SetPageWidth,
451 InspectThemeAtCursor,
452 SelectTheme,
453 SelectKeybindingMap,
454 SelectCursorStyle,
455 SelectLocale,
456
457 NextBuffer,
459 PrevBuffer,
460 SwitchToPreviousTab,
461 SwitchToTabByName,
462
463 ScrollTabsLeft,
465 ScrollTabsRight,
466
467 NavigateBack,
469 NavigateForward,
470
471 SplitHorizontal,
473 SplitVertical,
474 CloseSplit,
475 NextSplit,
476 PrevSplit,
477 IncreaseSplitSize,
478 DecreaseSplitSize,
479 ToggleMaximizeSplit,
480
481 PromptConfirm,
483 PromptConfirmWithText(String),
485 PromptCancel,
486 PromptBackspace,
487 PromptDelete,
488 PromptMoveLeft,
489 PromptMoveRight,
490 PromptMoveStart,
491 PromptMoveEnd,
492 PromptSelectPrev,
493 PromptSelectNext,
494 PromptPageUp,
495 PromptPageDown,
496 PromptAcceptSuggestion,
497 PromptMoveWordLeft,
498 PromptMoveWordRight,
499 PromptDeleteWordForward,
501 PromptDeleteWordBackward,
502 PromptDeleteToLineEnd,
503 PromptCopy,
504 PromptCut,
505 PromptPaste,
506 PromptMoveLeftSelecting,
508 PromptMoveRightSelecting,
509 PromptMoveHomeSelecting,
510 PromptMoveEndSelecting,
511 PromptSelectWordLeft,
512 PromptSelectWordRight,
513 PromptSelectAll,
514
515 FileBrowserToggleHidden,
517 FileBrowserToggleDetectEncoding,
518
519 PopupSelectNext,
521 PopupSelectPrev,
522 PopupPageUp,
523 PopupPageDown,
524 PopupConfirm,
525 PopupCancel,
526
527 ToggleFileExplorer,
529 ToggleMenuBar,
531 ToggleTabBar,
533 ToggleStatusBar,
535 TogglePromptLine,
537 ToggleVerticalScrollbar,
539 ToggleHorizontalScrollbar,
540 FocusFileExplorer,
541 FocusEditor,
542 FileExplorerUp,
543 FileExplorerDown,
544 FileExplorerPageUp,
545 FileExplorerPageDown,
546 FileExplorerExpand,
547 FileExplorerCollapse,
548 FileExplorerOpen,
549 FileExplorerRefresh,
550 FileExplorerNewFile,
551 FileExplorerNewDirectory,
552 FileExplorerDelete,
553 FileExplorerRename,
554 FileExplorerToggleHidden,
555 FileExplorerToggleGitignored,
556 FileExplorerSearchClear,
557 FileExplorerSearchBackspace,
558
559 LspCompletion,
561 LspGotoDefinition,
562 LspReferences,
563 LspRename,
564 LspHover,
565 LspSignatureHelp,
566 LspCodeActions,
567 LspRestart,
568 LspStop,
569 LspToggleForBuffer,
570 ToggleInlayHints,
571 ToggleMouseHover,
572
573 ToggleLineNumbers,
575 ToggleScrollSync,
576 ToggleMouseCapture,
577 ToggleDebugHighlights, SetBackground,
579 SetBackgroundBlend,
580
581 SetTabSize,
583 SetLineEnding,
584 SetEncoding,
585 ReloadWithEncoding,
586 SetLanguage,
587 ToggleIndentationStyle,
588 ToggleTabIndicators,
589 ToggleWhitespaceIndicators,
590 ResetBufferSettings,
591 AddRuler,
592 RemoveRuler,
593
594 DumpConfig,
596
597 Search,
599 FindInSelection,
600 FindNext,
601 FindPrevious,
602 FindSelectionNext, FindSelectionPrevious, Replace,
605 QueryReplace, MenuActivate, MenuClose, MenuLeft, MenuRight, MenuUp, MenuDown, MenuExecute, MenuOpen(String), SwitchKeybindingMap(String), PluginAction(String),
622
623 OpenSettings, CloseSettings, SettingsSave, SettingsReset, SettingsToggleFocus, SettingsActivate, SettingsSearch, SettingsHelp, SettingsIncrement, SettingsDecrement, SettingsInherit, OpenTerminal, CloseTerminal, FocusTerminal, TerminalEscape, ToggleKeyboardCapture, TerminalPaste, ShellCommand, ShellCommandReplace, ToUpperCase, ToLowerCase, ToggleCase, SortLines, CalibrateInput, EventDebug, OpenKeybindingEditor, LoadPluginFromBuffer, None,
668}
669
670macro_rules! define_action_str_mapping {
683 (
684 $args_name:ident;
685 simple { $($s_name:literal => $s_variant:ident),* $(,)? }
686 alias { $($a_name:literal => $a_variant:ident),* $(,)? }
687 with_char { $($c_name:literal => $c_variant:ident),* $(,)? }
688 custom { $($x_name:literal => $x_variant:ident : $x_body:expr),* $(,)? }
689 ) => {
690 pub fn from_str(s: &str, $args_name: &HashMap<String, serde_json::Value>) -> Option<Self> {
692 Some(match s {
693 $($s_name => Self::$s_variant,)*
694 $($a_name => Self::$a_variant,)*
695 $($c_name => return Self::with_char($args_name, Self::$c_variant),)*
696 $($x_name => $x_body,)*
697 _ => Self::PluginAction(s.to_string()),
700 })
701 }
702
703 pub fn to_action_str(&self) -> String {
706 match self {
707 $(Self::$s_variant => $s_name.to_string(),)*
708 $(Self::$c_variant(_) => $c_name.to_string(),)*
709 $(Self::$x_variant(_) => $x_name.to_string(),)*
710 Self::PluginAction(name) => name.clone(),
711 }
712 }
713
714 pub fn all_action_names() -> Vec<String> {
717 let mut names = vec![
718 $($s_name.to_string(),)*
719 $($a_name.to_string(),)*
720 $($c_name.to_string(),)*
721 $($x_name.to_string(),)*
722 ];
723 names.sort();
724 names
725 }
726 };
727}
728
729impl Action {
730 fn with_char(
731 args: &HashMap<String, serde_json::Value>,
732 make_action: impl FnOnce(char) -> Self,
733 ) -> Option<Self> {
734 if let Some(serde_json::Value::String(value)) = args.get("char") {
735 value.chars().next().map(make_action)
736 } else {
737 None
738 }
739 }
740
741 define_action_str_mapping! {
742 args;
743 simple {
744 "insert_newline" => InsertNewline,
745 "insert_tab" => InsertTab,
746
747 "move_left" => MoveLeft,
748 "move_right" => MoveRight,
749 "move_up" => MoveUp,
750 "move_down" => MoveDown,
751 "move_word_left" => MoveWordLeft,
752 "move_word_right" => MoveWordRight,
753 "move_word_end" => MoveWordEnd,
754 "vi_move_word_end" => ViMoveWordEnd,
755 "move_left_in_line" => MoveLeftInLine,
756 "move_right_in_line" => MoveRightInLine,
757 "move_line_start" => MoveLineStart,
758 "move_line_end" => MoveLineEnd,
759 "move_line_up" => MoveLineUp,
760 "move_line_down" => MoveLineDown,
761 "move_page_up" => MovePageUp,
762 "move_page_down" => MovePageDown,
763 "move_document_start" => MoveDocumentStart,
764 "move_document_end" => MoveDocumentEnd,
765
766 "select_left" => SelectLeft,
767 "select_right" => SelectRight,
768 "select_up" => SelectUp,
769 "select_down" => SelectDown,
770 "select_to_paragraph_up" => SelectToParagraphUp,
771 "select_to_paragraph_down" => SelectToParagraphDown,
772 "select_word_left" => SelectWordLeft,
773 "select_word_right" => SelectWordRight,
774 "select_word_end" => SelectWordEnd,
775 "vi_select_word_end" => ViSelectWordEnd,
776 "select_line_start" => SelectLineStart,
777 "select_line_end" => SelectLineEnd,
778 "select_document_start" => SelectDocumentStart,
779 "select_document_end" => SelectDocumentEnd,
780 "select_page_up" => SelectPageUp,
781 "select_page_down" => SelectPageDown,
782 "select_all" => SelectAll,
783 "select_word" => SelectWord,
784 "select_line" => SelectLine,
785 "expand_selection" => ExpandSelection,
786
787 "block_select_left" => BlockSelectLeft,
788 "block_select_right" => BlockSelectRight,
789 "block_select_up" => BlockSelectUp,
790 "block_select_down" => BlockSelectDown,
791
792 "delete_backward" => DeleteBackward,
793 "delete_forward" => DeleteForward,
794 "delete_word_backward" => DeleteWordBackward,
795 "delete_word_forward" => DeleteWordForward,
796 "delete_line" => DeleteLine,
797 "delete_to_line_end" => DeleteToLineEnd,
798 "delete_to_line_start" => DeleteToLineStart,
799 "delete_vi_word_end" => DeleteViWordEnd,
800 "transpose_chars" => TransposeChars,
801 "open_line" => OpenLine,
802 "duplicate_line" => DuplicateLine,
803 "recenter" => Recenter,
804 "set_mark" => SetMark,
805
806 "copy" => Copy,
807 "cut" => Cut,
808 "paste" => Paste,
809
810 "yank_word_forward" => YankWordForward,
811 "yank_word_backward" => YankWordBackward,
812 "yank_to_line_end" => YankToLineEnd,
813 "yank_to_line_start" => YankToLineStart,
814 "yank_vi_word_end" => YankViWordEnd,
815
816 "add_cursor_above" => AddCursorAbove,
817 "add_cursor_below" => AddCursorBelow,
818 "add_cursor_next_match" => AddCursorNextMatch,
819 "remove_secondary_cursors" => RemoveSecondaryCursors,
820
821 "save" => Save,
822 "save_as" => SaveAs,
823 "open" => Open,
824 "switch_project" => SwitchProject,
825 "new" => New,
826 "close" => Close,
827 "close_tab" => CloseTab,
828 "quit" => Quit,
829 "force_quit" => ForceQuit,
830 "detach" => Detach,
831 "revert" => Revert,
832 "toggle_auto_revert" => ToggleAutoRevert,
833 "format_buffer" => FormatBuffer,
834 "trim_trailing_whitespace" => TrimTrailingWhitespace,
835 "ensure_final_newline" => EnsureFinalNewline,
836 "goto_line" => GotoLine,
837 "scan_line_index" => ScanLineIndex,
838 "goto_matching_bracket" => GoToMatchingBracket,
839 "jump_to_next_error" => JumpToNextError,
840 "jump_to_previous_error" => JumpToPreviousError,
841
842 "smart_home" => SmartHome,
843 "dedent_selection" => DedentSelection,
844 "toggle_comment" => ToggleComment,
845 "dabbrev_expand" => DabbrevExpand,
846 "toggle_fold" => ToggleFold,
847
848 "list_bookmarks" => ListBookmarks,
849
850 "toggle_search_case_sensitive" => ToggleSearchCaseSensitive,
851 "toggle_search_whole_word" => ToggleSearchWholeWord,
852 "toggle_search_regex" => ToggleSearchRegex,
853 "toggle_search_confirm_each" => ToggleSearchConfirmEach,
854
855 "start_macro_recording" => StartMacroRecording,
856 "stop_macro_recording" => StopMacroRecording,
857
858 "list_macros" => ListMacros,
859 "prompt_record_macro" => PromptRecordMacro,
860 "prompt_play_macro" => PromptPlayMacro,
861 "play_last_macro" => PlayLastMacro,
862 "prompt_set_bookmark" => PromptSetBookmark,
863 "prompt_jump_to_bookmark" => PromptJumpToBookmark,
864
865 "undo" => Undo,
866 "redo" => Redo,
867
868 "scroll_up" => ScrollUp,
869 "scroll_down" => ScrollDown,
870 "show_help" => ShowHelp,
871 "keyboard_shortcuts" => ShowKeyboardShortcuts,
872 "show_warnings" => ShowWarnings,
873 "show_status_log" => ShowStatusLog,
874 "show_lsp_status" => ShowLspStatus,
875 "clear_warnings" => ClearWarnings,
876 "command_palette" => CommandPalette,
877 "quick_open" => QuickOpen,
878 "toggle_line_wrap" => ToggleLineWrap,
879 "toggle_current_line_highlight" => ToggleCurrentLineHighlight,
880 "toggle_read_only" => ToggleReadOnly,
881 "toggle_page_view" => TogglePageView,
882 "set_page_width" => SetPageWidth,
883
884 "next_buffer" => NextBuffer,
885 "prev_buffer" => PrevBuffer,
886 "switch_to_previous_tab" => SwitchToPreviousTab,
887 "switch_to_tab_by_name" => SwitchToTabByName,
888 "scroll_tabs_left" => ScrollTabsLeft,
889 "scroll_tabs_right" => ScrollTabsRight,
890
891 "navigate_back" => NavigateBack,
892 "navigate_forward" => NavigateForward,
893
894 "split_horizontal" => SplitHorizontal,
895 "split_vertical" => SplitVertical,
896 "close_split" => CloseSplit,
897 "next_split" => NextSplit,
898 "prev_split" => PrevSplit,
899 "increase_split_size" => IncreaseSplitSize,
900 "decrease_split_size" => DecreaseSplitSize,
901 "toggle_maximize_split" => ToggleMaximizeSplit,
902
903 "prompt_confirm" => PromptConfirm,
904 "prompt_cancel" => PromptCancel,
905 "prompt_backspace" => PromptBackspace,
906 "prompt_move_left" => PromptMoveLeft,
907 "prompt_move_right" => PromptMoveRight,
908 "prompt_move_start" => PromptMoveStart,
909 "prompt_move_end" => PromptMoveEnd,
910 "prompt_select_prev" => PromptSelectPrev,
911 "prompt_select_next" => PromptSelectNext,
912 "prompt_page_up" => PromptPageUp,
913 "prompt_page_down" => PromptPageDown,
914 "prompt_accept_suggestion" => PromptAcceptSuggestion,
915 "prompt_delete_word_forward" => PromptDeleteWordForward,
916 "prompt_delete_word_backward" => PromptDeleteWordBackward,
917 "prompt_delete_to_line_end" => PromptDeleteToLineEnd,
918 "prompt_copy" => PromptCopy,
919 "prompt_cut" => PromptCut,
920 "prompt_paste" => PromptPaste,
921 "prompt_move_left_selecting" => PromptMoveLeftSelecting,
922 "prompt_move_right_selecting" => PromptMoveRightSelecting,
923 "prompt_move_home_selecting" => PromptMoveHomeSelecting,
924 "prompt_move_end_selecting" => PromptMoveEndSelecting,
925 "prompt_select_word_left" => PromptSelectWordLeft,
926 "prompt_select_word_right" => PromptSelectWordRight,
927 "prompt_select_all" => PromptSelectAll,
928 "file_browser_toggle_hidden" => FileBrowserToggleHidden,
929 "file_browser_toggle_detect_encoding" => FileBrowserToggleDetectEncoding,
930 "prompt_move_word_left" => PromptMoveWordLeft,
931 "prompt_move_word_right" => PromptMoveWordRight,
932 "prompt_delete" => PromptDelete,
933
934 "popup_select_next" => PopupSelectNext,
935 "popup_select_prev" => PopupSelectPrev,
936 "popup_page_up" => PopupPageUp,
937 "popup_page_down" => PopupPageDown,
938 "popup_confirm" => PopupConfirm,
939 "popup_cancel" => PopupCancel,
940
941 "toggle_file_explorer" => ToggleFileExplorer,
942 "toggle_menu_bar" => ToggleMenuBar,
943 "toggle_tab_bar" => ToggleTabBar,
944 "toggle_status_bar" => ToggleStatusBar,
945 "toggle_prompt_line" => TogglePromptLine,
946 "toggle_vertical_scrollbar" => ToggleVerticalScrollbar,
947 "toggle_horizontal_scrollbar" => ToggleHorizontalScrollbar,
948 "focus_file_explorer" => FocusFileExplorer,
949 "focus_editor" => FocusEditor,
950 "file_explorer_up" => FileExplorerUp,
951 "file_explorer_down" => FileExplorerDown,
952 "file_explorer_page_up" => FileExplorerPageUp,
953 "file_explorer_page_down" => FileExplorerPageDown,
954 "file_explorer_expand" => FileExplorerExpand,
955 "file_explorer_collapse" => FileExplorerCollapse,
956 "file_explorer_open" => FileExplorerOpen,
957 "file_explorer_refresh" => FileExplorerRefresh,
958 "file_explorer_new_file" => FileExplorerNewFile,
959 "file_explorer_new_directory" => FileExplorerNewDirectory,
960 "file_explorer_delete" => FileExplorerDelete,
961 "file_explorer_rename" => FileExplorerRename,
962 "file_explorer_toggle_hidden" => FileExplorerToggleHidden,
963 "file_explorer_toggle_gitignored" => FileExplorerToggleGitignored,
964 "file_explorer_search_clear" => FileExplorerSearchClear,
965 "file_explorer_search_backspace" => FileExplorerSearchBackspace,
966
967 "lsp_completion" => LspCompletion,
968 "lsp_goto_definition" => LspGotoDefinition,
969 "lsp_references" => LspReferences,
970 "lsp_rename" => LspRename,
971 "lsp_hover" => LspHover,
972 "lsp_signature_help" => LspSignatureHelp,
973 "lsp_code_actions" => LspCodeActions,
974 "lsp_restart" => LspRestart,
975 "lsp_stop" => LspStop,
976 "lsp_toggle_for_buffer" => LspToggleForBuffer,
977 "toggle_inlay_hints" => ToggleInlayHints,
978 "toggle_mouse_hover" => ToggleMouseHover,
979
980 "toggle_line_numbers" => ToggleLineNumbers,
981 "toggle_scroll_sync" => ToggleScrollSync,
982 "toggle_mouse_capture" => ToggleMouseCapture,
983 "toggle_debug_highlights" => ToggleDebugHighlights,
984 "set_background" => SetBackground,
985 "set_background_blend" => SetBackgroundBlend,
986 "inspect_theme_at_cursor" => InspectThemeAtCursor,
987 "select_theme" => SelectTheme,
988 "select_keybinding_map" => SelectKeybindingMap,
989 "select_cursor_style" => SelectCursorStyle,
990 "select_locale" => SelectLocale,
991
992 "set_tab_size" => SetTabSize,
993 "set_line_ending" => SetLineEnding,
994 "set_encoding" => SetEncoding,
995 "reload_with_encoding" => ReloadWithEncoding,
996 "set_language" => SetLanguage,
997 "toggle_indentation_style" => ToggleIndentationStyle,
998 "toggle_tab_indicators" => ToggleTabIndicators,
999 "toggle_whitespace_indicators" => ToggleWhitespaceIndicators,
1000 "reset_buffer_settings" => ResetBufferSettings,
1001 "add_ruler" => AddRuler,
1002 "remove_ruler" => RemoveRuler,
1003
1004 "dump_config" => DumpConfig,
1005
1006 "search" => Search,
1007 "find_in_selection" => FindInSelection,
1008 "find_next" => FindNext,
1009 "find_previous" => FindPrevious,
1010 "find_selection_next" => FindSelectionNext,
1011 "find_selection_previous" => FindSelectionPrevious,
1012 "replace" => Replace,
1013 "query_replace" => QueryReplace,
1014
1015 "menu_activate" => MenuActivate,
1016 "menu_close" => MenuClose,
1017 "menu_left" => MenuLeft,
1018 "menu_right" => MenuRight,
1019 "menu_up" => MenuUp,
1020 "menu_down" => MenuDown,
1021 "menu_execute" => MenuExecute,
1022
1023 "open_terminal" => OpenTerminal,
1024 "close_terminal" => CloseTerminal,
1025 "focus_terminal" => FocusTerminal,
1026 "terminal_escape" => TerminalEscape,
1027 "toggle_keyboard_capture" => ToggleKeyboardCapture,
1028 "terminal_paste" => TerminalPaste,
1029
1030 "shell_command" => ShellCommand,
1031 "shell_command_replace" => ShellCommandReplace,
1032
1033 "to_upper_case" => ToUpperCase,
1034 "to_lower_case" => ToLowerCase,
1035 "toggle_case" => ToggleCase,
1036 "sort_lines" => SortLines,
1037
1038 "calibrate_input" => CalibrateInput,
1039 "event_debug" => EventDebug,
1040 "load_plugin_from_buffer" => LoadPluginFromBuffer,
1041 "open_keybinding_editor" => OpenKeybindingEditor,
1042
1043 "noop" => None,
1044
1045 "open_settings" => OpenSettings,
1046 "close_settings" => CloseSettings,
1047 "settings_save" => SettingsSave,
1048 "settings_reset" => SettingsReset,
1049 "settings_toggle_focus" => SettingsToggleFocus,
1050 "settings_activate" => SettingsActivate,
1051 "settings_search" => SettingsSearch,
1052 "settings_help" => SettingsHelp,
1053 "settings_increment" => SettingsIncrement,
1054 "settings_decrement" => SettingsDecrement,
1055 "settings_inherit" => SettingsInherit,
1056 }
1057 alias {
1058 "toggle_compose_mode" => TogglePageView,
1059 "set_compose_width" => SetPageWidth,
1060 }
1061 with_char {
1062 "insert_char" => InsertChar,
1063 "set_bookmark" => SetBookmark,
1064 "jump_to_bookmark" => JumpToBookmark,
1065 "clear_bookmark" => ClearBookmark,
1066 "play_macro" => PlayMacro,
1067 "toggle_macro_recording" => ToggleMacroRecording,
1068 "show_macro" => ShowMacro,
1069 }
1070 custom {
1071 "copy_with_theme" => CopyWithTheme : {
1072 let theme = args.get("theme").and_then(|v| v.as_str()).unwrap_or("");
1074 Self::CopyWithTheme(theme.to_string())
1075 },
1076 "menu_open" => MenuOpen : {
1077 let name = args.get("name")?.as_str()?;
1078 Self::MenuOpen(name.to_string())
1079 },
1080 "switch_keybinding_map" => SwitchKeybindingMap : {
1081 let map_name = args.get("map")?.as_str()?;
1082 Self::SwitchKeybindingMap(map_name.to_string())
1083 },
1084 "prompt_confirm_with_text" => PromptConfirmWithText : {
1085 let text = args.get("text")?.as_str()?;
1086 Self::PromptConfirmWithText(text.to_string())
1087 },
1088 }
1089 }
1090
1091 pub fn is_movement_or_editing(&self) -> bool {
1094 matches!(
1095 self,
1096 Action::MoveLeft
1098 | Action::MoveRight
1099 | Action::MoveUp
1100 | Action::MoveDown
1101 | Action::MoveWordLeft
1102 | Action::MoveWordRight
1103 | Action::MoveWordEnd
1104 | Action::ViMoveWordEnd
1105 | Action::MoveLeftInLine
1106 | Action::MoveRightInLine
1107 | Action::MoveLineStart
1108 | Action::MoveLineEnd
1109 | Action::MovePageUp
1110 | Action::MovePageDown
1111 | Action::MoveDocumentStart
1112 | Action::MoveDocumentEnd
1113 | Action::SelectLeft
1115 | Action::SelectRight
1116 | Action::SelectUp
1117 | Action::SelectDown
1118 | Action::SelectToParagraphUp
1119 | Action::SelectToParagraphDown
1120 | Action::SelectWordLeft
1121 | Action::SelectWordRight
1122 | Action::SelectWordEnd
1123 | Action::ViSelectWordEnd
1124 | Action::SelectLineStart
1125 | Action::SelectLineEnd
1126 | Action::SelectDocumentStart
1127 | Action::SelectDocumentEnd
1128 | Action::SelectPageUp
1129 | Action::SelectPageDown
1130 | Action::SelectAll
1131 | Action::SelectWord
1132 | Action::SelectLine
1133 | Action::ExpandSelection
1134 | Action::BlockSelectLeft
1136 | Action::BlockSelectRight
1137 | Action::BlockSelectUp
1138 | Action::BlockSelectDown
1139 | Action::InsertChar(_)
1141 | Action::InsertNewline
1142 | Action::InsertTab
1143 | Action::DeleteBackward
1144 | Action::DeleteForward
1145 | Action::DeleteWordBackward
1146 | Action::DeleteWordForward
1147 | Action::DeleteLine
1148 | Action::DeleteToLineEnd
1149 | Action::DeleteToLineStart
1150 | Action::TransposeChars
1151 | Action::OpenLine
1152 | Action::DuplicateLine
1153 | Action::MoveLineUp
1154 | Action::MoveLineDown
1155 | Action::Cut
1157 | Action::Paste
1158 | Action::Undo
1160 | Action::Redo
1161 )
1162 }
1163
1164 pub fn is_editing(&self) -> bool {
1167 matches!(
1168 self,
1169 Action::InsertChar(_)
1170 | Action::InsertNewline
1171 | Action::InsertTab
1172 | Action::DeleteBackward
1173 | Action::DeleteForward
1174 | Action::DeleteWordBackward
1175 | Action::DeleteWordForward
1176 | Action::DeleteLine
1177 | Action::DeleteToLineEnd
1178 | Action::DeleteToLineStart
1179 | Action::DeleteViWordEnd
1180 | Action::TransposeChars
1181 | Action::OpenLine
1182 | Action::DuplicateLine
1183 | Action::MoveLineUp
1184 | Action::MoveLineDown
1185 | Action::Cut
1186 | Action::Paste
1187 )
1188 }
1189}
1190
1191#[derive(Debug, Clone, PartialEq)]
1193pub enum ChordResolution {
1194 Complete(Action),
1196 Partial,
1198 NoMatch,
1200}
1201
1202#[derive(Clone)]
1204pub struct KeybindingResolver {
1205 bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1208
1209 default_bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1211
1212 plugin_defaults: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1215
1216 chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1219
1220 default_chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1222
1223 plugin_chord_defaults: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1225}
1226
1227impl KeybindingResolver {
1228 pub fn new(config: &Config) -> Self {
1230 let mut resolver = Self {
1231 bindings: HashMap::new(),
1232 default_bindings: HashMap::new(),
1233 plugin_defaults: HashMap::new(),
1234 chord_bindings: HashMap::new(),
1235 default_chord_bindings: HashMap::new(),
1236 plugin_chord_defaults: HashMap::new(),
1237 };
1238
1239 let map_bindings = config.resolve_keymap(&config.active_keybinding_map);
1241 resolver.load_default_bindings_from_vec(&map_bindings);
1242
1243 resolver.load_bindings_from_vec(&config.keybindings);
1245
1246 resolver
1247 }
1248
1249 fn load_default_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1251 for binding in bindings {
1252 let context = if let Some(ref when) = binding.when {
1254 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1255 } else {
1256 KeyContext::Normal
1257 };
1258
1259 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1260 if !binding.keys.is_empty() {
1262 let mut sequence = Vec::new();
1264 for key_press in &binding.keys {
1265 if let Some(key_code) = Self::parse_key(&key_press.key) {
1266 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1267 sequence.push((key_code, modifiers));
1268 } else {
1269 break;
1271 }
1272 }
1273
1274 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1276 self.default_chord_bindings
1277 .entry(context)
1278 .or_default()
1279 .insert(sequence, action);
1280 }
1281 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1282 let modifiers = Self::parse_modifiers(&binding.modifiers);
1284
1285 self.insert_binding_with_equivalents(
1287 context,
1288 key_code,
1289 modifiers,
1290 action,
1291 &binding.key,
1292 );
1293 }
1294 }
1295 }
1296 }
1297
1298 fn insert_binding_with_equivalents(
1301 &mut self,
1302 context: KeyContext,
1303 key_code: KeyCode,
1304 modifiers: KeyModifiers,
1305 action: Action,
1306 key_name: &str,
1307 ) {
1308 let context_bindings = self.default_bindings.entry(context.clone()).or_default();
1309
1310 context_bindings.insert((key_code, modifiers), action.clone());
1312
1313 let equivalents = terminal_key_equivalents(key_code, modifiers);
1315 for (equiv_key, equiv_mods) in equivalents {
1316 if let Some(existing_action) = context_bindings.get(&(equiv_key, equiv_mods)) {
1318 if existing_action != &action {
1320 let equiv_name = format!("{:?}", equiv_key);
1321 tracing::warn!(
1322 "Terminal key equivalent conflict in {:?} context: {} (equivalent of {}) \
1323 is bound to {:?}, but {} is bound to {:?}. \
1324 The explicit binding takes precedence.",
1325 context,
1326 equiv_name,
1327 key_name,
1328 existing_action,
1329 key_name,
1330 action
1331 );
1332 }
1333 } else {
1335 context_bindings.insert((equiv_key, equiv_mods), action.clone());
1337 }
1338 }
1339 }
1340
1341 fn load_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1343 for binding in bindings {
1344 let context = if let Some(ref when) = binding.when {
1346 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1347 } else {
1348 KeyContext::Normal
1349 };
1350
1351 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1352 if !binding.keys.is_empty() {
1354 let mut sequence = Vec::new();
1356 for key_press in &binding.keys {
1357 if let Some(key_code) = Self::parse_key(&key_press.key) {
1358 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1359 sequence.push((key_code, modifiers));
1360 } else {
1361 break;
1363 }
1364 }
1365
1366 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1368 self.chord_bindings
1369 .entry(context)
1370 .or_default()
1371 .insert(sequence, action);
1372 }
1373 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1374 let modifiers = Self::parse_modifiers(&binding.modifiers);
1376 self.bindings
1377 .entry(context)
1378 .or_default()
1379 .insert((key_code, modifiers), action);
1380 }
1381 }
1382 }
1383 }
1384
1385 pub fn load_plugin_default(
1387 &mut self,
1388 context: KeyContext,
1389 key_code: KeyCode,
1390 modifiers: KeyModifiers,
1391 action: Action,
1392 ) {
1393 self.plugin_defaults
1394 .entry(context)
1395 .or_default()
1396 .insert((key_code, modifiers), action);
1397 }
1398
1399 pub fn load_plugin_chord_default(
1401 &mut self,
1402 context: KeyContext,
1403 sequence: Vec<(KeyCode, KeyModifiers)>,
1404 action: Action,
1405 ) {
1406 self.plugin_chord_defaults
1407 .entry(context)
1408 .or_default()
1409 .insert(sequence, action);
1410 }
1411
1412 pub fn clear_plugin_defaults_for_mode(&mut self, mode_name: &str) {
1414 let context = KeyContext::Mode(mode_name.to_string());
1415 self.plugin_defaults.remove(&context);
1416 self.plugin_chord_defaults.remove(&context);
1417 }
1418
1419 pub fn get_plugin_defaults(
1421 &self,
1422 ) -> &HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>> {
1423 &self.plugin_defaults
1424 }
1425
1426 fn is_application_wide_action(action: &Action) -> bool {
1428 matches!(
1429 action,
1430 Action::Quit
1431 | Action::ForceQuit
1432 | Action::Save
1433 | Action::SaveAs
1434 | Action::ShowHelp
1435 | Action::ShowKeyboardShortcuts
1436 | Action::PromptCancel | Action::PopupCancel )
1439 }
1440
1441 pub fn is_terminal_ui_action(action: &Action) -> bool {
1445 matches!(
1446 action,
1447 Action::CommandPalette
1449 | Action::QuickOpen
1450 | Action::OpenSettings
1451 | Action::MenuActivate
1452 | Action::MenuOpen(_)
1453 | Action::ShowHelp
1454 | Action::ShowKeyboardShortcuts
1455 | Action::Quit
1456 | Action::ForceQuit
1457 | Action::NextSplit
1459 | Action::PrevSplit
1460 | Action::SplitHorizontal
1461 | Action::SplitVertical
1462 | Action::CloseSplit
1463 | Action::ToggleMaximizeSplit
1464 | Action::NextBuffer
1466 | Action::PrevBuffer
1467 | Action::Close
1468 | Action::ScrollTabsLeft
1469 | Action::ScrollTabsRight
1470 | Action::TerminalEscape
1472 | Action::ToggleKeyboardCapture
1473 | Action::OpenTerminal
1474 | Action::CloseTerminal
1475 | Action::TerminalPaste
1476 | Action::ToggleFileExplorer
1478 | Action::ToggleMenuBar
1480 )
1481 }
1482
1483 pub fn resolve_chord(
1489 &self,
1490 chord_state: &[(KeyCode, KeyModifiers)],
1491 event: &KeyEvent,
1492 context: KeyContext,
1493 ) -> ChordResolution {
1494 let mut full_sequence: Vec<(KeyCode, KeyModifiers)> = chord_state
1496 .iter()
1497 .map(|(c, m)| normalize_key(*c, *m))
1498 .collect();
1499 let (norm_code, norm_mods) = normalize_key(event.code, event.modifiers);
1500 full_sequence.push((norm_code, norm_mods));
1501
1502 tracing::trace!(
1503 "KeybindingResolver.resolve_chord: sequence={:?}, context={:?}",
1504 full_sequence,
1505 context
1506 );
1507
1508 let search_order = vec![
1510 (&self.chord_bindings, &KeyContext::Global, "custom global"),
1511 (
1512 &self.default_chord_bindings,
1513 &KeyContext::Global,
1514 "default global",
1515 ),
1516 (&self.chord_bindings, &context, "custom context"),
1517 (&self.default_chord_bindings, &context, "default context"),
1518 (
1519 &self.plugin_chord_defaults,
1520 &context,
1521 "plugin default context",
1522 ),
1523 ];
1524
1525 let mut has_partial_match = false;
1526
1527 for (binding_map, bind_context, label) in search_order {
1528 if let Some(context_chords) = binding_map.get(bind_context) {
1529 if let Some(action) = context_chords.get(&full_sequence) {
1531 tracing::trace!(" -> Complete chord match in {}: {:?}", label, action);
1532 return ChordResolution::Complete(action.clone());
1533 }
1534
1535 for (chord_seq, _) in context_chords.iter() {
1537 if chord_seq.len() > full_sequence.len()
1538 && chord_seq[..full_sequence.len()] == full_sequence[..]
1539 {
1540 tracing::trace!(" -> Partial chord match in {}", label);
1541 has_partial_match = true;
1542 break;
1543 }
1544 }
1545 }
1546 }
1547
1548 if has_partial_match {
1549 ChordResolution::Partial
1550 } else {
1551 tracing::trace!(" -> No chord match");
1552 ChordResolution::NoMatch
1553 }
1554 }
1555
1556 pub fn resolve(&self, event: &KeyEvent, context: KeyContext) -> Action {
1558 let (norm_code, norm_mods) = normalize_key(event.code, event.modifiers);
1561 let norm = &(norm_code, norm_mods);
1562 tracing::trace!(
1563 "KeybindingResolver.resolve: code={:?}, modifiers={:?}, context={:?}",
1564 event.code,
1565 event.modifiers,
1566 context
1567 );
1568
1569 if let Some(global_bindings) = self.bindings.get(&KeyContext::Global) {
1571 if let Some(action) = global_bindings.get(norm) {
1572 tracing::trace!(" -> Found in custom global bindings: {:?}", action);
1573 return action.clone();
1574 }
1575 }
1576
1577 if let Some(global_bindings) = self.default_bindings.get(&KeyContext::Global) {
1578 if let Some(action) = global_bindings.get(norm) {
1579 tracing::trace!(" -> Found in default global bindings: {:?}", action);
1580 return action.clone();
1581 }
1582 }
1583
1584 if let Some(context_bindings) = self.bindings.get(&context) {
1586 if let Some(action) = context_bindings.get(norm) {
1587 tracing::trace!(
1588 " -> Found in custom {} bindings: {:?}",
1589 context.to_when_clause(),
1590 action
1591 );
1592 return action.clone();
1593 }
1594 }
1595
1596 if let Some(context_bindings) = self.default_bindings.get(&context) {
1598 if let Some(action) = context_bindings.get(norm) {
1599 tracing::trace!(
1600 " -> Found in default {} bindings: {:?}",
1601 context.to_when_clause(),
1602 action
1603 );
1604 return action.clone();
1605 }
1606 }
1607
1608 if let Some(plugin_bindings) = self.plugin_defaults.get(&context) {
1610 if let Some(action) = plugin_bindings.get(norm) {
1611 tracing::trace!(
1612 " -> Found in plugin default {} bindings: {:?}",
1613 context.to_when_clause(),
1614 action
1615 );
1616 return action.clone();
1617 }
1618 }
1619
1620 if context != KeyContext::Normal {
1623 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
1624 if let Some(action) = normal_bindings.get(norm) {
1625 if Self::is_application_wide_action(action) {
1626 tracing::trace!(
1627 " -> Found application-wide action in custom normal bindings: {:?}",
1628 action
1629 );
1630 return action.clone();
1631 }
1632 }
1633 }
1634
1635 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
1636 if let Some(action) = normal_bindings.get(norm) {
1637 if Self::is_application_wide_action(action) {
1638 tracing::trace!(
1639 " -> Found application-wide action in default normal bindings: {:?}",
1640 action
1641 );
1642 return action.clone();
1643 }
1644 }
1645 }
1646 }
1647
1648 if context.allows_text_input() && is_text_input_modifier(event.modifiers) {
1650 if let KeyCode::Char(c) = event.code {
1651 tracing::trace!(" -> Character input: '{}'", c);
1652 return Action::InsertChar(c);
1653 }
1654 }
1655
1656 tracing::trace!(" -> No binding found, returning Action::None");
1657 Action::None
1658 }
1659
1660 pub fn resolve_in_context_only(&self, event: &KeyEvent, context: KeyContext) -> Option<Action> {
1665 let norm = normalize_key(event.code, event.modifiers);
1666 if let Some(context_bindings) = self.bindings.get(&context) {
1668 if let Some(action) = context_bindings.get(&norm) {
1669 return Some(action.clone());
1670 }
1671 }
1672
1673 if let Some(context_bindings) = self.default_bindings.get(&context) {
1675 if let Some(action) = context_bindings.get(&norm) {
1676 return Some(action.clone());
1677 }
1678 }
1679
1680 None
1681 }
1682
1683 pub fn resolve_terminal_ui_action(&self, event: &KeyEvent) -> Action {
1687 let norm = normalize_key(event.code, event.modifiers);
1688 tracing::trace!(
1689 "KeybindingResolver.resolve_terminal_ui_action: code={:?}, modifiers={:?}",
1690 event.code,
1691 event.modifiers
1692 );
1693
1694 for bindings in [&self.bindings, &self.default_bindings] {
1696 if let Some(terminal_bindings) = bindings.get(&KeyContext::Terminal) {
1697 if let Some(action) = terminal_bindings.get(&norm) {
1698 if Self::is_terminal_ui_action(action) {
1699 tracing::trace!(" -> Found UI action in terminal bindings: {:?}", action);
1700 return action.clone();
1701 }
1702 }
1703 }
1704 }
1705
1706 for bindings in [&self.bindings, &self.default_bindings] {
1708 if let Some(global_bindings) = bindings.get(&KeyContext::Global) {
1709 if let Some(action) = global_bindings.get(&norm) {
1710 if Self::is_terminal_ui_action(action) {
1711 tracing::trace!(" -> Found UI action in global bindings: {:?}", action);
1712 return action.clone();
1713 }
1714 }
1715 }
1716 }
1717
1718 for bindings in [&self.bindings, &self.default_bindings] {
1720 if let Some(normal_bindings) = bindings.get(&KeyContext::Normal) {
1721 if let Some(action) = normal_bindings.get(&norm) {
1722 if Self::is_terminal_ui_action(action) {
1723 tracing::trace!(" -> Found UI action in normal bindings: {:?}", action);
1724 return action.clone();
1725 }
1726 }
1727 }
1728 }
1729
1730 tracing::trace!(" -> No UI action found");
1731 Action::None
1732 }
1733
1734 pub fn find_keybinding_for_action(
1737 &self,
1738 action_name: &str,
1739 context: KeyContext,
1740 ) -> Option<String> {
1741 let target_action = Action::from_str(action_name, &HashMap::new())?;
1743
1744 let search_maps = vec![
1746 self.bindings.get(&context),
1747 self.bindings.get(&KeyContext::Global),
1748 self.default_bindings.get(&context),
1749 self.default_bindings.get(&KeyContext::Global),
1750 ];
1751
1752 for map in search_maps.into_iter().flatten() {
1753 let mut matches: Vec<(KeyCode, KeyModifiers)> = map
1755 .iter()
1756 .filter(|(_, action)| {
1757 std::mem::discriminant(*action) == std::mem::discriminant(&target_action)
1758 })
1759 .map(|((key_code, modifiers), _)| (*key_code, *modifiers))
1760 .collect();
1761
1762 if !matches.is_empty() {
1763 matches.sort_by(|(key_a, mod_a), (key_b, mod_b)| {
1765 let mod_count_a = mod_a.bits().count_ones();
1767 let mod_count_b = mod_b.bits().count_ones();
1768 match mod_count_a.cmp(&mod_count_b) {
1769 std::cmp::Ordering::Equal => {
1770 match mod_a.bits().cmp(&mod_b.bits()) {
1772 std::cmp::Ordering::Equal => {
1773 Self::key_code_sort_key(key_a)
1775 .cmp(&Self::key_code_sort_key(key_b))
1776 }
1777 other => other,
1778 }
1779 }
1780 other => other,
1781 }
1782 });
1783
1784 let (key_code, modifiers) = matches[0];
1785 return Some(format_keybinding(&key_code, &modifiers));
1786 }
1787 }
1788
1789 None
1790 }
1791
1792 fn key_code_sort_key(key_code: &KeyCode) -> (u8, u32) {
1794 match key_code {
1795 KeyCode::Char(c) => (0, *c as u32),
1796 KeyCode::F(n) => (1, *n as u32),
1797 KeyCode::Enter => (2, 0),
1798 KeyCode::Tab => (2, 1),
1799 KeyCode::Backspace => (2, 2),
1800 KeyCode::Delete => (2, 3),
1801 KeyCode::Esc => (2, 4),
1802 KeyCode::Left => (3, 0),
1803 KeyCode::Right => (3, 1),
1804 KeyCode::Up => (3, 2),
1805 KeyCode::Down => (3, 3),
1806 KeyCode::Home => (3, 4),
1807 KeyCode::End => (3, 5),
1808 KeyCode::PageUp => (3, 6),
1809 KeyCode::PageDown => (3, 7),
1810 _ => (255, 0),
1811 }
1812 }
1813
1814 pub fn find_menu_mnemonic(&self, menu_name: &str) -> Option<char> {
1817 let search_maps = vec![
1819 self.bindings.get(&KeyContext::Normal),
1820 self.bindings.get(&KeyContext::Global),
1821 self.default_bindings.get(&KeyContext::Normal),
1822 self.default_bindings.get(&KeyContext::Global),
1823 ];
1824
1825 for map in search_maps.into_iter().flatten() {
1826 for ((key_code, modifiers), action) in map {
1827 if let Action::MenuOpen(name) = action {
1829 if name.eq_ignore_ascii_case(menu_name) && *modifiers == KeyModifiers::ALT {
1830 if let KeyCode::Char(c) = key_code {
1832 return Some(c.to_ascii_lowercase());
1833 }
1834 }
1835 }
1836 }
1837 }
1838
1839 None
1840 }
1841
1842 fn parse_key(key: &str) -> Option<KeyCode> {
1844 let lower = key.to_lowercase();
1845 match lower.as_str() {
1846 "enter" => Some(KeyCode::Enter),
1847 "backspace" => Some(KeyCode::Backspace),
1848 "delete" | "del" => Some(KeyCode::Delete),
1849 "tab" => Some(KeyCode::Tab),
1850 "backtab" => Some(KeyCode::BackTab),
1851 "esc" | "escape" => Some(KeyCode::Esc),
1852 "space" => Some(KeyCode::Char(' ')),
1853
1854 "left" => Some(KeyCode::Left),
1855 "right" => Some(KeyCode::Right),
1856 "up" => Some(KeyCode::Up),
1857 "down" => Some(KeyCode::Down),
1858 "home" => Some(KeyCode::Home),
1859 "end" => Some(KeyCode::End),
1860 "pageup" => Some(KeyCode::PageUp),
1861 "pagedown" => Some(KeyCode::PageDown),
1862
1863 s if s.len() == 1 => s.chars().next().map(KeyCode::Char),
1864 s if s.starts_with('f') && s.len() >= 2 => s[1..].parse::<u8>().ok().map(KeyCode::F),
1866 _ => None,
1867 }
1868 }
1869
1870 fn parse_modifiers(modifiers: &[String]) -> KeyModifiers {
1872 let mut result = KeyModifiers::empty();
1873 for m in modifiers {
1874 match m.to_lowercase().as_str() {
1875 "ctrl" | "control" => result |= KeyModifiers::CONTROL,
1876 "shift" => result |= KeyModifiers::SHIFT,
1877 "alt" => result |= KeyModifiers::ALT,
1878 "super" | "cmd" | "command" | "meta" => result |= KeyModifiers::SUPER,
1879 _ => {}
1880 }
1881 }
1882 result
1883 }
1884
1885 pub fn get_all_bindings(&self) -> Vec<(String, String)> {
1889 let mut bindings = Vec::new();
1890
1891 for context in &[
1893 KeyContext::Normal,
1894 KeyContext::Prompt,
1895 KeyContext::Popup,
1896 KeyContext::FileExplorer,
1897 KeyContext::Menu,
1898 ] {
1899 let mut all_keys: HashMap<(KeyCode, KeyModifiers), Action> = HashMap::new();
1900
1901 if let Some(context_defaults) = self.default_bindings.get(context) {
1903 for (key, action) in context_defaults {
1904 all_keys.insert(*key, action.clone());
1905 }
1906 }
1907
1908 if let Some(context_bindings) = self.bindings.get(context) {
1910 for (key, action) in context_bindings {
1911 all_keys.insert(*key, action.clone());
1912 }
1913 }
1914
1915 let context_str = if *context != KeyContext::Normal {
1917 format!("[{}] ", context.to_when_clause())
1918 } else {
1919 String::new()
1920 };
1921
1922 for ((key_code, modifiers), action) in all_keys {
1923 let key_str = Self::format_key(key_code, modifiers);
1924 let action_str = format!("{}{}", context_str, Self::format_action(&action));
1925 bindings.push((key_str, action_str));
1926 }
1927 }
1928
1929 bindings.sort_by(|a, b| a.1.cmp(&b.1));
1931
1932 bindings
1933 }
1934
1935 fn format_key(key_code: KeyCode, modifiers: KeyModifiers) -> String {
1937 format_keybinding(&key_code, &modifiers)
1938 }
1939
1940 fn format_action(action: &Action) -> String {
1942 match action {
1943 Action::InsertChar(c) => t!("action.insert_char", char = c),
1944 Action::InsertNewline => t!("action.insert_newline"),
1945 Action::InsertTab => t!("action.insert_tab"),
1946 Action::MoveLeft => t!("action.move_left"),
1947 Action::MoveRight => t!("action.move_right"),
1948 Action::MoveUp => t!("action.move_up"),
1949 Action::MoveDown => t!("action.move_down"),
1950 Action::MoveWordLeft => t!("action.move_word_left"),
1951 Action::MoveWordRight => t!("action.move_word_right"),
1952 Action::MoveWordEnd => t!("action.move_word_end"),
1953 Action::ViMoveWordEnd => t!("action.move_word_end"),
1954 Action::MoveLeftInLine => t!("action.move_left"),
1955 Action::MoveRightInLine => t!("action.move_right"),
1956 Action::MoveLineStart => t!("action.move_line_start"),
1957 Action::MoveLineEnd => t!("action.move_line_end"),
1958 Action::MoveLineUp => t!("action.move_line_up"),
1959 Action::MoveLineDown => t!("action.move_line_down"),
1960 Action::MovePageUp => t!("action.move_page_up"),
1961 Action::MovePageDown => t!("action.move_page_down"),
1962 Action::MoveDocumentStart => t!("action.move_document_start"),
1963 Action::MoveDocumentEnd => t!("action.move_document_end"),
1964 Action::SelectLeft => t!("action.select_left"),
1965 Action::SelectRight => t!("action.select_right"),
1966 Action::SelectUp => t!("action.select_up"),
1967 Action::SelectDown => t!("action.select_down"),
1968 Action::SelectToParagraphUp => t!("action.select_to_paragraph_up"),
1969 Action::SelectToParagraphDown => t!("action.select_to_paragraph_down"),
1970 Action::SelectWordLeft => t!("action.select_word_left"),
1971 Action::SelectWordRight => t!("action.select_word_right"),
1972 Action::SelectWordEnd => t!("action.select_word_end"),
1973 Action::ViSelectWordEnd => t!("action.select_word_end"),
1974 Action::SelectLineStart => t!("action.select_line_start"),
1975 Action::SelectLineEnd => t!("action.select_line_end"),
1976 Action::SelectDocumentStart => t!("action.select_document_start"),
1977 Action::SelectDocumentEnd => t!("action.select_document_end"),
1978 Action::SelectPageUp => t!("action.select_page_up"),
1979 Action::SelectPageDown => t!("action.select_page_down"),
1980 Action::SelectAll => t!("action.select_all"),
1981 Action::SelectWord => t!("action.select_word"),
1982 Action::SelectLine => t!("action.select_line"),
1983 Action::ExpandSelection => t!("action.expand_selection"),
1984 Action::BlockSelectLeft => t!("action.block_select_left"),
1985 Action::BlockSelectRight => t!("action.block_select_right"),
1986 Action::BlockSelectUp => t!("action.block_select_up"),
1987 Action::BlockSelectDown => t!("action.block_select_down"),
1988 Action::DeleteBackward => t!("action.delete_backward"),
1989 Action::DeleteForward => t!("action.delete_forward"),
1990 Action::DeleteWordBackward => t!("action.delete_word_backward"),
1991 Action::DeleteWordForward => t!("action.delete_word_forward"),
1992 Action::DeleteLine => t!("action.delete_line"),
1993 Action::DeleteToLineEnd => t!("action.delete_to_line_end"),
1994 Action::DeleteToLineStart => t!("action.delete_to_line_start"),
1995 Action::DeleteViWordEnd => t!("action.delete_word_forward"),
1996 Action::TransposeChars => t!("action.transpose_chars"),
1997 Action::OpenLine => t!("action.open_line"),
1998 Action::DuplicateLine => t!("action.duplicate_line"),
1999 Action::Recenter => t!("action.recenter"),
2000 Action::SetMark => t!("action.set_mark"),
2001 Action::Copy => t!("action.copy"),
2002 Action::CopyWithTheme(theme) if theme.is_empty() => t!("action.copy_with_formatting"),
2003 Action::CopyWithTheme(theme) => t!("action.copy_with_theme", theme = theme),
2004 Action::Cut => t!("action.cut"),
2005 Action::Paste => t!("action.paste"),
2006 Action::YankWordForward => t!("action.yank_word_forward"),
2007 Action::YankWordBackward => t!("action.yank_word_backward"),
2008 Action::YankToLineEnd => t!("action.yank_to_line_end"),
2009 Action::YankToLineStart => t!("action.yank_to_line_start"),
2010 Action::YankViWordEnd => t!("action.yank_word_forward"),
2011 Action::AddCursorAbove => t!("action.add_cursor_above"),
2012 Action::AddCursorBelow => t!("action.add_cursor_below"),
2013 Action::AddCursorNextMatch => t!("action.add_cursor_next_match"),
2014 Action::RemoveSecondaryCursors => t!("action.remove_secondary_cursors"),
2015 Action::Save => t!("action.save"),
2016 Action::SaveAs => t!("action.save_as"),
2017 Action::Open => t!("action.open"),
2018 Action::SwitchProject => t!("action.switch_project"),
2019 Action::New => t!("action.new"),
2020 Action::Close => t!("action.close"),
2021 Action::CloseTab => t!("action.close_tab"),
2022 Action::Quit => t!("action.quit"),
2023 Action::ForceQuit => t!("action.force_quit"),
2024 Action::Detach => t!("action.detach"),
2025 Action::Revert => t!("action.revert"),
2026 Action::ToggleAutoRevert => t!("action.toggle_auto_revert"),
2027 Action::FormatBuffer => t!("action.format_buffer"),
2028 Action::TrimTrailingWhitespace => t!("action.trim_trailing_whitespace"),
2029 Action::EnsureFinalNewline => t!("action.ensure_final_newline"),
2030 Action::GotoLine => t!("action.goto_line"),
2031 Action::ScanLineIndex => t!("action.scan_line_index"),
2032 Action::GoToMatchingBracket => t!("action.goto_matching_bracket"),
2033 Action::JumpToNextError => t!("action.jump_to_next_error"),
2034 Action::JumpToPreviousError => t!("action.jump_to_previous_error"),
2035 Action::SmartHome => t!("action.smart_home"),
2036 Action::DedentSelection => t!("action.dedent_selection"),
2037 Action::ToggleComment => t!("action.toggle_comment"),
2038 Action::DabbrevExpand => std::borrow::Cow::Borrowed("Expand abbreviation (dabbrev)"),
2039 Action::ToggleFold => t!("action.toggle_fold"),
2040 Action::SetBookmark(c) => t!("action.set_bookmark", key = c),
2041 Action::JumpToBookmark(c) => t!("action.jump_to_bookmark", key = c),
2042 Action::ClearBookmark(c) => t!("action.clear_bookmark", key = c),
2043 Action::ListBookmarks => t!("action.list_bookmarks"),
2044 Action::ToggleSearchCaseSensitive => t!("action.toggle_search_case_sensitive"),
2045 Action::ToggleSearchWholeWord => t!("action.toggle_search_whole_word"),
2046 Action::ToggleSearchRegex => t!("action.toggle_search_regex"),
2047 Action::ToggleSearchConfirmEach => t!("action.toggle_search_confirm_each"),
2048 Action::StartMacroRecording => t!("action.start_macro_recording"),
2049 Action::StopMacroRecording => t!("action.stop_macro_recording"),
2050 Action::PlayMacro(c) => t!("action.play_macro", key = c),
2051 Action::ToggleMacroRecording(c) => t!("action.toggle_macro_recording", key = c),
2052 Action::ShowMacro(c) => t!("action.show_macro", key = c),
2053 Action::ListMacros => t!("action.list_macros"),
2054 Action::PromptRecordMacro => t!("action.prompt_record_macro"),
2055 Action::PromptPlayMacro => t!("action.prompt_play_macro"),
2056 Action::PlayLastMacro => t!("action.play_last_macro"),
2057 Action::PromptSetBookmark => t!("action.prompt_set_bookmark"),
2058 Action::PromptJumpToBookmark => t!("action.prompt_jump_to_bookmark"),
2059 Action::Undo => t!("action.undo"),
2060 Action::Redo => t!("action.redo"),
2061 Action::ScrollUp => t!("action.scroll_up"),
2062 Action::ScrollDown => t!("action.scroll_down"),
2063 Action::ShowHelp => t!("action.show_help"),
2064 Action::ShowKeyboardShortcuts => t!("action.show_keyboard_shortcuts"),
2065 Action::ShowWarnings => t!("action.show_warnings"),
2066 Action::ShowStatusLog => t!("action.show_status_log"),
2067 Action::ShowLspStatus => t!("action.show_lsp_status"),
2068 Action::ClearWarnings => t!("action.clear_warnings"),
2069 Action::CommandPalette => t!("action.command_palette"),
2070 Action::QuickOpen => t!("action.quick_open"),
2071 Action::InspectThemeAtCursor => t!("action.inspect_theme_at_cursor"),
2072 Action::ToggleLineWrap => t!("action.toggle_line_wrap"),
2073 Action::ToggleCurrentLineHighlight => t!("action.toggle_current_line_highlight"),
2074 Action::ToggleReadOnly => t!("action.toggle_read_only"),
2075 Action::TogglePageView => t!("action.toggle_page_view"),
2076 Action::SetPageWidth => t!("action.set_page_width"),
2077 Action::NextBuffer => t!("action.next_buffer"),
2078 Action::PrevBuffer => t!("action.prev_buffer"),
2079 Action::NavigateBack => t!("action.navigate_back"),
2080 Action::NavigateForward => t!("action.navigate_forward"),
2081 Action::SplitHorizontal => t!("action.split_horizontal"),
2082 Action::SplitVertical => t!("action.split_vertical"),
2083 Action::CloseSplit => t!("action.close_split"),
2084 Action::NextSplit => t!("action.next_split"),
2085 Action::PrevSplit => t!("action.prev_split"),
2086 Action::IncreaseSplitSize => t!("action.increase_split_size"),
2087 Action::DecreaseSplitSize => t!("action.decrease_split_size"),
2088 Action::ToggleMaximizeSplit => t!("action.toggle_maximize_split"),
2089 Action::PromptConfirm => t!("action.prompt_confirm"),
2090 Action::PromptConfirmWithText(ref text) => {
2091 format!("{} ({})", t!("action.prompt_confirm"), text).into()
2092 }
2093 Action::PromptCancel => t!("action.prompt_cancel"),
2094 Action::PromptBackspace => t!("action.prompt_backspace"),
2095 Action::PromptDelete => t!("action.prompt_delete"),
2096 Action::PromptMoveLeft => t!("action.prompt_move_left"),
2097 Action::PromptMoveRight => t!("action.prompt_move_right"),
2098 Action::PromptMoveStart => t!("action.prompt_move_start"),
2099 Action::PromptMoveEnd => t!("action.prompt_move_end"),
2100 Action::PromptSelectPrev => t!("action.prompt_select_prev"),
2101 Action::PromptSelectNext => t!("action.prompt_select_next"),
2102 Action::PromptPageUp => t!("action.prompt_page_up"),
2103 Action::PromptPageDown => t!("action.prompt_page_down"),
2104 Action::PromptAcceptSuggestion => t!("action.prompt_accept_suggestion"),
2105 Action::PromptMoveWordLeft => t!("action.prompt_move_word_left"),
2106 Action::PromptMoveWordRight => t!("action.prompt_move_word_right"),
2107 Action::PromptDeleteWordForward => t!("action.prompt_delete_word_forward"),
2108 Action::PromptDeleteWordBackward => t!("action.prompt_delete_word_backward"),
2109 Action::PromptDeleteToLineEnd => t!("action.prompt_delete_to_line_end"),
2110 Action::PromptCopy => t!("action.prompt_copy"),
2111 Action::PromptCut => t!("action.prompt_cut"),
2112 Action::PromptPaste => t!("action.prompt_paste"),
2113 Action::PromptMoveLeftSelecting => t!("action.prompt_move_left_selecting"),
2114 Action::PromptMoveRightSelecting => t!("action.prompt_move_right_selecting"),
2115 Action::PromptMoveHomeSelecting => t!("action.prompt_move_home_selecting"),
2116 Action::PromptMoveEndSelecting => t!("action.prompt_move_end_selecting"),
2117 Action::PromptSelectWordLeft => t!("action.prompt_select_word_left"),
2118 Action::PromptSelectWordRight => t!("action.prompt_select_word_right"),
2119 Action::PromptSelectAll => t!("action.prompt_select_all"),
2120 Action::FileBrowserToggleHidden => t!("action.file_browser_toggle_hidden"),
2121 Action::FileBrowserToggleDetectEncoding => {
2122 t!("action.file_browser_toggle_detect_encoding")
2123 }
2124 Action::PopupSelectNext => t!("action.popup_select_next"),
2125 Action::PopupSelectPrev => t!("action.popup_select_prev"),
2126 Action::PopupPageUp => t!("action.popup_page_up"),
2127 Action::PopupPageDown => t!("action.popup_page_down"),
2128 Action::PopupConfirm => t!("action.popup_confirm"),
2129 Action::PopupCancel => t!("action.popup_cancel"),
2130 Action::ToggleFileExplorer => t!("action.toggle_file_explorer"),
2131 Action::ToggleMenuBar => t!("action.toggle_menu_bar"),
2132 Action::ToggleTabBar => t!("action.toggle_tab_bar"),
2133 Action::ToggleStatusBar => t!("action.toggle_status_bar"),
2134 Action::TogglePromptLine => t!("action.toggle_prompt_line"),
2135 Action::ToggleVerticalScrollbar => t!("action.toggle_vertical_scrollbar"),
2136 Action::ToggleHorizontalScrollbar => t!("action.toggle_horizontal_scrollbar"),
2137 Action::FocusFileExplorer => t!("action.focus_file_explorer"),
2138 Action::FocusEditor => t!("action.focus_editor"),
2139 Action::FileExplorerUp => t!("action.file_explorer_up"),
2140 Action::FileExplorerDown => t!("action.file_explorer_down"),
2141 Action::FileExplorerPageUp => t!("action.file_explorer_page_up"),
2142 Action::FileExplorerPageDown => t!("action.file_explorer_page_down"),
2143 Action::FileExplorerExpand => t!("action.file_explorer_expand"),
2144 Action::FileExplorerCollapse => t!("action.file_explorer_collapse"),
2145 Action::FileExplorerOpen => t!("action.file_explorer_open"),
2146 Action::FileExplorerRefresh => t!("action.file_explorer_refresh"),
2147 Action::FileExplorerNewFile => t!("action.file_explorer_new_file"),
2148 Action::FileExplorerNewDirectory => t!("action.file_explorer_new_directory"),
2149 Action::FileExplorerDelete => t!("action.file_explorer_delete"),
2150 Action::FileExplorerRename => t!("action.file_explorer_rename"),
2151 Action::FileExplorerToggleHidden => t!("action.file_explorer_toggle_hidden"),
2152 Action::FileExplorerToggleGitignored => t!("action.file_explorer_toggle_gitignored"),
2153 Action::FileExplorerSearchClear => t!("action.file_explorer_search_clear"),
2154 Action::FileExplorerSearchBackspace => t!("action.file_explorer_search_backspace"),
2155 Action::LspCompletion => t!("action.lsp_completion"),
2156 Action::LspGotoDefinition => t!("action.lsp_goto_definition"),
2157 Action::LspReferences => t!("action.lsp_references"),
2158 Action::LspRename => t!("action.lsp_rename"),
2159 Action::LspHover => t!("action.lsp_hover"),
2160 Action::LspSignatureHelp => t!("action.lsp_signature_help"),
2161 Action::LspCodeActions => t!("action.lsp_code_actions"),
2162 Action::LspRestart => t!("action.lsp_restart"),
2163 Action::LspStop => t!("action.lsp_stop"),
2164 Action::LspToggleForBuffer => t!("action.lsp_toggle_for_buffer"),
2165 Action::ToggleInlayHints => t!("action.toggle_inlay_hints"),
2166 Action::ToggleMouseHover => t!("action.toggle_mouse_hover"),
2167 Action::ToggleLineNumbers => t!("action.toggle_line_numbers"),
2168 Action::ToggleScrollSync => t!("action.toggle_scroll_sync"),
2169 Action::ToggleMouseCapture => t!("action.toggle_mouse_capture"),
2170 Action::ToggleDebugHighlights => t!("action.toggle_debug_highlights"),
2171 Action::SetBackground => t!("action.set_background"),
2172 Action::SetBackgroundBlend => t!("action.set_background_blend"),
2173 Action::AddRuler => t!("action.add_ruler"),
2174 Action::RemoveRuler => t!("action.remove_ruler"),
2175 Action::SetTabSize => t!("action.set_tab_size"),
2176 Action::SetLineEnding => t!("action.set_line_ending"),
2177 Action::SetEncoding => t!("action.set_encoding"),
2178 Action::ReloadWithEncoding => t!("action.reload_with_encoding"),
2179 Action::SetLanguage => t!("action.set_language"),
2180 Action::ToggleIndentationStyle => t!("action.toggle_indentation_style"),
2181 Action::ToggleTabIndicators => t!("action.toggle_tab_indicators"),
2182 Action::ToggleWhitespaceIndicators => t!("action.toggle_whitespace_indicators"),
2183 Action::ResetBufferSettings => t!("action.reset_buffer_settings"),
2184 Action::DumpConfig => t!("action.dump_config"),
2185 Action::Search => t!("action.search"),
2186 Action::FindInSelection => t!("action.find_in_selection"),
2187 Action::FindNext => t!("action.find_next"),
2188 Action::FindPrevious => t!("action.find_previous"),
2189 Action::FindSelectionNext => t!("action.find_selection_next"),
2190 Action::FindSelectionPrevious => t!("action.find_selection_previous"),
2191 Action::Replace => t!("action.replace"),
2192 Action::QueryReplace => t!("action.query_replace"),
2193 Action::MenuActivate => t!("action.menu_activate"),
2194 Action::MenuClose => t!("action.menu_close"),
2195 Action::MenuLeft => t!("action.menu_left"),
2196 Action::MenuRight => t!("action.menu_right"),
2197 Action::MenuUp => t!("action.menu_up"),
2198 Action::MenuDown => t!("action.menu_down"),
2199 Action::MenuExecute => t!("action.menu_execute"),
2200 Action::MenuOpen(name) => t!("action.menu_open", name = name),
2201 Action::SwitchKeybindingMap(map) => t!("action.switch_keybinding_map", map = map),
2202 Action::PluginAction(name) => t!("action.plugin_action", name = name),
2203 Action::ScrollTabsLeft => t!("action.scroll_tabs_left"),
2204 Action::ScrollTabsRight => t!("action.scroll_tabs_right"),
2205 Action::SelectTheme => t!("action.select_theme"),
2206 Action::SelectKeybindingMap => t!("action.select_keybinding_map"),
2207 Action::SelectCursorStyle => t!("action.select_cursor_style"),
2208 Action::SelectLocale => t!("action.select_locale"),
2209 Action::SwitchToPreviousTab => t!("action.switch_to_previous_tab"),
2210 Action::SwitchToTabByName => t!("action.switch_to_tab_by_name"),
2211 Action::OpenTerminal => t!("action.open_terminal"),
2212 Action::CloseTerminal => t!("action.close_terminal"),
2213 Action::FocusTerminal => t!("action.focus_terminal"),
2214 Action::TerminalEscape => t!("action.terminal_escape"),
2215 Action::ToggleKeyboardCapture => t!("action.toggle_keyboard_capture"),
2216 Action::TerminalPaste => t!("action.terminal_paste"),
2217 Action::OpenSettings => t!("action.open_settings"),
2218 Action::CloseSettings => t!("action.close_settings"),
2219 Action::SettingsSave => t!("action.settings_save"),
2220 Action::SettingsReset => t!("action.settings_reset"),
2221 Action::SettingsToggleFocus => t!("action.settings_toggle_focus"),
2222 Action::SettingsActivate => t!("action.settings_activate"),
2223 Action::SettingsSearch => t!("action.settings_search"),
2224 Action::SettingsHelp => t!("action.settings_help"),
2225 Action::SettingsIncrement => t!("action.settings_increment"),
2226 Action::SettingsDecrement => t!("action.settings_decrement"),
2227 Action::SettingsInherit => t!("action.settings_inherit"),
2228 Action::ShellCommand => t!("action.shell_command"),
2229 Action::ShellCommandReplace => t!("action.shell_command_replace"),
2230 Action::ToUpperCase => t!("action.to_uppercase"),
2231 Action::ToLowerCase => t!("action.to_lowercase"),
2232 Action::ToggleCase => t!("action.to_uppercase"),
2233 Action::SortLines => t!("action.sort_lines"),
2234 Action::CalibrateInput => t!("action.calibrate_input"),
2235 Action::EventDebug => t!("action.event_debug"),
2236 Action::LoadPluginFromBuffer => "Load Plugin from Buffer".into(),
2237 Action::OpenKeybindingEditor => "Keybinding Editor".into(),
2238 Action::None => t!("action.none"),
2239 }
2240 .to_string()
2241 }
2242
2243 pub fn parse_key_public(key: &str) -> Option<KeyCode> {
2245 Self::parse_key(key)
2246 }
2247
2248 pub fn parse_modifiers_public(modifiers: &[String]) -> KeyModifiers {
2250 Self::parse_modifiers(modifiers)
2251 }
2252
2253 pub fn format_action_from_str(action_name: &str) -> String {
2257 if let Some(action) = Action::from_str(action_name, &std::collections::HashMap::new()) {
2259 Self::format_action(&action)
2260 } else {
2261 action_name
2263 .split('_')
2264 .map(|word| {
2265 let mut chars = word.chars();
2266 match chars.next() {
2267 Some(c) => {
2268 let upper: String = c.to_uppercase().collect();
2269 format!("{}{}", upper, chars.as_str())
2270 }
2271 None => String::new(),
2272 }
2273 })
2274 .collect::<Vec<_>>()
2275 .join(" ")
2276 }
2277 }
2278
2279 pub fn all_action_names() -> Vec<String> {
2283 Action::all_action_names()
2284 }
2285
2286 pub fn get_keybinding_for_action(
2292 &self,
2293 action: &Action,
2294 context: KeyContext,
2295 ) -> Option<String> {
2296 fn find_best_keybinding(
2298 bindings: &HashMap<(KeyCode, KeyModifiers), Action>,
2299 action: &Action,
2300 ) -> Option<(KeyCode, KeyModifiers)> {
2301 let matches: Vec<_> = bindings
2302 .iter()
2303 .filter(|(_, a)| *a == action)
2304 .map(|((k, m), _)| (*k, *m))
2305 .collect();
2306
2307 if matches.is_empty() {
2308 return None;
2309 }
2310
2311 let mut sorted = matches;
2314 sorted.sort_by(|(k1, m1), (k2, m2)| {
2315 let score1 = keybinding_priority_score(k1);
2316 let score2 = keybinding_priority_score(k2);
2317 match score1.cmp(&score2) {
2319 std::cmp::Ordering::Equal => {
2320 let s1 = format_keybinding(k1, m1);
2322 let s2 = format_keybinding(k2, m2);
2323 s1.cmp(&s2)
2324 }
2325 other => other,
2326 }
2327 });
2328
2329 sorted.into_iter().next()
2330 }
2331
2332 if let Some(context_bindings) = self.bindings.get(&context) {
2334 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
2335 return Some(format_keybinding(&keycode, &modifiers));
2336 }
2337 }
2338
2339 if let Some(context_bindings) = self.default_bindings.get(&context) {
2341 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
2342 return Some(format_keybinding(&keycode, &modifiers));
2343 }
2344 }
2345
2346 if context != KeyContext::Normal && Self::is_application_wide_action(action) {
2348 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
2350 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
2351 return Some(format_keybinding(&keycode, &modifiers));
2352 }
2353 }
2354
2355 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
2357 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
2358 return Some(format_keybinding(&keycode, &modifiers));
2359 }
2360 }
2361 }
2362
2363 None
2364 }
2365
2366 pub fn reload(&mut self, config: &Config) {
2368 self.bindings.clear();
2369 for binding in &config.keybindings {
2370 if let Some(key_code) = Self::parse_key(&binding.key) {
2371 let modifiers = Self::parse_modifiers(&binding.modifiers);
2372 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
2373 let context = if let Some(ref when) = binding.when {
2375 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
2376 } else {
2377 KeyContext::Normal
2378 };
2379
2380 self.bindings
2381 .entry(context)
2382 .or_default()
2383 .insert((key_code, modifiers), action);
2384 }
2385 }
2386 }
2387 }
2388}
2389
2390#[cfg(test)]
2391mod tests {
2392 use super::*;
2393
2394 #[test]
2395 fn test_parse_key() {
2396 assert_eq!(KeybindingResolver::parse_key("enter"), Some(KeyCode::Enter));
2397 assert_eq!(
2398 KeybindingResolver::parse_key("backspace"),
2399 Some(KeyCode::Backspace)
2400 );
2401 assert_eq!(KeybindingResolver::parse_key("tab"), Some(KeyCode::Tab));
2402 assert_eq!(
2403 KeybindingResolver::parse_key("backtab"),
2404 Some(KeyCode::BackTab)
2405 );
2406 assert_eq!(
2407 KeybindingResolver::parse_key("BackTab"),
2408 Some(KeyCode::BackTab)
2409 );
2410 assert_eq!(KeybindingResolver::parse_key("a"), Some(KeyCode::Char('a')));
2411 }
2412
2413 #[test]
2414 fn test_parse_modifiers() {
2415 let mods = vec!["ctrl".to_string()];
2416 assert_eq!(
2417 KeybindingResolver::parse_modifiers(&mods),
2418 KeyModifiers::CONTROL
2419 );
2420
2421 let mods = vec!["ctrl".to_string(), "shift".to_string()];
2422 assert_eq!(
2423 KeybindingResolver::parse_modifiers(&mods),
2424 KeyModifiers::CONTROL | KeyModifiers::SHIFT
2425 );
2426 }
2427
2428 #[test]
2429 fn test_resolve_basic() {
2430 let config = Config::default();
2431 let resolver = KeybindingResolver::new(&config);
2432
2433 let event = KeyEvent::new(KeyCode::Left, KeyModifiers::empty());
2434 assert_eq!(
2435 resolver.resolve(&event, KeyContext::Normal),
2436 Action::MoveLeft
2437 );
2438
2439 let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2440 assert_eq!(
2441 resolver.resolve(&event, KeyContext::Normal),
2442 Action::InsertChar('a')
2443 );
2444 }
2445
2446 #[test]
2447 fn test_action_from_str() {
2448 let args = HashMap::new();
2449 assert_eq!(Action::from_str("move_left", &args), Some(Action::MoveLeft));
2450 assert_eq!(Action::from_str("save", &args), Some(Action::Save));
2451 assert_eq!(
2453 Action::from_str("unknown", &args),
2454 Some(Action::PluginAction("unknown".to_string()))
2455 );
2456
2457 assert_eq!(
2459 Action::from_str("keyboard_shortcuts", &args),
2460 Some(Action::ShowKeyboardShortcuts)
2461 );
2462 assert_eq!(
2463 Action::from_str("prompt_confirm", &args),
2464 Some(Action::PromptConfirm)
2465 );
2466 assert_eq!(
2467 Action::from_str("popup_cancel", &args),
2468 Some(Action::PopupCancel)
2469 );
2470
2471 assert_eq!(
2473 Action::from_str("calibrate_input", &args),
2474 Some(Action::CalibrateInput)
2475 );
2476 }
2477
2478 #[test]
2479 fn test_key_context_from_when_clause() {
2480 assert_eq!(
2481 KeyContext::from_when_clause("normal"),
2482 Some(KeyContext::Normal)
2483 );
2484 assert_eq!(
2485 KeyContext::from_when_clause("prompt"),
2486 Some(KeyContext::Prompt)
2487 );
2488 assert_eq!(
2489 KeyContext::from_when_clause("popup"),
2490 Some(KeyContext::Popup)
2491 );
2492 assert_eq!(KeyContext::from_when_clause("help"), None);
2493 assert_eq!(KeyContext::from_when_clause(" help "), None); assert_eq!(KeyContext::from_when_clause("unknown"), None);
2495 assert_eq!(KeyContext::from_when_clause(""), None);
2496 }
2497
2498 #[test]
2499 fn test_key_context_to_when_clause() {
2500 assert_eq!(KeyContext::Normal.to_when_clause(), "normal");
2501 assert_eq!(KeyContext::Prompt.to_when_clause(), "prompt");
2502 assert_eq!(KeyContext::Popup.to_when_clause(), "popup");
2503 }
2504
2505 #[test]
2506 fn test_context_specific_bindings() {
2507 let config = Config::default();
2508 let resolver = KeybindingResolver::new(&config);
2509
2510 let enter_event = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
2512 assert_eq!(
2513 resolver.resolve(&enter_event, KeyContext::Prompt),
2514 Action::PromptConfirm
2515 );
2516 assert_eq!(
2517 resolver.resolve(&enter_event, KeyContext::Normal),
2518 Action::InsertNewline
2519 );
2520
2521 let up_event = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
2523 assert_eq!(
2524 resolver.resolve(&up_event, KeyContext::Popup),
2525 Action::PopupSelectPrev
2526 );
2527 assert_eq!(
2528 resolver.resolve(&up_event, KeyContext::Normal),
2529 Action::MoveUp
2530 );
2531 }
2532
2533 #[test]
2534 fn test_context_fallback_to_normal() {
2535 let config = Config::default();
2536 let resolver = KeybindingResolver::new(&config);
2537
2538 let save_event = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL);
2540 assert_eq!(
2541 resolver.resolve(&save_event, KeyContext::Normal),
2542 Action::Save
2543 );
2544 assert_eq!(
2545 resolver.resolve(&save_event, KeyContext::Popup),
2546 Action::Save
2547 );
2548 }
2550
2551 #[test]
2552 fn test_context_priority_resolution() {
2553 use crate::config::Keybinding;
2554
2555 let mut config = Config::default();
2557 config.keybindings.push(Keybinding {
2558 key: "esc".to_string(),
2559 modifiers: vec![],
2560 keys: vec![],
2561 action: "quit".to_string(), args: HashMap::new(),
2563 when: Some("popup".to_string()),
2564 });
2565
2566 let resolver = KeybindingResolver::new(&config);
2567 let esc_event = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
2568
2569 assert_eq!(
2571 resolver.resolve(&esc_event, KeyContext::Popup),
2572 Action::Quit
2573 );
2574
2575 assert_eq!(
2577 resolver.resolve(&esc_event, KeyContext::Normal),
2578 Action::RemoveSecondaryCursors
2579 );
2580 }
2581
2582 #[test]
2583 fn test_character_input_in_contexts() {
2584 let config = Config::default();
2585 let resolver = KeybindingResolver::new(&config);
2586
2587 let char_event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2588
2589 assert_eq!(
2591 resolver.resolve(&char_event, KeyContext::Normal),
2592 Action::InsertChar('a')
2593 );
2594 assert_eq!(
2595 resolver.resolve(&char_event, KeyContext::Prompt),
2596 Action::InsertChar('a')
2597 );
2598
2599 assert_eq!(
2601 resolver.resolve(&char_event, KeyContext::Popup),
2602 Action::None
2603 );
2604 }
2605
2606 #[test]
2607 fn test_custom_keybinding_loading() {
2608 use crate::config::Keybinding;
2609
2610 let mut config = Config::default();
2611
2612 config.keybindings.push(Keybinding {
2614 key: "f".to_string(),
2615 modifiers: vec!["ctrl".to_string()],
2616 keys: vec![],
2617 action: "command_palette".to_string(),
2618 args: HashMap::new(),
2619 when: None, });
2621
2622 let resolver = KeybindingResolver::new(&config);
2623
2624 let ctrl_f = KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL);
2626 assert_eq!(
2627 resolver.resolve(&ctrl_f, KeyContext::Normal),
2628 Action::CommandPalette
2629 );
2630
2631 let ctrl_k = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
2633 assert_eq!(
2634 resolver.resolve(&ctrl_k, KeyContext::Prompt),
2635 Action::PromptDeleteToLineEnd
2636 );
2637 assert_eq!(
2638 resolver.resolve(&ctrl_k, KeyContext::Normal),
2639 Action::DeleteToLineEnd
2640 );
2641 }
2642
2643 #[test]
2644 fn test_all_context_default_bindings_exist() {
2645 let config = Config::default();
2646 let resolver = KeybindingResolver::new(&config);
2647
2648 assert!(resolver.default_bindings.contains_key(&KeyContext::Normal));
2650 assert!(resolver.default_bindings.contains_key(&KeyContext::Prompt));
2651 assert!(resolver.default_bindings.contains_key(&KeyContext::Popup));
2652 assert!(resolver
2653 .default_bindings
2654 .contains_key(&KeyContext::FileExplorer));
2655 assert!(resolver.default_bindings.contains_key(&KeyContext::Menu));
2656
2657 assert!(!resolver.default_bindings[&KeyContext::Normal].is_empty());
2659 assert!(!resolver.default_bindings[&KeyContext::Prompt].is_empty());
2660 assert!(!resolver.default_bindings[&KeyContext::Popup].is_empty());
2661 assert!(!resolver.default_bindings[&KeyContext::FileExplorer].is_empty());
2662 assert!(!resolver.default_bindings[&KeyContext::Menu].is_empty());
2663 }
2664
2665 #[test]
2669 fn test_all_builtin_keymaps_have_valid_action_names() {
2670 let known_actions: std::collections::HashSet<String> =
2671 Action::all_action_names().into_iter().collect();
2672
2673 let config = Config::default();
2674
2675 for map_name in crate::config::KeybindingMapName::BUILTIN_OPTIONS {
2676 let bindings = config.resolve_keymap(map_name);
2677 for binding in &bindings {
2678 assert!(
2679 known_actions.contains(&binding.action),
2680 "Keymap '{}' contains unknown action '{}' (key: '{}', when: {:?}). \
2681 This will be treated as a plugin action at runtime. \
2682 Check for typos in the keymap JSON file.",
2683 map_name,
2684 binding.action,
2685 binding.key,
2686 binding.when,
2687 );
2688 }
2689 }
2690 }
2691
2692 #[test]
2693 fn test_resolve_determinism() {
2694 let config = Config::default();
2696 let resolver = KeybindingResolver::new(&config);
2697
2698 let test_cases = vec![
2699 (KeyCode::Left, KeyModifiers::empty(), KeyContext::Normal),
2700 (
2701 KeyCode::Esc,
2702 KeyModifiers::empty(),
2703 KeyContext::FileExplorer,
2704 ),
2705 (KeyCode::Enter, KeyModifiers::empty(), KeyContext::Prompt),
2706 (KeyCode::Down, KeyModifiers::empty(), KeyContext::Popup),
2707 ];
2708
2709 for (key_code, modifiers, context) in test_cases {
2710 let event = KeyEvent::new(key_code, modifiers);
2711 let action1 = resolver.resolve(&event, context.clone());
2712 let action2 = resolver.resolve(&event, context.clone());
2713 let action3 = resolver.resolve(&event, context);
2714
2715 assert_eq!(action1, action2, "Resolve should be deterministic");
2716 assert_eq!(action2, action3, "Resolve should be deterministic");
2717 }
2718 }
2719
2720 #[test]
2721 fn test_modifier_combinations() {
2722 let config = Config::default();
2723 let resolver = KeybindingResolver::new(&config);
2724
2725 let char_s = KeyCode::Char('s');
2727
2728 let no_mod = KeyEvent::new(char_s, KeyModifiers::empty());
2729 let ctrl = KeyEvent::new(char_s, KeyModifiers::CONTROL);
2730 let shift = KeyEvent::new(char_s, KeyModifiers::SHIFT);
2731 let ctrl_shift = KeyEvent::new(char_s, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
2732
2733 let action_no_mod = resolver.resolve(&no_mod, KeyContext::Normal);
2734 let action_ctrl = resolver.resolve(&ctrl, KeyContext::Normal);
2735 let action_shift = resolver.resolve(&shift, KeyContext::Normal);
2736 let action_ctrl_shift = resolver.resolve(&ctrl_shift, KeyContext::Normal);
2737
2738 assert_eq!(action_no_mod, Action::InsertChar('s'));
2740 assert_eq!(action_ctrl, Action::Save);
2741 assert_eq!(action_shift, Action::InsertChar('s')); assert_eq!(action_ctrl_shift, Action::None);
2744 }
2745
2746 #[test]
2747 fn test_scroll_keybindings() {
2748 let config = Config::default();
2749 let resolver = KeybindingResolver::new(&config);
2750
2751 let ctrl_up = KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL);
2753 assert_eq!(
2754 resolver.resolve(&ctrl_up, KeyContext::Normal),
2755 Action::ScrollUp,
2756 "Ctrl+Up should resolve to ScrollUp"
2757 );
2758
2759 let ctrl_down = KeyEvent::new(KeyCode::Down, KeyModifiers::CONTROL);
2761 assert_eq!(
2762 resolver.resolve(&ctrl_down, KeyContext::Normal),
2763 Action::ScrollDown,
2764 "Ctrl+Down should resolve to ScrollDown"
2765 );
2766 }
2767
2768 #[test]
2769 fn test_lsp_completion_keybinding() {
2770 let config = Config::default();
2771 let resolver = KeybindingResolver::new(&config);
2772
2773 let ctrl_space = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::CONTROL);
2775 assert_eq!(
2776 resolver.resolve(&ctrl_space, KeyContext::Normal),
2777 Action::LspCompletion,
2778 "Ctrl+Space should resolve to LspCompletion"
2779 );
2780 }
2781
2782 #[test]
2783 fn test_terminal_key_equivalents() {
2784 let ctrl = KeyModifiers::CONTROL;
2786
2787 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2789 assert_eq!(slash_equivs, vec![(KeyCode::Char('7'), ctrl)]);
2790
2791 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2792 assert_eq!(seven_equivs, vec![(KeyCode::Char('/'), ctrl)]);
2793
2794 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2796 assert_eq!(backspace_equivs, vec![(KeyCode::Char('h'), ctrl)]);
2797
2798 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2799 assert_eq!(h_equivs, vec![(KeyCode::Backspace, ctrl)]);
2800
2801 let a_equivs = terminal_key_equivalents(KeyCode::Char('a'), ctrl);
2803 assert!(a_equivs.is_empty());
2804
2805 let slash_no_ctrl = terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty());
2807 assert!(slash_no_ctrl.is_empty());
2808 }
2809
2810 #[test]
2811 fn test_terminal_key_equivalents_auto_binding() {
2812 let config = Config::default();
2813 let resolver = KeybindingResolver::new(&config);
2814
2815 let ctrl_slash = KeyEvent::new(KeyCode::Char('/'), KeyModifiers::CONTROL);
2817 let action_slash = resolver.resolve(&ctrl_slash, KeyContext::Normal);
2818 assert_eq!(
2819 action_slash,
2820 Action::ToggleComment,
2821 "Ctrl+/ should resolve to ToggleComment"
2822 );
2823
2824 let ctrl_7 = KeyEvent::new(KeyCode::Char('7'), KeyModifiers::CONTROL);
2826 let action_7 = resolver.resolve(&ctrl_7, KeyContext::Normal);
2827 assert_eq!(
2828 action_7,
2829 Action::ToggleComment,
2830 "Ctrl+7 should resolve to ToggleComment (terminal equivalent of Ctrl+/)"
2831 );
2832 }
2833
2834 #[test]
2835 fn test_terminal_key_equivalents_normalization() {
2836 let ctrl = KeyModifiers::CONTROL;
2841
2842 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2845 assert_eq!(
2846 slash_equivs,
2847 vec![(KeyCode::Char('7'), ctrl)],
2848 "Ctrl+/ should map to Ctrl+7"
2849 );
2850 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2851 assert_eq!(
2852 seven_equivs,
2853 vec![(KeyCode::Char('/'), ctrl)],
2854 "Ctrl+7 should map back to Ctrl+/"
2855 );
2856
2857 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2860 assert_eq!(
2861 backspace_equivs,
2862 vec![(KeyCode::Char('h'), ctrl)],
2863 "Ctrl+Backspace should map to Ctrl+H"
2864 );
2865 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2866 assert_eq!(
2867 h_equivs,
2868 vec![(KeyCode::Backspace, ctrl)],
2869 "Ctrl+H should map back to Ctrl+Backspace"
2870 );
2871
2872 let space_equivs = terminal_key_equivalents(KeyCode::Char(' '), ctrl);
2875 assert_eq!(
2876 space_equivs,
2877 vec![(KeyCode::Char('@'), ctrl)],
2878 "Ctrl+Space should map to Ctrl+@"
2879 );
2880 let at_equivs = terminal_key_equivalents(KeyCode::Char('@'), ctrl);
2881 assert_eq!(
2882 at_equivs,
2883 vec![(KeyCode::Char(' '), ctrl)],
2884 "Ctrl+@ should map back to Ctrl+Space"
2885 );
2886
2887 let minus_equivs = terminal_key_equivalents(KeyCode::Char('-'), ctrl);
2890 assert_eq!(
2891 minus_equivs,
2892 vec![(KeyCode::Char('_'), ctrl)],
2893 "Ctrl+- should map to Ctrl+_"
2894 );
2895 let underscore_equivs = terminal_key_equivalents(KeyCode::Char('_'), ctrl);
2896 assert_eq!(
2897 underscore_equivs,
2898 vec![(KeyCode::Char('-'), ctrl)],
2899 "Ctrl+_ should map back to Ctrl+-"
2900 );
2901
2902 assert!(
2904 terminal_key_equivalents(KeyCode::Char('a'), ctrl).is_empty(),
2905 "Ctrl+A should have no terminal equivalents"
2906 );
2907 assert!(
2908 terminal_key_equivalents(KeyCode::Char('z'), ctrl).is_empty(),
2909 "Ctrl+Z should have no terminal equivalents"
2910 );
2911 assert!(
2912 terminal_key_equivalents(KeyCode::Enter, ctrl).is_empty(),
2913 "Ctrl+Enter should have no terminal equivalents"
2914 );
2915
2916 assert!(
2918 terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty()).is_empty(),
2919 "/ without Ctrl should have no equivalents"
2920 );
2921 assert!(
2922 terminal_key_equivalents(KeyCode::Char('7'), KeyModifiers::SHIFT).is_empty(),
2923 "Shift+7 should have no equivalents"
2924 );
2925 assert!(
2926 terminal_key_equivalents(KeyCode::Char('h'), KeyModifiers::ALT).is_empty(),
2927 "Alt+H should have no equivalents"
2928 );
2929
2930 let ctrl_shift = KeyModifiers::CONTROL | KeyModifiers::SHIFT;
2933 let ctrl_shift_h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl_shift);
2934 assert!(
2935 ctrl_shift_h_equivs.is_empty(),
2936 "Ctrl+Shift+H should NOT map to Ctrl+Shift+Backspace"
2937 );
2938 }
2939
2940 #[test]
2941 fn test_no_duplicate_keybindings_in_keymaps() {
2942 use std::collections::HashMap;
2945
2946 let keymaps: &[(&str, &str)] = &[
2947 ("default", include_str!("../../keymaps/default.json")),
2948 ("macos", include_str!("../../keymaps/macos.json")),
2949 ];
2950
2951 for (keymap_name, json_content) in keymaps {
2952 let keymap: crate::config::KeymapConfig = serde_json::from_str(json_content)
2953 .unwrap_or_else(|e| panic!("Failed to parse keymap '{}': {}", keymap_name, e));
2954
2955 let mut seen: HashMap<(String, Vec<String>, String), String> = HashMap::new();
2957 let mut duplicates: Vec<String> = Vec::new();
2958
2959 for binding in &keymap.bindings {
2960 let when = binding.when.clone().unwrap_or_default();
2961 let key_id = (binding.key.clone(), binding.modifiers.clone(), when.clone());
2962
2963 if let Some(existing_action) = seen.get(&key_id) {
2964 duplicates.push(format!(
2965 "Duplicate in '{}': key='{}', modifiers={:?}, when='{}' -> '{}' vs '{}'",
2966 keymap_name,
2967 binding.key,
2968 binding.modifiers,
2969 when,
2970 existing_action,
2971 binding.action
2972 ));
2973 } else {
2974 seen.insert(key_id, binding.action.clone());
2975 }
2976 }
2977
2978 assert!(
2979 duplicates.is_empty(),
2980 "Found duplicate keybindings:\n{}",
2981 duplicates.join("\n")
2982 );
2983 }
2984 }
2985}