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, OpenTerminal, CloseTerminal, FocusTerminal, TerminalEscape, ToggleKeyboardCapture, TerminalPaste, ShellCommand, ShellCommandReplace, ToUpperCase, ToLowerCase, ToggleCase, SortLines, CalibrateInput, EventDebug, OpenKeybindingEditor, LoadPluginFromBuffer, None,
667}
668
669macro_rules! define_action_str_mapping {
682 (
683 $args_name:ident;
684 simple { $($s_name:literal => $s_variant:ident),* $(,)? }
685 alias { $($a_name:literal => $a_variant:ident),* $(,)? }
686 with_char { $($c_name:literal => $c_variant:ident),* $(,)? }
687 custom { $($x_name:literal => $x_variant:ident : $x_body:expr),* $(,)? }
688 ) => {
689 pub fn from_str(s: &str, $args_name: &HashMap<String, serde_json::Value>) -> Option<Self> {
691 Some(match s {
692 $($s_name => Self::$s_variant,)*
693 $($a_name => Self::$a_variant,)*
694 $($c_name => return Self::with_char($args_name, Self::$c_variant),)*
695 $($x_name => $x_body,)*
696 _ => Self::PluginAction(s.to_string()),
699 })
700 }
701
702 pub fn to_action_str(&self) -> String {
705 match self {
706 $(Self::$s_variant => $s_name.to_string(),)*
707 $(Self::$c_variant(_) => $c_name.to_string(),)*
708 $(Self::$x_variant(_) => $x_name.to_string(),)*
709 Self::PluginAction(name) => name.clone(),
710 }
711 }
712
713 pub fn all_action_names() -> Vec<String> {
716 let mut names = vec![
717 $($s_name.to_string(),)*
718 $($a_name.to_string(),)*
719 $($c_name.to_string(),)*
720 $($x_name.to_string(),)*
721 ];
722 names.sort();
723 names
724 }
725 };
726}
727
728impl Action {
729 fn with_char(
730 args: &HashMap<String, serde_json::Value>,
731 make_action: impl FnOnce(char) -> Self,
732 ) -> Option<Self> {
733 if let Some(serde_json::Value::String(value)) = args.get("char") {
734 value.chars().next().map(make_action)
735 } else {
736 None
737 }
738 }
739
740 define_action_str_mapping! {
741 args;
742 simple {
743 "insert_newline" => InsertNewline,
744 "insert_tab" => InsertTab,
745
746 "move_left" => MoveLeft,
747 "move_right" => MoveRight,
748 "move_up" => MoveUp,
749 "move_down" => MoveDown,
750 "move_word_left" => MoveWordLeft,
751 "move_word_right" => MoveWordRight,
752 "move_word_end" => MoveWordEnd,
753 "vi_move_word_end" => ViMoveWordEnd,
754 "move_left_in_line" => MoveLeftInLine,
755 "move_right_in_line" => MoveRightInLine,
756 "move_line_start" => MoveLineStart,
757 "move_line_end" => MoveLineEnd,
758 "move_line_up" => MoveLineUp,
759 "move_line_down" => MoveLineDown,
760 "move_page_up" => MovePageUp,
761 "move_page_down" => MovePageDown,
762 "move_document_start" => MoveDocumentStart,
763 "move_document_end" => MoveDocumentEnd,
764
765 "select_left" => SelectLeft,
766 "select_right" => SelectRight,
767 "select_up" => SelectUp,
768 "select_down" => SelectDown,
769 "select_to_paragraph_up" => SelectToParagraphUp,
770 "select_to_paragraph_down" => SelectToParagraphDown,
771 "select_word_left" => SelectWordLeft,
772 "select_word_right" => SelectWordRight,
773 "select_word_end" => SelectWordEnd,
774 "vi_select_word_end" => ViSelectWordEnd,
775 "select_line_start" => SelectLineStart,
776 "select_line_end" => SelectLineEnd,
777 "select_document_start" => SelectDocumentStart,
778 "select_document_end" => SelectDocumentEnd,
779 "select_page_up" => SelectPageUp,
780 "select_page_down" => SelectPageDown,
781 "select_all" => SelectAll,
782 "select_word" => SelectWord,
783 "select_line" => SelectLine,
784 "expand_selection" => ExpandSelection,
785
786 "block_select_left" => BlockSelectLeft,
787 "block_select_right" => BlockSelectRight,
788 "block_select_up" => BlockSelectUp,
789 "block_select_down" => BlockSelectDown,
790
791 "delete_backward" => DeleteBackward,
792 "delete_forward" => DeleteForward,
793 "delete_word_backward" => DeleteWordBackward,
794 "delete_word_forward" => DeleteWordForward,
795 "delete_line" => DeleteLine,
796 "delete_to_line_end" => DeleteToLineEnd,
797 "delete_to_line_start" => DeleteToLineStart,
798 "delete_vi_word_end" => DeleteViWordEnd,
799 "transpose_chars" => TransposeChars,
800 "open_line" => OpenLine,
801 "duplicate_line" => DuplicateLine,
802 "recenter" => Recenter,
803 "set_mark" => SetMark,
804
805 "copy" => Copy,
806 "cut" => Cut,
807 "paste" => Paste,
808
809 "yank_word_forward" => YankWordForward,
810 "yank_word_backward" => YankWordBackward,
811 "yank_to_line_end" => YankToLineEnd,
812 "yank_to_line_start" => YankToLineStart,
813 "yank_vi_word_end" => YankViWordEnd,
814
815 "add_cursor_above" => AddCursorAbove,
816 "add_cursor_below" => AddCursorBelow,
817 "add_cursor_next_match" => AddCursorNextMatch,
818 "remove_secondary_cursors" => RemoveSecondaryCursors,
819
820 "save" => Save,
821 "save_as" => SaveAs,
822 "open" => Open,
823 "switch_project" => SwitchProject,
824 "new" => New,
825 "close" => Close,
826 "close_tab" => CloseTab,
827 "quit" => Quit,
828 "force_quit" => ForceQuit,
829 "detach" => Detach,
830 "revert" => Revert,
831 "toggle_auto_revert" => ToggleAutoRevert,
832 "format_buffer" => FormatBuffer,
833 "trim_trailing_whitespace" => TrimTrailingWhitespace,
834 "ensure_final_newline" => EnsureFinalNewline,
835 "goto_line" => GotoLine,
836 "scan_line_index" => ScanLineIndex,
837 "goto_matching_bracket" => GoToMatchingBracket,
838 "jump_to_next_error" => JumpToNextError,
839 "jump_to_previous_error" => JumpToPreviousError,
840
841 "smart_home" => SmartHome,
842 "dedent_selection" => DedentSelection,
843 "toggle_comment" => ToggleComment,
844 "dabbrev_expand" => DabbrevExpand,
845 "toggle_fold" => ToggleFold,
846
847 "list_bookmarks" => ListBookmarks,
848
849 "toggle_search_case_sensitive" => ToggleSearchCaseSensitive,
850 "toggle_search_whole_word" => ToggleSearchWholeWord,
851 "toggle_search_regex" => ToggleSearchRegex,
852 "toggle_search_confirm_each" => ToggleSearchConfirmEach,
853
854 "start_macro_recording" => StartMacroRecording,
855 "stop_macro_recording" => StopMacroRecording,
856
857 "list_macros" => ListMacros,
858 "prompt_record_macro" => PromptRecordMacro,
859 "prompt_play_macro" => PromptPlayMacro,
860 "play_last_macro" => PlayLastMacro,
861 "prompt_set_bookmark" => PromptSetBookmark,
862 "prompt_jump_to_bookmark" => PromptJumpToBookmark,
863
864 "undo" => Undo,
865 "redo" => Redo,
866
867 "scroll_up" => ScrollUp,
868 "scroll_down" => ScrollDown,
869 "show_help" => ShowHelp,
870 "keyboard_shortcuts" => ShowKeyboardShortcuts,
871 "show_warnings" => ShowWarnings,
872 "show_status_log" => ShowStatusLog,
873 "show_lsp_status" => ShowLspStatus,
874 "clear_warnings" => ClearWarnings,
875 "command_palette" => CommandPalette,
876 "quick_open" => QuickOpen,
877 "toggle_line_wrap" => ToggleLineWrap,
878 "toggle_current_line_highlight" => ToggleCurrentLineHighlight,
879 "toggle_read_only" => ToggleReadOnly,
880 "toggle_page_view" => TogglePageView,
881 "set_page_width" => SetPageWidth,
882
883 "next_buffer" => NextBuffer,
884 "prev_buffer" => PrevBuffer,
885 "switch_to_previous_tab" => SwitchToPreviousTab,
886 "switch_to_tab_by_name" => SwitchToTabByName,
887 "scroll_tabs_left" => ScrollTabsLeft,
888 "scroll_tabs_right" => ScrollTabsRight,
889
890 "navigate_back" => NavigateBack,
891 "navigate_forward" => NavigateForward,
892
893 "split_horizontal" => SplitHorizontal,
894 "split_vertical" => SplitVertical,
895 "close_split" => CloseSplit,
896 "next_split" => NextSplit,
897 "prev_split" => PrevSplit,
898 "increase_split_size" => IncreaseSplitSize,
899 "decrease_split_size" => DecreaseSplitSize,
900 "toggle_maximize_split" => ToggleMaximizeSplit,
901
902 "prompt_confirm" => PromptConfirm,
903 "prompt_cancel" => PromptCancel,
904 "prompt_backspace" => PromptBackspace,
905 "prompt_move_left" => PromptMoveLeft,
906 "prompt_move_right" => PromptMoveRight,
907 "prompt_move_start" => PromptMoveStart,
908 "prompt_move_end" => PromptMoveEnd,
909 "prompt_select_prev" => PromptSelectPrev,
910 "prompt_select_next" => PromptSelectNext,
911 "prompt_page_up" => PromptPageUp,
912 "prompt_page_down" => PromptPageDown,
913 "prompt_accept_suggestion" => PromptAcceptSuggestion,
914 "prompt_delete_word_forward" => PromptDeleteWordForward,
915 "prompt_delete_word_backward" => PromptDeleteWordBackward,
916 "prompt_delete_to_line_end" => PromptDeleteToLineEnd,
917 "prompt_copy" => PromptCopy,
918 "prompt_cut" => PromptCut,
919 "prompt_paste" => PromptPaste,
920 "prompt_move_left_selecting" => PromptMoveLeftSelecting,
921 "prompt_move_right_selecting" => PromptMoveRightSelecting,
922 "prompt_move_home_selecting" => PromptMoveHomeSelecting,
923 "prompt_move_end_selecting" => PromptMoveEndSelecting,
924 "prompt_select_word_left" => PromptSelectWordLeft,
925 "prompt_select_word_right" => PromptSelectWordRight,
926 "prompt_select_all" => PromptSelectAll,
927 "file_browser_toggle_hidden" => FileBrowserToggleHidden,
928 "file_browser_toggle_detect_encoding" => FileBrowserToggleDetectEncoding,
929 "prompt_move_word_left" => PromptMoveWordLeft,
930 "prompt_move_word_right" => PromptMoveWordRight,
931 "prompt_delete" => PromptDelete,
932
933 "popup_select_next" => PopupSelectNext,
934 "popup_select_prev" => PopupSelectPrev,
935 "popup_page_up" => PopupPageUp,
936 "popup_page_down" => PopupPageDown,
937 "popup_confirm" => PopupConfirm,
938 "popup_cancel" => PopupCancel,
939
940 "toggle_file_explorer" => ToggleFileExplorer,
941 "toggle_menu_bar" => ToggleMenuBar,
942 "toggle_tab_bar" => ToggleTabBar,
943 "toggle_status_bar" => ToggleStatusBar,
944 "toggle_prompt_line" => TogglePromptLine,
945 "toggle_vertical_scrollbar" => ToggleVerticalScrollbar,
946 "toggle_horizontal_scrollbar" => ToggleHorizontalScrollbar,
947 "focus_file_explorer" => FocusFileExplorer,
948 "focus_editor" => FocusEditor,
949 "file_explorer_up" => FileExplorerUp,
950 "file_explorer_down" => FileExplorerDown,
951 "file_explorer_page_up" => FileExplorerPageUp,
952 "file_explorer_page_down" => FileExplorerPageDown,
953 "file_explorer_expand" => FileExplorerExpand,
954 "file_explorer_collapse" => FileExplorerCollapse,
955 "file_explorer_open" => FileExplorerOpen,
956 "file_explorer_refresh" => FileExplorerRefresh,
957 "file_explorer_new_file" => FileExplorerNewFile,
958 "file_explorer_new_directory" => FileExplorerNewDirectory,
959 "file_explorer_delete" => FileExplorerDelete,
960 "file_explorer_rename" => FileExplorerRename,
961 "file_explorer_toggle_hidden" => FileExplorerToggleHidden,
962 "file_explorer_toggle_gitignored" => FileExplorerToggleGitignored,
963 "file_explorer_search_clear" => FileExplorerSearchClear,
964 "file_explorer_search_backspace" => FileExplorerSearchBackspace,
965
966 "lsp_completion" => LspCompletion,
967 "lsp_goto_definition" => LspGotoDefinition,
968 "lsp_references" => LspReferences,
969 "lsp_rename" => LspRename,
970 "lsp_hover" => LspHover,
971 "lsp_signature_help" => LspSignatureHelp,
972 "lsp_code_actions" => LspCodeActions,
973 "lsp_restart" => LspRestart,
974 "lsp_stop" => LspStop,
975 "lsp_toggle_for_buffer" => LspToggleForBuffer,
976 "toggle_inlay_hints" => ToggleInlayHints,
977 "toggle_mouse_hover" => ToggleMouseHover,
978
979 "toggle_line_numbers" => ToggleLineNumbers,
980 "toggle_scroll_sync" => ToggleScrollSync,
981 "toggle_mouse_capture" => ToggleMouseCapture,
982 "toggle_debug_highlights" => ToggleDebugHighlights,
983 "set_background" => SetBackground,
984 "set_background_blend" => SetBackgroundBlend,
985 "inspect_theme_at_cursor" => InspectThemeAtCursor,
986 "select_theme" => SelectTheme,
987 "select_keybinding_map" => SelectKeybindingMap,
988 "select_cursor_style" => SelectCursorStyle,
989 "select_locale" => SelectLocale,
990
991 "set_tab_size" => SetTabSize,
992 "set_line_ending" => SetLineEnding,
993 "set_encoding" => SetEncoding,
994 "reload_with_encoding" => ReloadWithEncoding,
995 "set_language" => SetLanguage,
996 "toggle_indentation_style" => ToggleIndentationStyle,
997 "toggle_tab_indicators" => ToggleTabIndicators,
998 "toggle_whitespace_indicators" => ToggleWhitespaceIndicators,
999 "reset_buffer_settings" => ResetBufferSettings,
1000 "add_ruler" => AddRuler,
1001 "remove_ruler" => RemoveRuler,
1002
1003 "dump_config" => DumpConfig,
1004
1005 "search" => Search,
1006 "find_in_selection" => FindInSelection,
1007 "find_next" => FindNext,
1008 "find_previous" => FindPrevious,
1009 "find_selection_next" => FindSelectionNext,
1010 "find_selection_previous" => FindSelectionPrevious,
1011 "replace" => Replace,
1012 "query_replace" => QueryReplace,
1013
1014 "menu_activate" => MenuActivate,
1015 "menu_close" => MenuClose,
1016 "menu_left" => MenuLeft,
1017 "menu_right" => MenuRight,
1018 "menu_up" => MenuUp,
1019 "menu_down" => MenuDown,
1020 "menu_execute" => MenuExecute,
1021
1022 "open_terminal" => OpenTerminal,
1023 "close_terminal" => CloseTerminal,
1024 "focus_terminal" => FocusTerminal,
1025 "terminal_escape" => TerminalEscape,
1026 "toggle_keyboard_capture" => ToggleKeyboardCapture,
1027 "terminal_paste" => TerminalPaste,
1028
1029 "shell_command" => ShellCommand,
1030 "shell_command_replace" => ShellCommandReplace,
1031
1032 "to_upper_case" => ToUpperCase,
1033 "to_lower_case" => ToLowerCase,
1034 "toggle_case" => ToggleCase,
1035 "sort_lines" => SortLines,
1036
1037 "calibrate_input" => CalibrateInput,
1038 "event_debug" => EventDebug,
1039 "load_plugin_from_buffer" => LoadPluginFromBuffer,
1040 "open_keybinding_editor" => OpenKeybindingEditor,
1041
1042 "noop" => None,
1043
1044 "open_settings" => OpenSettings,
1045 "close_settings" => CloseSettings,
1046 "settings_save" => SettingsSave,
1047 "settings_reset" => SettingsReset,
1048 "settings_toggle_focus" => SettingsToggleFocus,
1049 "settings_activate" => SettingsActivate,
1050 "settings_search" => SettingsSearch,
1051 "settings_help" => SettingsHelp,
1052 "settings_increment" => SettingsIncrement,
1053 "settings_decrement" => SettingsDecrement,
1054 }
1055 alias {
1056 "toggle_compose_mode" => TogglePageView,
1057 "set_compose_width" => SetPageWidth,
1058 }
1059 with_char {
1060 "insert_char" => InsertChar,
1061 "set_bookmark" => SetBookmark,
1062 "jump_to_bookmark" => JumpToBookmark,
1063 "clear_bookmark" => ClearBookmark,
1064 "play_macro" => PlayMacro,
1065 "toggle_macro_recording" => ToggleMacroRecording,
1066 "show_macro" => ShowMacro,
1067 }
1068 custom {
1069 "copy_with_theme" => CopyWithTheme : {
1070 let theme = args.get("theme").and_then(|v| v.as_str()).unwrap_or("");
1072 Self::CopyWithTheme(theme.to_string())
1073 },
1074 "menu_open" => MenuOpen : {
1075 let name = args.get("name")?.as_str()?;
1076 Self::MenuOpen(name.to_string())
1077 },
1078 "switch_keybinding_map" => SwitchKeybindingMap : {
1079 let map_name = args.get("map")?.as_str()?;
1080 Self::SwitchKeybindingMap(map_name.to_string())
1081 },
1082 "prompt_confirm_with_text" => PromptConfirmWithText : {
1083 let text = args.get("text")?.as_str()?;
1084 Self::PromptConfirmWithText(text.to_string())
1085 },
1086 }
1087 }
1088
1089 pub fn is_movement_or_editing(&self) -> bool {
1092 matches!(
1093 self,
1094 Action::MoveLeft
1096 | Action::MoveRight
1097 | Action::MoveUp
1098 | Action::MoveDown
1099 | Action::MoveWordLeft
1100 | Action::MoveWordRight
1101 | Action::MoveWordEnd
1102 | Action::ViMoveWordEnd
1103 | Action::MoveLeftInLine
1104 | Action::MoveRightInLine
1105 | Action::MoveLineStart
1106 | Action::MoveLineEnd
1107 | Action::MovePageUp
1108 | Action::MovePageDown
1109 | Action::MoveDocumentStart
1110 | Action::MoveDocumentEnd
1111 | Action::SelectLeft
1113 | Action::SelectRight
1114 | Action::SelectUp
1115 | Action::SelectDown
1116 | Action::SelectToParagraphUp
1117 | Action::SelectToParagraphDown
1118 | Action::SelectWordLeft
1119 | Action::SelectWordRight
1120 | Action::SelectWordEnd
1121 | Action::ViSelectWordEnd
1122 | Action::SelectLineStart
1123 | Action::SelectLineEnd
1124 | Action::SelectDocumentStart
1125 | Action::SelectDocumentEnd
1126 | Action::SelectPageUp
1127 | Action::SelectPageDown
1128 | Action::SelectAll
1129 | Action::SelectWord
1130 | Action::SelectLine
1131 | Action::ExpandSelection
1132 | Action::BlockSelectLeft
1134 | Action::BlockSelectRight
1135 | Action::BlockSelectUp
1136 | Action::BlockSelectDown
1137 | Action::InsertChar(_)
1139 | Action::InsertNewline
1140 | Action::InsertTab
1141 | Action::DeleteBackward
1142 | Action::DeleteForward
1143 | Action::DeleteWordBackward
1144 | Action::DeleteWordForward
1145 | Action::DeleteLine
1146 | Action::DeleteToLineEnd
1147 | Action::DeleteToLineStart
1148 | Action::TransposeChars
1149 | Action::OpenLine
1150 | Action::DuplicateLine
1151 | Action::MoveLineUp
1152 | Action::MoveLineDown
1153 | Action::Cut
1155 | Action::Paste
1156 | Action::Undo
1158 | Action::Redo
1159 )
1160 }
1161
1162 pub fn is_editing(&self) -> bool {
1165 matches!(
1166 self,
1167 Action::InsertChar(_)
1168 | Action::InsertNewline
1169 | Action::InsertTab
1170 | Action::DeleteBackward
1171 | Action::DeleteForward
1172 | Action::DeleteWordBackward
1173 | Action::DeleteWordForward
1174 | Action::DeleteLine
1175 | Action::DeleteToLineEnd
1176 | Action::DeleteToLineStart
1177 | Action::DeleteViWordEnd
1178 | Action::TransposeChars
1179 | Action::OpenLine
1180 | Action::DuplicateLine
1181 | Action::MoveLineUp
1182 | Action::MoveLineDown
1183 | Action::Cut
1184 | Action::Paste
1185 )
1186 }
1187}
1188
1189#[derive(Debug, Clone, PartialEq)]
1191pub enum ChordResolution {
1192 Complete(Action),
1194 Partial,
1196 NoMatch,
1198}
1199
1200#[derive(Clone)]
1202pub struct KeybindingResolver {
1203 bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1206
1207 default_bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1209
1210 plugin_defaults: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1213
1214 chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1217
1218 default_chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1220
1221 plugin_chord_defaults: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1223}
1224
1225impl KeybindingResolver {
1226 pub fn new(config: &Config) -> Self {
1228 let mut resolver = Self {
1229 bindings: HashMap::new(),
1230 default_bindings: HashMap::new(),
1231 plugin_defaults: HashMap::new(),
1232 chord_bindings: HashMap::new(),
1233 default_chord_bindings: HashMap::new(),
1234 plugin_chord_defaults: HashMap::new(),
1235 };
1236
1237 let map_bindings = config.resolve_keymap(&config.active_keybinding_map);
1239 resolver.load_default_bindings_from_vec(&map_bindings);
1240
1241 resolver.load_bindings_from_vec(&config.keybindings);
1243
1244 resolver
1245 }
1246
1247 fn load_default_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1249 for binding in bindings {
1250 let context = if let Some(ref when) = binding.when {
1252 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1253 } else {
1254 KeyContext::Normal
1255 };
1256
1257 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1258 if !binding.keys.is_empty() {
1260 let mut sequence = Vec::new();
1262 for key_press in &binding.keys {
1263 if let Some(key_code) = Self::parse_key(&key_press.key) {
1264 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1265 sequence.push((key_code, modifiers));
1266 } else {
1267 break;
1269 }
1270 }
1271
1272 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1274 self.default_chord_bindings
1275 .entry(context)
1276 .or_default()
1277 .insert(sequence, action);
1278 }
1279 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1280 let modifiers = Self::parse_modifiers(&binding.modifiers);
1282
1283 self.insert_binding_with_equivalents(
1285 context,
1286 key_code,
1287 modifiers,
1288 action,
1289 &binding.key,
1290 );
1291 }
1292 }
1293 }
1294 }
1295
1296 fn insert_binding_with_equivalents(
1299 &mut self,
1300 context: KeyContext,
1301 key_code: KeyCode,
1302 modifiers: KeyModifiers,
1303 action: Action,
1304 key_name: &str,
1305 ) {
1306 let context_bindings = self.default_bindings.entry(context.clone()).or_default();
1307
1308 context_bindings.insert((key_code, modifiers), action.clone());
1310
1311 let equivalents = terminal_key_equivalents(key_code, modifiers);
1313 for (equiv_key, equiv_mods) in equivalents {
1314 if let Some(existing_action) = context_bindings.get(&(equiv_key, equiv_mods)) {
1316 if existing_action != &action {
1318 let equiv_name = format!("{:?}", equiv_key);
1319 tracing::warn!(
1320 "Terminal key equivalent conflict in {:?} context: {} (equivalent of {}) \
1321 is bound to {:?}, but {} is bound to {:?}. \
1322 The explicit binding takes precedence.",
1323 context,
1324 equiv_name,
1325 key_name,
1326 existing_action,
1327 key_name,
1328 action
1329 );
1330 }
1331 } else {
1333 context_bindings.insert((equiv_key, equiv_mods), action.clone());
1335 }
1336 }
1337 }
1338
1339 fn load_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1341 for binding in bindings {
1342 let context = if let Some(ref when) = binding.when {
1344 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1345 } else {
1346 KeyContext::Normal
1347 };
1348
1349 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1350 if !binding.keys.is_empty() {
1352 let mut sequence = Vec::new();
1354 for key_press in &binding.keys {
1355 if let Some(key_code) = Self::parse_key(&key_press.key) {
1356 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1357 sequence.push((key_code, modifiers));
1358 } else {
1359 break;
1361 }
1362 }
1363
1364 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1366 self.chord_bindings
1367 .entry(context)
1368 .or_default()
1369 .insert(sequence, action);
1370 }
1371 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1372 let modifiers = Self::parse_modifiers(&binding.modifiers);
1374 self.bindings
1375 .entry(context)
1376 .or_default()
1377 .insert((key_code, modifiers), action);
1378 }
1379 }
1380 }
1381 }
1382
1383 pub fn load_plugin_default(
1385 &mut self,
1386 context: KeyContext,
1387 key_code: KeyCode,
1388 modifiers: KeyModifiers,
1389 action: Action,
1390 ) {
1391 self.plugin_defaults
1392 .entry(context)
1393 .or_default()
1394 .insert((key_code, modifiers), action);
1395 }
1396
1397 pub fn load_plugin_chord_default(
1399 &mut self,
1400 context: KeyContext,
1401 sequence: Vec<(KeyCode, KeyModifiers)>,
1402 action: Action,
1403 ) {
1404 self.plugin_chord_defaults
1405 .entry(context)
1406 .or_default()
1407 .insert(sequence, action);
1408 }
1409
1410 pub fn clear_plugin_defaults_for_mode(&mut self, mode_name: &str) {
1412 let context = KeyContext::Mode(mode_name.to_string());
1413 self.plugin_defaults.remove(&context);
1414 self.plugin_chord_defaults.remove(&context);
1415 }
1416
1417 pub fn get_plugin_defaults(
1419 &self,
1420 ) -> &HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>> {
1421 &self.plugin_defaults
1422 }
1423
1424 fn is_application_wide_action(action: &Action) -> bool {
1426 matches!(
1427 action,
1428 Action::Quit
1429 | Action::ForceQuit
1430 | Action::Save
1431 | Action::SaveAs
1432 | Action::ShowHelp
1433 | Action::ShowKeyboardShortcuts
1434 | Action::PromptCancel | Action::PopupCancel )
1437 }
1438
1439 pub fn is_terminal_ui_action(action: &Action) -> bool {
1443 matches!(
1444 action,
1445 Action::CommandPalette
1447 | Action::QuickOpen
1448 | Action::OpenSettings
1449 | Action::MenuActivate
1450 | Action::MenuOpen(_)
1451 | Action::ShowHelp
1452 | Action::ShowKeyboardShortcuts
1453 | Action::Quit
1454 | Action::ForceQuit
1455 | Action::NextSplit
1457 | Action::PrevSplit
1458 | Action::SplitHorizontal
1459 | Action::SplitVertical
1460 | Action::CloseSplit
1461 | Action::ToggleMaximizeSplit
1462 | Action::NextBuffer
1464 | Action::PrevBuffer
1465 | Action::Close
1466 | Action::ScrollTabsLeft
1467 | Action::ScrollTabsRight
1468 | Action::TerminalEscape
1470 | Action::ToggleKeyboardCapture
1471 | Action::OpenTerminal
1472 | Action::CloseTerminal
1473 | Action::TerminalPaste
1474 | Action::ToggleFileExplorer
1476 | Action::ToggleMenuBar
1478 )
1479 }
1480
1481 pub fn resolve_chord(
1487 &self,
1488 chord_state: &[(KeyCode, KeyModifiers)],
1489 event: &KeyEvent,
1490 context: KeyContext,
1491 ) -> ChordResolution {
1492 let mut full_sequence: Vec<(KeyCode, KeyModifiers)> = chord_state
1494 .iter()
1495 .map(|(c, m)| normalize_key(*c, *m))
1496 .collect();
1497 let (norm_code, norm_mods) = normalize_key(event.code, event.modifiers);
1498 full_sequence.push((norm_code, norm_mods));
1499
1500 tracing::trace!(
1501 "KeybindingResolver.resolve_chord: sequence={:?}, context={:?}",
1502 full_sequence,
1503 context
1504 );
1505
1506 let search_order = vec![
1508 (&self.chord_bindings, &KeyContext::Global, "custom global"),
1509 (
1510 &self.default_chord_bindings,
1511 &KeyContext::Global,
1512 "default global",
1513 ),
1514 (&self.chord_bindings, &context, "custom context"),
1515 (&self.default_chord_bindings, &context, "default context"),
1516 (
1517 &self.plugin_chord_defaults,
1518 &context,
1519 "plugin default context",
1520 ),
1521 ];
1522
1523 let mut has_partial_match = false;
1524
1525 for (binding_map, bind_context, label) in search_order {
1526 if let Some(context_chords) = binding_map.get(bind_context) {
1527 if let Some(action) = context_chords.get(&full_sequence) {
1529 tracing::trace!(" -> Complete chord match in {}: {:?}", label, action);
1530 return ChordResolution::Complete(action.clone());
1531 }
1532
1533 for (chord_seq, _) in context_chords.iter() {
1535 if chord_seq.len() > full_sequence.len()
1536 && chord_seq[..full_sequence.len()] == full_sequence[..]
1537 {
1538 tracing::trace!(" -> Partial chord match in {}", label);
1539 has_partial_match = true;
1540 break;
1541 }
1542 }
1543 }
1544 }
1545
1546 if has_partial_match {
1547 ChordResolution::Partial
1548 } else {
1549 tracing::trace!(" -> No chord match");
1550 ChordResolution::NoMatch
1551 }
1552 }
1553
1554 pub fn resolve(&self, event: &KeyEvent, context: KeyContext) -> Action {
1556 let (norm_code, norm_mods) = normalize_key(event.code, event.modifiers);
1559 let norm = &(norm_code, norm_mods);
1560 tracing::trace!(
1561 "KeybindingResolver.resolve: code={:?}, modifiers={:?}, context={:?}",
1562 event.code,
1563 event.modifiers,
1564 context
1565 );
1566
1567 if let Some(global_bindings) = self.bindings.get(&KeyContext::Global) {
1569 if let Some(action) = global_bindings.get(norm) {
1570 tracing::trace!(" -> Found in custom global bindings: {:?}", action);
1571 return action.clone();
1572 }
1573 }
1574
1575 if let Some(global_bindings) = self.default_bindings.get(&KeyContext::Global) {
1576 if let Some(action) = global_bindings.get(norm) {
1577 tracing::trace!(" -> Found in default global bindings: {:?}", action);
1578 return action.clone();
1579 }
1580 }
1581
1582 if let Some(context_bindings) = self.bindings.get(&context) {
1584 if let Some(action) = context_bindings.get(norm) {
1585 tracing::trace!(
1586 " -> Found in custom {} bindings: {:?}",
1587 context.to_when_clause(),
1588 action
1589 );
1590 return action.clone();
1591 }
1592 }
1593
1594 if let Some(context_bindings) = self.default_bindings.get(&context) {
1596 if let Some(action) = context_bindings.get(norm) {
1597 tracing::trace!(
1598 " -> Found in default {} bindings: {:?}",
1599 context.to_when_clause(),
1600 action
1601 );
1602 return action.clone();
1603 }
1604 }
1605
1606 if let Some(plugin_bindings) = self.plugin_defaults.get(&context) {
1608 if let Some(action) = plugin_bindings.get(norm) {
1609 tracing::trace!(
1610 " -> Found in plugin default {} bindings: {:?}",
1611 context.to_when_clause(),
1612 action
1613 );
1614 return action.clone();
1615 }
1616 }
1617
1618 if context != KeyContext::Normal {
1621 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
1622 if let Some(action) = normal_bindings.get(norm) {
1623 if Self::is_application_wide_action(action) {
1624 tracing::trace!(
1625 " -> Found application-wide action in custom normal bindings: {:?}",
1626 action
1627 );
1628 return action.clone();
1629 }
1630 }
1631 }
1632
1633 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
1634 if let Some(action) = normal_bindings.get(norm) {
1635 if Self::is_application_wide_action(action) {
1636 tracing::trace!(
1637 " -> Found application-wide action in default normal bindings: {:?}",
1638 action
1639 );
1640 return action.clone();
1641 }
1642 }
1643 }
1644 }
1645
1646 if context.allows_text_input() && is_text_input_modifier(event.modifiers) {
1648 if let KeyCode::Char(c) = event.code {
1649 tracing::trace!(" -> Character input: '{}'", c);
1650 return Action::InsertChar(c);
1651 }
1652 }
1653
1654 tracing::trace!(" -> No binding found, returning Action::None");
1655 Action::None
1656 }
1657
1658 pub fn resolve_in_context_only(&self, event: &KeyEvent, context: KeyContext) -> Option<Action> {
1663 let norm = normalize_key(event.code, event.modifiers);
1664 if let Some(context_bindings) = self.bindings.get(&context) {
1666 if let Some(action) = context_bindings.get(&norm) {
1667 return Some(action.clone());
1668 }
1669 }
1670
1671 if let Some(context_bindings) = self.default_bindings.get(&context) {
1673 if let Some(action) = context_bindings.get(&norm) {
1674 return Some(action.clone());
1675 }
1676 }
1677
1678 None
1679 }
1680
1681 pub fn resolve_terminal_ui_action(&self, event: &KeyEvent) -> Action {
1685 let norm = normalize_key(event.code, event.modifiers);
1686 tracing::trace!(
1687 "KeybindingResolver.resolve_terminal_ui_action: code={:?}, modifiers={:?}",
1688 event.code,
1689 event.modifiers
1690 );
1691
1692 for bindings in [&self.bindings, &self.default_bindings] {
1694 if let Some(terminal_bindings) = bindings.get(&KeyContext::Terminal) {
1695 if let Some(action) = terminal_bindings.get(&norm) {
1696 if Self::is_terminal_ui_action(action) {
1697 tracing::trace!(" -> Found UI action in terminal bindings: {:?}", action);
1698 return action.clone();
1699 }
1700 }
1701 }
1702 }
1703
1704 for bindings in [&self.bindings, &self.default_bindings] {
1706 if let Some(global_bindings) = bindings.get(&KeyContext::Global) {
1707 if let Some(action) = global_bindings.get(&norm) {
1708 if Self::is_terminal_ui_action(action) {
1709 tracing::trace!(" -> Found UI action in global bindings: {:?}", action);
1710 return action.clone();
1711 }
1712 }
1713 }
1714 }
1715
1716 for bindings in [&self.bindings, &self.default_bindings] {
1718 if let Some(normal_bindings) = bindings.get(&KeyContext::Normal) {
1719 if let Some(action) = normal_bindings.get(&norm) {
1720 if Self::is_terminal_ui_action(action) {
1721 tracing::trace!(" -> Found UI action in normal bindings: {:?}", action);
1722 return action.clone();
1723 }
1724 }
1725 }
1726 }
1727
1728 tracing::trace!(" -> No UI action found");
1729 Action::None
1730 }
1731
1732 pub fn find_keybinding_for_action(
1735 &self,
1736 action_name: &str,
1737 context: KeyContext,
1738 ) -> Option<String> {
1739 let target_action = Action::from_str(action_name, &HashMap::new())?;
1741
1742 let search_maps = vec![
1744 self.bindings.get(&context),
1745 self.bindings.get(&KeyContext::Global),
1746 self.default_bindings.get(&context),
1747 self.default_bindings.get(&KeyContext::Global),
1748 ];
1749
1750 for map in search_maps.into_iter().flatten() {
1751 let mut matches: Vec<(KeyCode, KeyModifiers)> = map
1753 .iter()
1754 .filter(|(_, action)| {
1755 std::mem::discriminant(*action) == std::mem::discriminant(&target_action)
1756 })
1757 .map(|((key_code, modifiers), _)| (*key_code, *modifiers))
1758 .collect();
1759
1760 if !matches.is_empty() {
1761 matches.sort_by(|(key_a, mod_a), (key_b, mod_b)| {
1763 let mod_count_a = mod_a.bits().count_ones();
1765 let mod_count_b = mod_b.bits().count_ones();
1766 match mod_count_a.cmp(&mod_count_b) {
1767 std::cmp::Ordering::Equal => {
1768 match mod_a.bits().cmp(&mod_b.bits()) {
1770 std::cmp::Ordering::Equal => {
1771 Self::key_code_sort_key(key_a)
1773 .cmp(&Self::key_code_sort_key(key_b))
1774 }
1775 other => other,
1776 }
1777 }
1778 other => other,
1779 }
1780 });
1781
1782 let (key_code, modifiers) = matches[0];
1783 return Some(format_keybinding(&key_code, &modifiers));
1784 }
1785 }
1786
1787 None
1788 }
1789
1790 fn key_code_sort_key(key_code: &KeyCode) -> (u8, u32) {
1792 match key_code {
1793 KeyCode::Char(c) => (0, *c as u32),
1794 KeyCode::F(n) => (1, *n as u32),
1795 KeyCode::Enter => (2, 0),
1796 KeyCode::Tab => (2, 1),
1797 KeyCode::Backspace => (2, 2),
1798 KeyCode::Delete => (2, 3),
1799 KeyCode::Esc => (2, 4),
1800 KeyCode::Left => (3, 0),
1801 KeyCode::Right => (3, 1),
1802 KeyCode::Up => (3, 2),
1803 KeyCode::Down => (3, 3),
1804 KeyCode::Home => (3, 4),
1805 KeyCode::End => (3, 5),
1806 KeyCode::PageUp => (3, 6),
1807 KeyCode::PageDown => (3, 7),
1808 _ => (255, 0),
1809 }
1810 }
1811
1812 pub fn find_menu_mnemonic(&self, menu_name: &str) -> Option<char> {
1815 let search_maps = vec![
1817 self.bindings.get(&KeyContext::Normal),
1818 self.bindings.get(&KeyContext::Global),
1819 self.default_bindings.get(&KeyContext::Normal),
1820 self.default_bindings.get(&KeyContext::Global),
1821 ];
1822
1823 for map in search_maps.into_iter().flatten() {
1824 for ((key_code, modifiers), action) in map {
1825 if let Action::MenuOpen(name) = action {
1827 if name.eq_ignore_ascii_case(menu_name) && *modifiers == KeyModifiers::ALT {
1828 if let KeyCode::Char(c) = key_code {
1830 return Some(c.to_ascii_lowercase());
1831 }
1832 }
1833 }
1834 }
1835 }
1836
1837 None
1838 }
1839
1840 fn parse_key(key: &str) -> Option<KeyCode> {
1842 let lower = key.to_lowercase();
1843 match lower.as_str() {
1844 "enter" => Some(KeyCode::Enter),
1845 "backspace" => Some(KeyCode::Backspace),
1846 "delete" | "del" => Some(KeyCode::Delete),
1847 "tab" => Some(KeyCode::Tab),
1848 "backtab" => Some(KeyCode::BackTab),
1849 "esc" | "escape" => Some(KeyCode::Esc),
1850 "space" => Some(KeyCode::Char(' ')),
1851
1852 "left" => Some(KeyCode::Left),
1853 "right" => Some(KeyCode::Right),
1854 "up" => Some(KeyCode::Up),
1855 "down" => Some(KeyCode::Down),
1856 "home" => Some(KeyCode::Home),
1857 "end" => Some(KeyCode::End),
1858 "pageup" => Some(KeyCode::PageUp),
1859 "pagedown" => Some(KeyCode::PageDown),
1860
1861 s if s.len() == 1 => s.chars().next().map(KeyCode::Char),
1862 s if s.starts_with('f') && s.len() >= 2 => s[1..].parse::<u8>().ok().map(KeyCode::F),
1864 _ => None,
1865 }
1866 }
1867
1868 fn parse_modifiers(modifiers: &[String]) -> KeyModifiers {
1870 let mut result = KeyModifiers::empty();
1871 for m in modifiers {
1872 match m.to_lowercase().as_str() {
1873 "ctrl" | "control" => result |= KeyModifiers::CONTROL,
1874 "shift" => result |= KeyModifiers::SHIFT,
1875 "alt" => result |= KeyModifiers::ALT,
1876 "super" | "cmd" | "command" | "meta" => result |= KeyModifiers::SUPER,
1877 _ => {}
1878 }
1879 }
1880 result
1881 }
1882
1883 pub fn get_all_bindings(&self) -> Vec<(String, String)> {
1887 let mut bindings = Vec::new();
1888
1889 for context in &[
1891 KeyContext::Normal,
1892 KeyContext::Prompt,
1893 KeyContext::Popup,
1894 KeyContext::FileExplorer,
1895 KeyContext::Menu,
1896 ] {
1897 let mut all_keys: HashMap<(KeyCode, KeyModifiers), Action> = HashMap::new();
1898
1899 if let Some(context_defaults) = self.default_bindings.get(context) {
1901 for (key, action) in context_defaults {
1902 all_keys.insert(*key, action.clone());
1903 }
1904 }
1905
1906 if let Some(context_bindings) = self.bindings.get(context) {
1908 for (key, action) in context_bindings {
1909 all_keys.insert(*key, action.clone());
1910 }
1911 }
1912
1913 let context_str = if *context != KeyContext::Normal {
1915 format!("[{}] ", context.to_when_clause())
1916 } else {
1917 String::new()
1918 };
1919
1920 for ((key_code, modifiers), action) in all_keys {
1921 let key_str = Self::format_key(key_code, modifiers);
1922 let action_str = format!("{}{}", context_str, Self::format_action(&action));
1923 bindings.push((key_str, action_str));
1924 }
1925 }
1926
1927 bindings.sort_by(|a, b| a.1.cmp(&b.1));
1929
1930 bindings
1931 }
1932
1933 fn format_key(key_code: KeyCode, modifiers: KeyModifiers) -> String {
1935 format_keybinding(&key_code, &modifiers)
1936 }
1937
1938 fn format_action(action: &Action) -> String {
1940 match action {
1941 Action::InsertChar(c) => t!("action.insert_char", char = c),
1942 Action::InsertNewline => t!("action.insert_newline"),
1943 Action::InsertTab => t!("action.insert_tab"),
1944 Action::MoveLeft => t!("action.move_left"),
1945 Action::MoveRight => t!("action.move_right"),
1946 Action::MoveUp => t!("action.move_up"),
1947 Action::MoveDown => t!("action.move_down"),
1948 Action::MoveWordLeft => t!("action.move_word_left"),
1949 Action::MoveWordRight => t!("action.move_word_right"),
1950 Action::MoveWordEnd => t!("action.move_word_end"),
1951 Action::ViMoveWordEnd => t!("action.move_word_end"),
1952 Action::MoveLeftInLine => t!("action.move_left"),
1953 Action::MoveRightInLine => t!("action.move_right"),
1954 Action::MoveLineStart => t!("action.move_line_start"),
1955 Action::MoveLineEnd => t!("action.move_line_end"),
1956 Action::MoveLineUp => t!("action.move_line_up"),
1957 Action::MoveLineDown => t!("action.move_line_down"),
1958 Action::MovePageUp => t!("action.move_page_up"),
1959 Action::MovePageDown => t!("action.move_page_down"),
1960 Action::MoveDocumentStart => t!("action.move_document_start"),
1961 Action::MoveDocumentEnd => t!("action.move_document_end"),
1962 Action::SelectLeft => t!("action.select_left"),
1963 Action::SelectRight => t!("action.select_right"),
1964 Action::SelectUp => t!("action.select_up"),
1965 Action::SelectDown => t!("action.select_down"),
1966 Action::SelectToParagraphUp => t!("action.select_to_paragraph_up"),
1967 Action::SelectToParagraphDown => t!("action.select_to_paragraph_down"),
1968 Action::SelectWordLeft => t!("action.select_word_left"),
1969 Action::SelectWordRight => t!("action.select_word_right"),
1970 Action::SelectWordEnd => t!("action.select_word_end"),
1971 Action::ViSelectWordEnd => t!("action.select_word_end"),
1972 Action::SelectLineStart => t!("action.select_line_start"),
1973 Action::SelectLineEnd => t!("action.select_line_end"),
1974 Action::SelectDocumentStart => t!("action.select_document_start"),
1975 Action::SelectDocumentEnd => t!("action.select_document_end"),
1976 Action::SelectPageUp => t!("action.select_page_up"),
1977 Action::SelectPageDown => t!("action.select_page_down"),
1978 Action::SelectAll => t!("action.select_all"),
1979 Action::SelectWord => t!("action.select_word"),
1980 Action::SelectLine => t!("action.select_line"),
1981 Action::ExpandSelection => t!("action.expand_selection"),
1982 Action::BlockSelectLeft => t!("action.block_select_left"),
1983 Action::BlockSelectRight => t!("action.block_select_right"),
1984 Action::BlockSelectUp => t!("action.block_select_up"),
1985 Action::BlockSelectDown => t!("action.block_select_down"),
1986 Action::DeleteBackward => t!("action.delete_backward"),
1987 Action::DeleteForward => t!("action.delete_forward"),
1988 Action::DeleteWordBackward => t!("action.delete_word_backward"),
1989 Action::DeleteWordForward => t!("action.delete_word_forward"),
1990 Action::DeleteLine => t!("action.delete_line"),
1991 Action::DeleteToLineEnd => t!("action.delete_to_line_end"),
1992 Action::DeleteToLineStart => t!("action.delete_to_line_start"),
1993 Action::DeleteViWordEnd => t!("action.delete_word_forward"),
1994 Action::TransposeChars => t!("action.transpose_chars"),
1995 Action::OpenLine => t!("action.open_line"),
1996 Action::DuplicateLine => t!("action.duplicate_line"),
1997 Action::Recenter => t!("action.recenter"),
1998 Action::SetMark => t!("action.set_mark"),
1999 Action::Copy => t!("action.copy"),
2000 Action::CopyWithTheme(theme) if theme.is_empty() => t!("action.copy_with_formatting"),
2001 Action::CopyWithTheme(theme) => t!("action.copy_with_theme", theme = theme),
2002 Action::Cut => t!("action.cut"),
2003 Action::Paste => t!("action.paste"),
2004 Action::YankWordForward => t!("action.yank_word_forward"),
2005 Action::YankWordBackward => t!("action.yank_word_backward"),
2006 Action::YankToLineEnd => t!("action.yank_to_line_end"),
2007 Action::YankToLineStart => t!("action.yank_to_line_start"),
2008 Action::YankViWordEnd => t!("action.yank_word_forward"),
2009 Action::AddCursorAbove => t!("action.add_cursor_above"),
2010 Action::AddCursorBelow => t!("action.add_cursor_below"),
2011 Action::AddCursorNextMatch => t!("action.add_cursor_next_match"),
2012 Action::RemoveSecondaryCursors => t!("action.remove_secondary_cursors"),
2013 Action::Save => t!("action.save"),
2014 Action::SaveAs => t!("action.save_as"),
2015 Action::Open => t!("action.open"),
2016 Action::SwitchProject => t!("action.switch_project"),
2017 Action::New => t!("action.new"),
2018 Action::Close => t!("action.close"),
2019 Action::CloseTab => t!("action.close_tab"),
2020 Action::Quit => t!("action.quit"),
2021 Action::ForceQuit => t!("action.force_quit"),
2022 Action::Detach => t!("action.detach"),
2023 Action::Revert => t!("action.revert"),
2024 Action::ToggleAutoRevert => t!("action.toggle_auto_revert"),
2025 Action::FormatBuffer => t!("action.format_buffer"),
2026 Action::TrimTrailingWhitespace => t!("action.trim_trailing_whitespace"),
2027 Action::EnsureFinalNewline => t!("action.ensure_final_newline"),
2028 Action::GotoLine => t!("action.goto_line"),
2029 Action::ScanLineIndex => t!("action.scan_line_index"),
2030 Action::GoToMatchingBracket => t!("action.goto_matching_bracket"),
2031 Action::JumpToNextError => t!("action.jump_to_next_error"),
2032 Action::JumpToPreviousError => t!("action.jump_to_previous_error"),
2033 Action::SmartHome => t!("action.smart_home"),
2034 Action::DedentSelection => t!("action.dedent_selection"),
2035 Action::ToggleComment => t!("action.toggle_comment"),
2036 Action::DabbrevExpand => std::borrow::Cow::Borrowed("Expand abbreviation (dabbrev)"),
2037 Action::ToggleFold => t!("action.toggle_fold"),
2038 Action::SetBookmark(c) => t!("action.set_bookmark", key = c),
2039 Action::JumpToBookmark(c) => t!("action.jump_to_bookmark", key = c),
2040 Action::ClearBookmark(c) => t!("action.clear_bookmark", key = c),
2041 Action::ListBookmarks => t!("action.list_bookmarks"),
2042 Action::ToggleSearchCaseSensitive => t!("action.toggle_search_case_sensitive"),
2043 Action::ToggleSearchWholeWord => t!("action.toggle_search_whole_word"),
2044 Action::ToggleSearchRegex => t!("action.toggle_search_regex"),
2045 Action::ToggleSearchConfirmEach => t!("action.toggle_search_confirm_each"),
2046 Action::StartMacroRecording => t!("action.start_macro_recording"),
2047 Action::StopMacroRecording => t!("action.stop_macro_recording"),
2048 Action::PlayMacro(c) => t!("action.play_macro", key = c),
2049 Action::ToggleMacroRecording(c) => t!("action.toggle_macro_recording", key = c),
2050 Action::ShowMacro(c) => t!("action.show_macro", key = c),
2051 Action::ListMacros => t!("action.list_macros"),
2052 Action::PromptRecordMacro => t!("action.prompt_record_macro"),
2053 Action::PromptPlayMacro => t!("action.prompt_play_macro"),
2054 Action::PlayLastMacro => t!("action.play_last_macro"),
2055 Action::PromptSetBookmark => t!("action.prompt_set_bookmark"),
2056 Action::PromptJumpToBookmark => t!("action.prompt_jump_to_bookmark"),
2057 Action::Undo => t!("action.undo"),
2058 Action::Redo => t!("action.redo"),
2059 Action::ScrollUp => t!("action.scroll_up"),
2060 Action::ScrollDown => t!("action.scroll_down"),
2061 Action::ShowHelp => t!("action.show_help"),
2062 Action::ShowKeyboardShortcuts => t!("action.show_keyboard_shortcuts"),
2063 Action::ShowWarnings => t!("action.show_warnings"),
2064 Action::ShowStatusLog => t!("action.show_status_log"),
2065 Action::ShowLspStatus => t!("action.show_lsp_status"),
2066 Action::ClearWarnings => t!("action.clear_warnings"),
2067 Action::CommandPalette => t!("action.command_palette"),
2068 Action::QuickOpen => t!("action.quick_open"),
2069 Action::InspectThemeAtCursor => t!("action.inspect_theme_at_cursor"),
2070 Action::ToggleLineWrap => t!("action.toggle_line_wrap"),
2071 Action::ToggleCurrentLineHighlight => t!("action.toggle_current_line_highlight"),
2072 Action::ToggleReadOnly => t!("action.toggle_read_only"),
2073 Action::TogglePageView => t!("action.toggle_page_view"),
2074 Action::SetPageWidth => t!("action.set_page_width"),
2075 Action::NextBuffer => t!("action.next_buffer"),
2076 Action::PrevBuffer => t!("action.prev_buffer"),
2077 Action::NavigateBack => t!("action.navigate_back"),
2078 Action::NavigateForward => t!("action.navigate_forward"),
2079 Action::SplitHorizontal => t!("action.split_horizontal"),
2080 Action::SplitVertical => t!("action.split_vertical"),
2081 Action::CloseSplit => t!("action.close_split"),
2082 Action::NextSplit => t!("action.next_split"),
2083 Action::PrevSplit => t!("action.prev_split"),
2084 Action::IncreaseSplitSize => t!("action.increase_split_size"),
2085 Action::DecreaseSplitSize => t!("action.decrease_split_size"),
2086 Action::ToggleMaximizeSplit => t!("action.toggle_maximize_split"),
2087 Action::PromptConfirm => t!("action.prompt_confirm"),
2088 Action::PromptConfirmWithText(ref text) => {
2089 format!("{} ({})", t!("action.prompt_confirm"), text).into()
2090 }
2091 Action::PromptCancel => t!("action.prompt_cancel"),
2092 Action::PromptBackspace => t!("action.prompt_backspace"),
2093 Action::PromptDelete => t!("action.prompt_delete"),
2094 Action::PromptMoveLeft => t!("action.prompt_move_left"),
2095 Action::PromptMoveRight => t!("action.prompt_move_right"),
2096 Action::PromptMoveStart => t!("action.prompt_move_start"),
2097 Action::PromptMoveEnd => t!("action.prompt_move_end"),
2098 Action::PromptSelectPrev => t!("action.prompt_select_prev"),
2099 Action::PromptSelectNext => t!("action.prompt_select_next"),
2100 Action::PromptPageUp => t!("action.prompt_page_up"),
2101 Action::PromptPageDown => t!("action.prompt_page_down"),
2102 Action::PromptAcceptSuggestion => t!("action.prompt_accept_suggestion"),
2103 Action::PromptMoveWordLeft => t!("action.prompt_move_word_left"),
2104 Action::PromptMoveWordRight => t!("action.prompt_move_word_right"),
2105 Action::PromptDeleteWordForward => t!("action.prompt_delete_word_forward"),
2106 Action::PromptDeleteWordBackward => t!("action.prompt_delete_word_backward"),
2107 Action::PromptDeleteToLineEnd => t!("action.prompt_delete_to_line_end"),
2108 Action::PromptCopy => t!("action.prompt_copy"),
2109 Action::PromptCut => t!("action.prompt_cut"),
2110 Action::PromptPaste => t!("action.prompt_paste"),
2111 Action::PromptMoveLeftSelecting => t!("action.prompt_move_left_selecting"),
2112 Action::PromptMoveRightSelecting => t!("action.prompt_move_right_selecting"),
2113 Action::PromptMoveHomeSelecting => t!("action.prompt_move_home_selecting"),
2114 Action::PromptMoveEndSelecting => t!("action.prompt_move_end_selecting"),
2115 Action::PromptSelectWordLeft => t!("action.prompt_select_word_left"),
2116 Action::PromptSelectWordRight => t!("action.prompt_select_word_right"),
2117 Action::PromptSelectAll => t!("action.prompt_select_all"),
2118 Action::FileBrowserToggleHidden => t!("action.file_browser_toggle_hidden"),
2119 Action::FileBrowserToggleDetectEncoding => {
2120 t!("action.file_browser_toggle_detect_encoding")
2121 }
2122 Action::PopupSelectNext => t!("action.popup_select_next"),
2123 Action::PopupSelectPrev => t!("action.popup_select_prev"),
2124 Action::PopupPageUp => t!("action.popup_page_up"),
2125 Action::PopupPageDown => t!("action.popup_page_down"),
2126 Action::PopupConfirm => t!("action.popup_confirm"),
2127 Action::PopupCancel => t!("action.popup_cancel"),
2128 Action::ToggleFileExplorer => t!("action.toggle_file_explorer"),
2129 Action::ToggleMenuBar => t!("action.toggle_menu_bar"),
2130 Action::ToggleTabBar => t!("action.toggle_tab_bar"),
2131 Action::ToggleStatusBar => t!("action.toggle_status_bar"),
2132 Action::TogglePromptLine => t!("action.toggle_prompt_line"),
2133 Action::ToggleVerticalScrollbar => t!("action.toggle_vertical_scrollbar"),
2134 Action::ToggleHorizontalScrollbar => t!("action.toggle_horizontal_scrollbar"),
2135 Action::FocusFileExplorer => t!("action.focus_file_explorer"),
2136 Action::FocusEditor => t!("action.focus_editor"),
2137 Action::FileExplorerUp => t!("action.file_explorer_up"),
2138 Action::FileExplorerDown => t!("action.file_explorer_down"),
2139 Action::FileExplorerPageUp => t!("action.file_explorer_page_up"),
2140 Action::FileExplorerPageDown => t!("action.file_explorer_page_down"),
2141 Action::FileExplorerExpand => t!("action.file_explorer_expand"),
2142 Action::FileExplorerCollapse => t!("action.file_explorer_collapse"),
2143 Action::FileExplorerOpen => t!("action.file_explorer_open"),
2144 Action::FileExplorerRefresh => t!("action.file_explorer_refresh"),
2145 Action::FileExplorerNewFile => t!("action.file_explorer_new_file"),
2146 Action::FileExplorerNewDirectory => t!("action.file_explorer_new_directory"),
2147 Action::FileExplorerDelete => t!("action.file_explorer_delete"),
2148 Action::FileExplorerRename => t!("action.file_explorer_rename"),
2149 Action::FileExplorerToggleHidden => t!("action.file_explorer_toggle_hidden"),
2150 Action::FileExplorerToggleGitignored => t!("action.file_explorer_toggle_gitignored"),
2151 Action::FileExplorerSearchClear => t!("action.file_explorer_search_clear"),
2152 Action::FileExplorerSearchBackspace => t!("action.file_explorer_search_backspace"),
2153 Action::LspCompletion => t!("action.lsp_completion"),
2154 Action::LspGotoDefinition => t!("action.lsp_goto_definition"),
2155 Action::LspReferences => t!("action.lsp_references"),
2156 Action::LspRename => t!("action.lsp_rename"),
2157 Action::LspHover => t!("action.lsp_hover"),
2158 Action::LspSignatureHelp => t!("action.lsp_signature_help"),
2159 Action::LspCodeActions => t!("action.lsp_code_actions"),
2160 Action::LspRestart => t!("action.lsp_restart"),
2161 Action::LspStop => t!("action.lsp_stop"),
2162 Action::LspToggleForBuffer => t!("action.lsp_toggle_for_buffer"),
2163 Action::ToggleInlayHints => t!("action.toggle_inlay_hints"),
2164 Action::ToggleMouseHover => t!("action.toggle_mouse_hover"),
2165 Action::ToggleLineNumbers => t!("action.toggle_line_numbers"),
2166 Action::ToggleScrollSync => t!("action.toggle_scroll_sync"),
2167 Action::ToggleMouseCapture => t!("action.toggle_mouse_capture"),
2168 Action::ToggleDebugHighlights => t!("action.toggle_debug_highlights"),
2169 Action::SetBackground => t!("action.set_background"),
2170 Action::SetBackgroundBlend => t!("action.set_background_blend"),
2171 Action::AddRuler => t!("action.add_ruler"),
2172 Action::RemoveRuler => t!("action.remove_ruler"),
2173 Action::SetTabSize => t!("action.set_tab_size"),
2174 Action::SetLineEnding => t!("action.set_line_ending"),
2175 Action::SetEncoding => t!("action.set_encoding"),
2176 Action::ReloadWithEncoding => t!("action.reload_with_encoding"),
2177 Action::SetLanguage => t!("action.set_language"),
2178 Action::ToggleIndentationStyle => t!("action.toggle_indentation_style"),
2179 Action::ToggleTabIndicators => t!("action.toggle_tab_indicators"),
2180 Action::ToggleWhitespaceIndicators => t!("action.toggle_whitespace_indicators"),
2181 Action::ResetBufferSettings => t!("action.reset_buffer_settings"),
2182 Action::DumpConfig => t!("action.dump_config"),
2183 Action::Search => t!("action.search"),
2184 Action::FindInSelection => t!("action.find_in_selection"),
2185 Action::FindNext => t!("action.find_next"),
2186 Action::FindPrevious => t!("action.find_previous"),
2187 Action::FindSelectionNext => t!("action.find_selection_next"),
2188 Action::FindSelectionPrevious => t!("action.find_selection_previous"),
2189 Action::Replace => t!("action.replace"),
2190 Action::QueryReplace => t!("action.query_replace"),
2191 Action::MenuActivate => t!("action.menu_activate"),
2192 Action::MenuClose => t!("action.menu_close"),
2193 Action::MenuLeft => t!("action.menu_left"),
2194 Action::MenuRight => t!("action.menu_right"),
2195 Action::MenuUp => t!("action.menu_up"),
2196 Action::MenuDown => t!("action.menu_down"),
2197 Action::MenuExecute => t!("action.menu_execute"),
2198 Action::MenuOpen(name) => t!("action.menu_open", name = name),
2199 Action::SwitchKeybindingMap(map) => t!("action.switch_keybinding_map", map = map),
2200 Action::PluginAction(name) => t!("action.plugin_action", name = name),
2201 Action::ScrollTabsLeft => t!("action.scroll_tabs_left"),
2202 Action::ScrollTabsRight => t!("action.scroll_tabs_right"),
2203 Action::SelectTheme => t!("action.select_theme"),
2204 Action::SelectKeybindingMap => t!("action.select_keybinding_map"),
2205 Action::SelectCursorStyle => t!("action.select_cursor_style"),
2206 Action::SelectLocale => t!("action.select_locale"),
2207 Action::SwitchToPreviousTab => t!("action.switch_to_previous_tab"),
2208 Action::SwitchToTabByName => t!("action.switch_to_tab_by_name"),
2209 Action::OpenTerminal => t!("action.open_terminal"),
2210 Action::CloseTerminal => t!("action.close_terminal"),
2211 Action::FocusTerminal => t!("action.focus_terminal"),
2212 Action::TerminalEscape => t!("action.terminal_escape"),
2213 Action::ToggleKeyboardCapture => t!("action.toggle_keyboard_capture"),
2214 Action::TerminalPaste => t!("action.terminal_paste"),
2215 Action::OpenSettings => t!("action.open_settings"),
2216 Action::CloseSettings => t!("action.close_settings"),
2217 Action::SettingsSave => t!("action.settings_save"),
2218 Action::SettingsReset => t!("action.settings_reset"),
2219 Action::SettingsToggleFocus => t!("action.settings_toggle_focus"),
2220 Action::SettingsActivate => t!("action.settings_activate"),
2221 Action::SettingsSearch => t!("action.settings_search"),
2222 Action::SettingsHelp => t!("action.settings_help"),
2223 Action::SettingsIncrement => t!("action.settings_increment"),
2224 Action::SettingsDecrement => t!("action.settings_decrement"),
2225 Action::ShellCommand => t!("action.shell_command"),
2226 Action::ShellCommandReplace => t!("action.shell_command_replace"),
2227 Action::ToUpperCase => t!("action.to_uppercase"),
2228 Action::ToLowerCase => t!("action.to_lowercase"),
2229 Action::ToggleCase => t!("action.to_uppercase"),
2230 Action::SortLines => t!("action.sort_lines"),
2231 Action::CalibrateInput => t!("action.calibrate_input"),
2232 Action::EventDebug => t!("action.event_debug"),
2233 Action::LoadPluginFromBuffer => "Load Plugin from Buffer".into(),
2234 Action::OpenKeybindingEditor => "Keybinding Editor".into(),
2235 Action::None => t!("action.none"),
2236 }
2237 .to_string()
2238 }
2239
2240 pub fn parse_key_public(key: &str) -> Option<KeyCode> {
2242 Self::parse_key(key)
2243 }
2244
2245 pub fn parse_modifiers_public(modifiers: &[String]) -> KeyModifiers {
2247 Self::parse_modifiers(modifiers)
2248 }
2249
2250 pub fn format_action_from_str(action_name: &str) -> String {
2254 if let Some(action) = Action::from_str(action_name, &std::collections::HashMap::new()) {
2256 Self::format_action(&action)
2257 } else {
2258 action_name
2260 .split('_')
2261 .map(|word| {
2262 let mut chars = word.chars();
2263 match chars.next() {
2264 Some(c) => {
2265 let upper: String = c.to_uppercase().collect();
2266 format!("{}{}", upper, chars.as_str())
2267 }
2268 None => String::new(),
2269 }
2270 })
2271 .collect::<Vec<_>>()
2272 .join(" ")
2273 }
2274 }
2275
2276 pub fn all_action_names() -> Vec<String> {
2280 Action::all_action_names()
2281 }
2282
2283 pub fn get_keybinding_for_action(
2289 &self,
2290 action: &Action,
2291 context: KeyContext,
2292 ) -> Option<String> {
2293 fn find_best_keybinding(
2295 bindings: &HashMap<(KeyCode, KeyModifiers), Action>,
2296 action: &Action,
2297 ) -> Option<(KeyCode, KeyModifiers)> {
2298 let matches: Vec<_> = bindings
2299 .iter()
2300 .filter(|(_, a)| *a == action)
2301 .map(|((k, m), _)| (*k, *m))
2302 .collect();
2303
2304 if matches.is_empty() {
2305 return None;
2306 }
2307
2308 let mut sorted = matches;
2311 sorted.sort_by(|(k1, m1), (k2, m2)| {
2312 let score1 = keybinding_priority_score(k1);
2313 let score2 = keybinding_priority_score(k2);
2314 match score1.cmp(&score2) {
2316 std::cmp::Ordering::Equal => {
2317 let s1 = format_keybinding(k1, m1);
2319 let s2 = format_keybinding(k2, m2);
2320 s1.cmp(&s2)
2321 }
2322 other => other,
2323 }
2324 });
2325
2326 sorted.into_iter().next()
2327 }
2328
2329 if let Some(context_bindings) = self.bindings.get(&context) {
2331 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
2332 return Some(format_keybinding(&keycode, &modifiers));
2333 }
2334 }
2335
2336 if let Some(context_bindings) = self.default_bindings.get(&context) {
2338 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
2339 return Some(format_keybinding(&keycode, &modifiers));
2340 }
2341 }
2342
2343 if context != KeyContext::Normal && Self::is_application_wide_action(action) {
2345 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
2347 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
2348 return Some(format_keybinding(&keycode, &modifiers));
2349 }
2350 }
2351
2352 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
2354 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
2355 return Some(format_keybinding(&keycode, &modifiers));
2356 }
2357 }
2358 }
2359
2360 None
2361 }
2362
2363 pub fn reload(&mut self, config: &Config) {
2365 self.bindings.clear();
2366 for binding in &config.keybindings {
2367 if let Some(key_code) = Self::parse_key(&binding.key) {
2368 let modifiers = Self::parse_modifiers(&binding.modifiers);
2369 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
2370 let context = if let Some(ref when) = binding.when {
2372 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
2373 } else {
2374 KeyContext::Normal
2375 };
2376
2377 self.bindings
2378 .entry(context)
2379 .or_default()
2380 .insert((key_code, modifiers), action);
2381 }
2382 }
2383 }
2384 }
2385}
2386
2387#[cfg(test)]
2388mod tests {
2389 use super::*;
2390
2391 #[test]
2392 fn test_parse_key() {
2393 assert_eq!(KeybindingResolver::parse_key("enter"), Some(KeyCode::Enter));
2394 assert_eq!(
2395 KeybindingResolver::parse_key("backspace"),
2396 Some(KeyCode::Backspace)
2397 );
2398 assert_eq!(KeybindingResolver::parse_key("tab"), Some(KeyCode::Tab));
2399 assert_eq!(
2400 KeybindingResolver::parse_key("backtab"),
2401 Some(KeyCode::BackTab)
2402 );
2403 assert_eq!(
2404 KeybindingResolver::parse_key("BackTab"),
2405 Some(KeyCode::BackTab)
2406 );
2407 assert_eq!(KeybindingResolver::parse_key("a"), Some(KeyCode::Char('a')));
2408 }
2409
2410 #[test]
2411 fn test_parse_modifiers() {
2412 let mods = vec!["ctrl".to_string()];
2413 assert_eq!(
2414 KeybindingResolver::parse_modifiers(&mods),
2415 KeyModifiers::CONTROL
2416 );
2417
2418 let mods = vec!["ctrl".to_string(), "shift".to_string()];
2419 assert_eq!(
2420 KeybindingResolver::parse_modifiers(&mods),
2421 KeyModifiers::CONTROL | KeyModifiers::SHIFT
2422 );
2423 }
2424
2425 #[test]
2426 fn test_resolve_basic() {
2427 let config = Config::default();
2428 let resolver = KeybindingResolver::new(&config);
2429
2430 let event = KeyEvent::new(KeyCode::Left, KeyModifiers::empty());
2431 assert_eq!(
2432 resolver.resolve(&event, KeyContext::Normal),
2433 Action::MoveLeft
2434 );
2435
2436 let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2437 assert_eq!(
2438 resolver.resolve(&event, KeyContext::Normal),
2439 Action::InsertChar('a')
2440 );
2441 }
2442
2443 #[test]
2444 fn test_action_from_str() {
2445 let args = HashMap::new();
2446 assert_eq!(Action::from_str("move_left", &args), Some(Action::MoveLeft));
2447 assert_eq!(Action::from_str("save", &args), Some(Action::Save));
2448 assert_eq!(
2450 Action::from_str("unknown", &args),
2451 Some(Action::PluginAction("unknown".to_string()))
2452 );
2453
2454 assert_eq!(
2456 Action::from_str("keyboard_shortcuts", &args),
2457 Some(Action::ShowKeyboardShortcuts)
2458 );
2459 assert_eq!(
2460 Action::from_str("prompt_confirm", &args),
2461 Some(Action::PromptConfirm)
2462 );
2463 assert_eq!(
2464 Action::from_str("popup_cancel", &args),
2465 Some(Action::PopupCancel)
2466 );
2467
2468 assert_eq!(
2470 Action::from_str("calibrate_input", &args),
2471 Some(Action::CalibrateInput)
2472 );
2473 }
2474
2475 #[test]
2476 fn test_key_context_from_when_clause() {
2477 assert_eq!(
2478 KeyContext::from_when_clause("normal"),
2479 Some(KeyContext::Normal)
2480 );
2481 assert_eq!(
2482 KeyContext::from_when_clause("prompt"),
2483 Some(KeyContext::Prompt)
2484 );
2485 assert_eq!(
2486 KeyContext::from_when_clause("popup"),
2487 Some(KeyContext::Popup)
2488 );
2489 assert_eq!(KeyContext::from_when_clause("help"), None);
2490 assert_eq!(KeyContext::from_when_clause(" help "), None); assert_eq!(KeyContext::from_when_clause("unknown"), None);
2492 assert_eq!(KeyContext::from_when_clause(""), None);
2493 }
2494
2495 #[test]
2496 fn test_key_context_to_when_clause() {
2497 assert_eq!(KeyContext::Normal.to_when_clause(), "normal");
2498 assert_eq!(KeyContext::Prompt.to_when_clause(), "prompt");
2499 assert_eq!(KeyContext::Popup.to_when_clause(), "popup");
2500 }
2501
2502 #[test]
2503 fn test_context_specific_bindings() {
2504 let config = Config::default();
2505 let resolver = KeybindingResolver::new(&config);
2506
2507 let enter_event = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
2509 assert_eq!(
2510 resolver.resolve(&enter_event, KeyContext::Prompt),
2511 Action::PromptConfirm
2512 );
2513 assert_eq!(
2514 resolver.resolve(&enter_event, KeyContext::Normal),
2515 Action::InsertNewline
2516 );
2517
2518 let up_event = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
2520 assert_eq!(
2521 resolver.resolve(&up_event, KeyContext::Popup),
2522 Action::PopupSelectPrev
2523 );
2524 assert_eq!(
2525 resolver.resolve(&up_event, KeyContext::Normal),
2526 Action::MoveUp
2527 );
2528 }
2529
2530 #[test]
2531 fn test_context_fallback_to_normal() {
2532 let config = Config::default();
2533 let resolver = KeybindingResolver::new(&config);
2534
2535 let save_event = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL);
2537 assert_eq!(
2538 resolver.resolve(&save_event, KeyContext::Normal),
2539 Action::Save
2540 );
2541 assert_eq!(
2542 resolver.resolve(&save_event, KeyContext::Popup),
2543 Action::Save
2544 );
2545 }
2547
2548 #[test]
2549 fn test_context_priority_resolution() {
2550 use crate::config::Keybinding;
2551
2552 let mut config = Config::default();
2554 config.keybindings.push(Keybinding {
2555 key: "esc".to_string(),
2556 modifiers: vec![],
2557 keys: vec![],
2558 action: "quit".to_string(), args: HashMap::new(),
2560 when: Some("popup".to_string()),
2561 });
2562
2563 let resolver = KeybindingResolver::new(&config);
2564 let esc_event = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
2565
2566 assert_eq!(
2568 resolver.resolve(&esc_event, KeyContext::Popup),
2569 Action::Quit
2570 );
2571
2572 assert_eq!(
2574 resolver.resolve(&esc_event, KeyContext::Normal),
2575 Action::RemoveSecondaryCursors
2576 );
2577 }
2578
2579 #[test]
2580 fn test_character_input_in_contexts() {
2581 let config = Config::default();
2582 let resolver = KeybindingResolver::new(&config);
2583
2584 let char_event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2585
2586 assert_eq!(
2588 resolver.resolve(&char_event, KeyContext::Normal),
2589 Action::InsertChar('a')
2590 );
2591 assert_eq!(
2592 resolver.resolve(&char_event, KeyContext::Prompt),
2593 Action::InsertChar('a')
2594 );
2595
2596 assert_eq!(
2598 resolver.resolve(&char_event, KeyContext::Popup),
2599 Action::None
2600 );
2601 }
2602
2603 #[test]
2604 fn test_custom_keybinding_loading() {
2605 use crate::config::Keybinding;
2606
2607 let mut config = Config::default();
2608
2609 config.keybindings.push(Keybinding {
2611 key: "f".to_string(),
2612 modifiers: vec!["ctrl".to_string()],
2613 keys: vec![],
2614 action: "command_palette".to_string(),
2615 args: HashMap::new(),
2616 when: None, });
2618
2619 let resolver = KeybindingResolver::new(&config);
2620
2621 let ctrl_f = KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL);
2623 assert_eq!(
2624 resolver.resolve(&ctrl_f, KeyContext::Normal),
2625 Action::CommandPalette
2626 );
2627
2628 let ctrl_k = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
2630 assert_eq!(
2631 resolver.resolve(&ctrl_k, KeyContext::Prompt),
2632 Action::PromptDeleteToLineEnd
2633 );
2634 assert_eq!(
2635 resolver.resolve(&ctrl_k, KeyContext::Normal),
2636 Action::DeleteToLineEnd
2637 );
2638 }
2639
2640 #[test]
2641 fn test_all_context_default_bindings_exist() {
2642 let config = Config::default();
2643 let resolver = KeybindingResolver::new(&config);
2644
2645 assert!(resolver.default_bindings.contains_key(&KeyContext::Normal));
2647 assert!(resolver.default_bindings.contains_key(&KeyContext::Prompt));
2648 assert!(resolver.default_bindings.contains_key(&KeyContext::Popup));
2649 assert!(resolver
2650 .default_bindings
2651 .contains_key(&KeyContext::FileExplorer));
2652 assert!(resolver.default_bindings.contains_key(&KeyContext::Menu));
2653
2654 assert!(!resolver.default_bindings[&KeyContext::Normal].is_empty());
2656 assert!(!resolver.default_bindings[&KeyContext::Prompt].is_empty());
2657 assert!(!resolver.default_bindings[&KeyContext::Popup].is_empty());
2658 assert!(!resolver.default_bindings[&KeyContext::FileExplorer].is_empty());
2659 assert!(!resolver.default_bindings[&KeyContext::Menu].is_empty());
2660 }
2661
2662 #[test]
2666 fn test_all_builtin_keymaps_have_valid_action_names() {
2667 let known_actions: std::collections::HashSet<String> =
2668 Action::all_action_names().into_iter().collect();
2669
2670 let config = Config::default();
2671
2672 for map_name in crate::config::KeybindingMapName::BUILTIN_OPTIONS {
2673 let bindings = config.resolve_keymap(map_name);
2674 for binding in &bindings {
2675 assert!(
2676 known_actions.contains(&binding.action),
2677 "Keymap '{}' contains unknown action '{}' (key: '{}', when: {:?}). \
2678 This will be treated as a plugin action at runtime. \
2679 Check for typos in the keymap JSON file.",
2680 map_name,
2681 binding.action,
2682 binding.key,
2683 binding.when,
2684 );
2685 }
2686 }
2687 }
2688
2689 #[test]
2690 fn test_resolve_determinism() {
2691 let config = Config::default();
2693 let resolver = KeybindingResolver::new(&config);
2694
2695 let test_cases = vec![
2696 (KeyCode::Left, KeyModifiers::empty(), KeyContext::Normal),
2697 (
2698 KeyCode::Esc,
2699 KeyModifiers::empty(),
2700 KeyContext::FileExplorer,
2701 ),
2702 (KeyCode::Enter, KeyModifiers::empty(), KeyContext::Prompt),
2703 (KeyCode::Down, KeyModifiers::empty(), KeyContext::Popup),
2704 ];
2705
2706 for (key_code, modifiers, context) in test_cases {
2707 let event = KeyEvent::new(key_code, modifiers);
2708 let action1 = resolver.resolve(&event, context.clone());
2709 let action2 = resolver.resolve(&event, context.clone());
2710 let action3 = resolver.resolve(&event, context);
2711
2712 assert_eq!(action1, action2, "Resolve should be deterministic");
2713 assert_eq!(action2, action3, "Resolve should be deterministic");
2714 }
2715 }
2716
2717 #[test]
2718 fn test_modifier_combinations() {
2719 let config = Config::default();
2720 let resolver = KeybindingResolver::new(&config);
2721
2722 let char_s = KeyCode::Char('s');
2724
2725 let no_mod = KeyEvent::new(char_s, KeyModifiers::empty());
2726 let ctrl = KeyEvent::new(char_s, KeyModifiers::CONTROL);
2727 let shift = KeyEvent::new(char_s, KeyModifiers::SHIFT);
2728 let ctrl_shift = KeyEvent::new(char_s, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
2729
2730 let action_no_mod = resolver.resolve(&no_mod, KeyContext::Normal);
2731 let action_ctrl = resolver.resolve(&ctrl, KeyContext::Normal);
2732 let action_shift = resolver.resolve(&shift, KeyContext::Normal);
2733 let action_ctrl_shift = resolver.resolve(&ctrl_shift, KeyContext::Normal);
2734
2735 assert_eq!(action_no_mod, Action::InsertChar('s'));
2737 assert_eq!(action_ctrl, Action::Save);
2738 assert_eq!(action_shift, Action::InsertChar('s')); assert_eq!(action_ctrl_shift, Action::None);
2741 }
2742
2743 #[test]
2744 fn test_scroll_keybindings() {
2745 let config = Config::default();
2746 let resolver = KeybindingResolver::new(&config);
2747
2748 let ctrl_up = KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL);
2750 assert_eq!(
2751 resolver.resolve(&ctrl_up, KeyContext::Normal),
2752 Action::ScrollUp,
2753 "Ctrl+Up should resolve to ScrollUp"
2754 );
2755
2756 let ctrl_down = KeyEvent::new(KeyCode::Down, KeyModifiers::CONTROL);
2758 assert_eq!(
2759 resolver.resolve(&ctrl_down, KeyContext::Normal),
2760 Action::ScrollDown,
2761 "Ctrl+Down should resolve to ScrollDown"
2762 );
2763 }
2764
2765 #[test]
2766 fn test_lsp_completion_keybinding() {
2767 let config = Config::default();
2768 let resolver = KeybindingResolver::new(&config);
2769
2770 let ctrl_space = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::CONTROL);
2772 assert_eq!(
2773 resolver.resolve(&ctrl_space, KeyContext::Normal),
2774 Action::LspCompletion,
2775 "Ctrl+Space should resolve to LspCompletion"
2776 );
2777 }
2778
2779 #[test]
2780 fn test_terminal_key_equivalents() {
2781 let ctrl = KeyModifiers::CONTROL;
2783
2784 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2786 assert_eq!(slash_equivs, vec![(KeyCode::Char('7'), ctrl)]);
2787
2788 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2789 assert_eq!(seven_equivs, vec![(KeyCode::Char('/'), ctrl)]);
2790
2791 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2793 assert_eq!(backspace_equivs, vec![(KeyCode::Char('h'), ctrl)]);
2794
2795 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2796 assert_eq!(h_equivs, vec![(KeyCode::Backspace, ctrl)]);
2797
2798 let a_equivs = terminal_key_equivalents(KeyCode::Char('a'), ctrl);
2800 assert!(a_equivs.is_empty());
2801
2802 let slash_no_ctrl = terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty());
2804 assert!(slash_no_ctrl.is_empty());
2805 }
2806
2807 #[test]
2808 fn test_terminal_key_equivalents_auto_binding() {
2809 let config = Config::default();
2810 let resolver = KeybindingResolver::new(&config);
2811
2812 let ctrl_slash = KeyEvent::new(KeyCode::Char('/'), KeyModifiers::CONTROL);
2814 let action_slash = resolver.resolve(&ctrl_slash, KeyContext::Normal);
2815 assert_eq!(
2816 action_slash,
2817 Action::ToggleComment,
2818 "Ctrl+/ should resolve to ToggleComment"
2819 );
2820
2821 let ctrl_7 = KeyEvent::new(KeyCode::Char('7'), KeyModifiers::CONTROL);
2823 let action_7 = resolver.resolve(&ctrl_7, KeyContext::Normal);
2824 assert_eq!(
2825 action_7,
2826 Action::ToggleComment,
2827 "Ctrl+7 should resolve to ToggleComment (terminal equivalent of Ctrl+/)"
2828 );
2829 }
2830
2831 #[test]
2832 fn test_terminal_key_equivalents_normalization() {
2833 let ctrl = KeyModifiers::CONTROL;
2838
2839 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2842 assert_eq!(
2843 slash_equivs,
2844 vec![(KeyCode::Char('7'), ctrl)],
2845 "Ctrl+/ should map to Ctrl+7"
2846 );
2847 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2848 assert_eq!(
2849 seven_equivs,
2850 vec![(KeyCode::Char('/'), ctrl)],
2851 "Ctrl+7 should map back to Ctrl+/"
2852 );
2853
2854 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2857 assert_eq!(
2858 backspace_equivs,
2859 vec![(KeyCode::Char('h'), ctrl)],
2860 "Ctrl+Backspace should map to Ctrl+H"
2861 );
2862 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2863 assert_eq!(
2864 h_equivs,
2865 vec![(KeyCode::Backspace, ctrl)],
2866 "Ctrl+H should map back to Ctrl+Backspace"
2867 );
2868
2869 let space_equivs = terminal_key_equivalents(KeyCode::Char(' '), ctrl);
2872 assert_eq!(
2873 space_equivs,
2874 vec![(KeyCode::Char('@'), ctrl)],
2875 "Ctrl+Space should map to Ctrl+@"
2876 );
2877 let at_equivs = terminal_key_equivalents(KeyCode::Char('@'), ctrl);
2878 assert_eq!(
2879 at_equivs,
2880 vec![(KeyCode::Char(' '), ctrl)],
2881 "Ctrl+@ should map back to Ctrl+Space"
2882 );
2883
2884 let minus_equivs = terminal_key_equivalents(KeyCode::Char('-'), ctrl);
2887 assert_eq!(
2888 minus_equivs,
2889 vec![(KeyCode::Char('_'), ctrl)],
2890 "Ctrl+- should map to Ctrl+_"
2891 );
2892 let underscore_equivs = terminal_key_equivalents(KeyCode::Char('_'), ctrl);
2893 assert_eq!(
2894 underscore_equivs,
2895 vec![(KeyCode::Char('-'), ctrl)],
2896 "Ctrl+_ should map back to Ctrl+-"
2897 );
2898
2899 assert!(
2901 terminal_key_equivalents(KeyCode::Char('a'), ctrl).is_empty(),
2902 "Ctrl+A should have no terminal equivalents"
2903 );
2904 assert!(
2905 terminal_key_equivalents(KeyCode::Char('z'), ctrl).is_empty(),
2906 "Ctrl+Z should have no terminal equivalents"
2907 );
2908 assert!(
2909 terminal_key_equivalents(KeyCode::Enter, ctrl).is_empty(),
2910 "Ctrl+Enter should have no terminal equivalents"
2911 );
2912
2913 assert!(
2915 terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty()).is_empty(),
2916 "/ without Ctrl should have no equivalents"
2917 );
2918 assert!(
2919 terminal_key_equivalents(KeyCode::Char('7'), KeyModifiers::SHIFT).is_empty(),
2920 "Shift+7 should have no equivalents"
2921 );
2922 assert!(
2923 terminal_key_equivalents(KeyCode::Char('h'), KeyModifiers::ALT).is_empty(),
2924 "Alt+H should have no equivalents"
2925 );
2926
2927 let ctrl_shift = KeyModifiers::CONTROL | KeyModifiers::SHIFT;
2930 let ctrl_shift_h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl_shift);
2931 assert!(
2932 ctrl_shift_h_equivs.is_empty(),
2933 "Ctrl+Shift+H should NOT map to Ctrl+Shift+Backspace"
2934 );
2935 }
2936
2937 #[test]
2938 fn test_no_duplicate_keybindings_in_keymaps() {
2939 use std::collections::HashMap;
2942
2943 let keymaps: &[(&str, &str)] = &[
2944 ("default", include_str!("../../keymaps/default.json")),
2945 ("macos", include_str!("../../keymaps/macos.json")),
2946 ];
2947
2948 for (keymap_name, json_content) in keymaps {
2949 let keymap: crate::config::KeymapConfig = serde_json::from_str(json_content)
2950 .unwrap_or_else(|e| panic!("Failed to parse keymap '{}': {}", keymap_name, e));
2951
2952 let mut seen: HashMap<(String, Vec<String>, String), String> = HashMap::new();
2954 let mut duplicates: Vec<String> = Vec::new();
2955
2956 for binding in &keymap.bindings {
2957 let when = binding.when.clone().unwrap_or_default();
2958 let key_id = (binding.key.clone(), binding.modifiers.clone(), when.clone());
2959
2960 if let Some(existing_action) = seen.get(&key_id) {
2961 duplicates.push(format!(
2962 "Duplicate in '{}': key='{}', modifiers={:?}, when='{}' -> '{}' vs '{}'",
2963 keymap_name,
2964 binding.key,
2965 binding.modifiers,
2966 when,
2967 existing_action,
2968 binding.action
2969 ));
2970 } else {
2971 seen.insert(key_id, binding.action.clone());
2972 }
2973 }
2974
2975 assert!(
2976 duplicates.is_empty(),
2977 "Found duplicate keybindings:\n{}",
2978 duplicates.join("\n")
2979 );
2980 }
2981 }
2982}