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) {
20 if code == KeyCode::BackTab {
21 return (code, modifiers.difference(KeyModifiers::SHIFT));
22 }
23 if code == KeyCode::Backspace {
24 return (code, modifiers.difference(KeyModifiers::SHIFT));
25 }
26 if let KeyCode::Char(c) = code {
27 if c.is_ascii_uppercase() {
28 return (KeyCode::Char(c.to_ascii_lowercase()), modifiers);
29 }
30 }
31 (code, modifiers)
32}
33
34static FORCE_LINUX_KEYBINDINGS: AtomicBool = AtomicBool::new(false);
37
38pub fn set_force_linux_keybindings(force: bool) {
41 FORCE_LINUX_KEYBINDINGS.store(force, Ordering::SeqCst);
42}
43
44fn use_macos_symbols() -> bool {
46 if FORCE_LINUX_KEYBINDINGS.load(Ordering::SeqCst) {
47 return false;
48 }
49 cfg!(target_os = "macos")
50}
51
52fn is_text_input_modifier(modifiers: KeyModifiers) -> bool {
63 if modifiers.is_empty() || modifiers == KeyModifiers::SHIFT {
64 return true;
65 }
66
67 #[cfg(windows)]
71 if modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT)
72 || modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT)
73 {
74 return true;
75 }
76
77 false
78}
79
80pub fn format_keybinding(keycode: &KeyCode, modifiers: &KeyModifiers) -> String {
84 let mut result = String::new();
85
86 let (ctrl_label, alt_label, shift_label, super_label) = if use_macos_symbols() {
88 ("⌃", "⌥", "⇧", "⌘")
89 } else {
90 ("Ctrl", "Alt", "Shift", "Super")
91 };
92
93 let use_plus = !use_macos_symbols();
94
95 if modifiers.contains(KeyModifiers::SUPER) {
96 result.push_str(super_label);
97 if use_plus {
98 result.push('+');
99 }
100 }
101 if modifiers.contains(KeyModifiers::CONTROL) {
102 result.push_str(ctrl_label);
103 if use_plus {
104 result.push('+');
105 }
106 }
107 if modifiers.contains(KeyModifiers::ALT) {
108 result.push_str(alt_label);
109 if use_plus {
110 result.push('+');
111 }
112 }
113 if modifiers.contains(KeyModifiers::SHIFT) {
114 result.push_str(shift_label);
115 if use_plus {
116 result.push('+');
117 }
118 }
119
120 match keycode {
121 KeyCode::Enter => result.push_str("Enter"),
122 KeyCode::Backspace => result.push_str("Backspace"),
123 KeyCode::Delete => result.push_str("Del"),
124 KeyCode::Tab => result.push_str("Tab"),
125 KeyCode::Esc => result.push_str("Esc"),
126 KeyCode::Left => result.push('←'),
127 KeyCode::Right => result.push('→'),
128 KeyCode::Up => result.push('↑'),
129 KeyCode::Down => result.push('↓'),
130 KeyCode::Home => result.push_str("Home"),
131 KeyCode::End => result.push_str("End"),
132 KeyCode::PageUp => result.push_str("PgUp"),
133 KeyCode::PageDown => result.push_str("PgDn"),
134 KeyCode::Char(' ') => result.push_str("Space"),
135 KeyCode::Char(c) => result.push_str(&c.to_uppercase().to_string()),
136 KeyCode::F(n) => result.push_str(&format!("F{}", n)),
137 _ => return String::new(),
138 }
139
140 result
141}
142
143fn keybinding_priority_score(key: &KeyCode) -> u32 {
147 match key {
148 KeyCode::Char('@') => 100, KeyCode::Char('7') => 100, KeyCode::Char('_') => 100, _ => 0,
155 }
156}
157
158pub fn terminal_key_equivalents(
169 key: KeyCode,
170 modifiers: KeyModifiers,
171) -> Vec<(KeyCode, KeyModifiers)> {
172 let mut equivalents = Vec::new();
173
174 if modifiers.contains(KeyModifiers::CONTROL) {
176 let base_modifiers = modifiers; match key {
179 KeyCode::Char('/') => {
181 equivalents.push((KeyCode::Char('7'), base_modifiers));
182 }
183 KeyCode::Char('7') => {
184 equivalents.push((KeyCode::Char('/'), base_modifiers));
185 }
186
187 KeyCode::Backspace => {
189 equivalents.push((KeyCode::Char('h'), base_modifiers));
190 }
191 KeyCode::Char('h') if modifiers == KeyModifiers::CONTROL => {
192 equivalents.push((KeyCode::Backspace, base_modifiers));
194 }
195
196 KeyCode::Char(' ') => {
198 equivalents.push((KeyCode::Char('@'), base_modifiers));
199 }
200 KeyCode::Char('@') => {
201 equivalents.push((KeyCode::Char(' '), base_modifiers));
202 }
203
204 KeyCode::Char('-') => {
206 equivalents.push((KeyCode::Char('_'), base_modifiers));
207 }
208 KeyCode::Char('_') => {
209 equivalents.push((KeyCode::Char('-'), base_modifiers));
210 }
211
212 _ => {}
213 }
214 }
215
216 equivalents
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Hash)]
221pub enum KeyContext {
222 Global,
224 Normal,
226 Prompt,
228 Popup,
230 Completion,
234 FileExplorer,
236 Menu,
238 Terminal,
240 Settings,
242 CompositeBuffer,
244 Mode(String),
246}
247
248impl KeyContext {
249 pub fn allows_normal_fallthrough(&self) -> bool {
256 matches!(self, Self::CompositeBuffer)
257 }
258
259 pub fn allows_text_input(&self) -> bool {
261 matches!(self, Self::Normal | Self::Prompt | Self::FileExplorer)
262 }
263
264 pub fn from_when_clause(when: &str) -> Option<Self> {
266 let trimmed = when.trim();
267 if let Some(mode_name) = trimmed.strip_prefix("mode:") {
268 return Some(Self::Mode(mode_name.to_string()));
269 }
270 Some(match trimmed {
271 "global" => Self::Global,
272 "prompt" => Self::Prompt,
273 "popup" => Self::Popup,
274 "completion" => Self::Completion,
275 "fileExplorer" | "file_explorer" => Self::FileExplorer,
276 "normal" => Self::Normal,
277 "menu" => Self::Menu,
278 "terminal" => Self::Terminal,
279 "settings" => Self::Settings,
280 "compositeBuffer" | "composite_buffer" => Self::CompositeBuffer,
281 _ => return None,
282 })
283 }
284
285 pub fn to_when_clause(&self) -> String {
287 match self {
288 Self::Global => "global".to_string(),
289 Self::Normal => "normal".to_string(),
290 Self::Prompt => "prompt".to_string(),
291 Self::Popup => "popup".to_string(),
292 Self::Completion => "completion".to_string(),
293 Self::FileExplorer => "fileExplorer".to_string(),
294 Self::Menu => "menu".to_string(),
295 Self::Terminal => "terminal".to_string(),
296 Self::Settings => "settings".to_string(),
297 Self::CompositeBuffer => "compositeBuffer".to_string(),
298 Self::Mode(name) => format!("mode:{}", name),
299 }
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
305pub enum Action {
306 InsertChar(char),
308 InsertNewline,
309 InsertTab,
310
311 MoveLeft,
313 MoveRight,
314 MoveUp,
315 MoveDown,
316 MoveWordLeft,
317 MoveWordRight,
318 MoveWordEnd, ViMoveWordEnd, MoveLeftInLine, MoveRightInLine, MoveLineStart,
323 MoveLineEnd,
324 MoveLineUp,
325 MoveLineDown,
326 MovePageUp,
327 MovePageDown,
328 MoveDocumentStart,
329 MoveDocumentEnd,
330
331 SelectLeft,
333 SelectRight,
334 SelectUp,
335 SelectDown,
336 SelectToParagraphUp, SelectToParagraphDown, SelectWordLeft,
339 SelectWordRight,
340 SelectWordEnd, ViSelectWordEnd, SelectLineStart,
343 SelectLineEnd,
344 SelectDocumentStart,
345 SelectDocumentEnd,
346 SelectPageUp,
347 SelectPageDown,
348 SelectAll,
349 SelectWord,
350 SelectLine,
351 ExpandSelection,
352
353 BlockSelectLeft,
355 BlockSelectRight,
356 BlockSelectUp,
357 BlockSelectDown,
358
359 DeleteBackward,
361 DeleteForward,
362 DeleteWordBackward,
363 DeleteWordForward,
364 DeleteLine,
365 DeleteToLineEnd,
366 DeleteToLineStart,
367 DeleteViWordEnd, TransposeChars,
369 OpenLine,
370 DuplicateLine,
371
372 Recenter,
374
375 SetMark,
377
378 Copy,
380 CopyWithTheme(String),
381 Cut,
382 Paste,
383 CopyFilePath,
385 CopyRelativeFilePath,
388
389 YankWordForward,
391 YankWordBackward,
392 YankToLineEnd,
393 YankToLineStart,
394 YankViWordEnd, AddCursorAbove,
398 AddCursorBelow,
399 AddCursorNextMatch,
400 RemoveSecondaryCursors,
401
402 Save,
404 SaveAs,
405 Open,
406 SwitchProject,
407 New,
408 Close,
409 CloseTab,
410 Quit,
411 ForceQuit,
412 Detach,
413 Revert,
414 ToggleAutoRevert,
415 FormatBuffer,
416 TrimTrailingWhitespace,
417 EnsureFinalNewline,
418
419 GotoLine,
421 ScanLineIndex,
422 GoToMatchingBracket,
423 JumpToNextError,
424 JumpToPreviousError,
425
426 SmartHome,
428 DedentSelection,
429 ToggleComment,
430 DabbrevExpand,
431 ToggleFold,
432
433 SetBookmark(char),
435 JumpToBookmark(char),
436 ClearBookmark(char),
437 ListBookmarks,
438
439 ToggleSearchCaseSensitive,
441 ToggleSearchWholeWord,
442 ToggleSearchRegex,
443 ToggleSearchConfirmEach,
444
445 StartMacroRecording,
447 StopMacroRecording,
448 PlayMacro(char),
449 ToggleMacroRecording(char),
450 ShowMacro(char),
451 ListMacros,
452 PromptRecordMacro,
453 PromptPlayMacro,
454 PlayLastMacro,
455
456 PromptSetBookmark,
458 PromptJumpToBookmark,
459
460 Undo,
462 Redo,
463
464 ScrollUp,
466 ScrollDown,
467 ShowHelp,
468 ShowKeyboardShortcuts,
469 ShowWarnings,
470 ShowStatusLog,
471 ShowLspStatus,
472 ShowRemoteIndicatorMenu,
473 ClearWarnings,
474 CommandPalette, QuickOpen,
477 QuickOpenBuffers,
479 QuickOpenFiles,
481 OpenLiveGrep,
483 ResumeLiveGrep,
485 LiveGrepExportQuickfix,
489 ToggleUtilityDock,
493 OpenTerminalInDock,
496 CycleLiveGrepProvider,
501 ToggleLineWrap,
502 ToggleCurrentLineHighlight,
503 ToggleReadOnly,
504 TogglePageView,
505 SetPageWidth,
506 InspectThemeAtCursor,
507 SelectTheme,
508 SelectKeybindingMap,
509 SelectCursorStyle,
510 SelectLocale,
511
512 NextBuffer,
514 PrevBuffer,
515 SwitchToPreviousTab,
516 SwitchToTabByName,
517
518 ScrollTabsLeft,
520 ScrollTabsRight,
521
522 NavigateBack,
524 NavigateForward,
525
526 SplitHorizontal,
528 SplitVertical,
529 CloseSplit,
530 NextSplit,
531 PrevSplit,
532 IncreaseSplitSize,
533 DecreaseSplitSize,
534 ToggleMaximizeSplit,
535
536 PromptConfirm,
538 PromptConfirmWithText(String),
540 PromptCancel,
541 PromptBackspace,
542 PromptDelete,
543 PromptMoveLeft,
544 PromptMoveRight,
545 PromptMoveStart,
546 PromptMoveEnd,
547 PromptSelectPrev,
548 PromptSelectNext,
549 PromptPageUp,
550 PromptPageDown,
551 PromptAcceptSuggestion,
552 PromptMoveWordLeft,
553 PromptMoveWordRight,
554 PromptDeleteWordForward,
556 PromptDeleteWordBackward,
557 PromptDeleteToLineEnd,
558 PromptCopy,
559 PromptCut,
560 PromptPaste,
561 PromptMoveLeftSelecting,
563 PromptMoveRightSelecting,
564 PromptMoveHomeSelecting,
565 PromptMoveEndSelecting,
566 PromptSelectWordLeft,
567 PromptSelectWordRight,
568 PromptSelectAll,
569
570 FileBrowserToggleHidden,
572 FileBrowserToggleDetectEncoding,
573
574 PopupSelectNext,
576 PopupSelectPrev,
577 PopupPageUp,
578 PopupPageDown,
579 PopupConfirm,
580 PopupCancel,
581 PopupFocus,
586
587 CompletionAccept,
589 CompletionDismiss,
590
591 ToggleFileExplorer,
593 ToggleMenuBar,
595 ToggleTabBar,
597 ToggleStatusBar,
599 TogglePromptLine,
601 ToggleVerticalScrollbar,
603 ToggleHorizontalScrollbar,
604 FocusFileExplorer,
605 FocusEditor,
606 FileExplorerUp,
607 FileExplorerDown,
608 FileExplorerPageUp,
609 FileExplorerPageDown,
610 FileExplorerExpand,
611 FileExplorerCollapse,
612 FileExplorerOpen,
613 FileExplorerRefresh,
614 FileExplorerNewFile,
615 FileExplorerNewDirectory,
616 FileExplorerDelete,
617 FileExplorerRename,
618 FileExplorerToggleHidden,
619 FileExplorerToggleGitignored,
620 FileExplorerSearchClear,
621 FileExplorerSearchBackspace,
622 FileExplorerCopy,
623 FileExplorerCut,
624 FileExplorerPaste,
625 FileExplorerDuplicate,
626 FileExplorerCopyFullPath,
627 FileExplorerCopyRelativePath,
628 FileExplorerExtendSelectionUp,
629 FileExplorerExtendSelectionDown,
630 FileExplorerToggleSelect,
631 FileExplorerSelectAll,
632
633 LspCompletion,
635 LspGotoDefinition,
636 LspReferences,
637 LspRename,
638 LspHover,
639 LspSignatureHelp,
640 LspCodeActions,
641 LspRestart,
642 LspStop,
643 LspToggleForBuffer,
644 ToggleInlayHints,
645 ToggleMouseHover,
646
647 ToggleLineNumbers,
649 ToggleScrollSync,
650 ToggleMouseCapture,
651 ToggleDebugHighlights, SetBackground,
653 SetBackgroundBlend,
654
655 SetTabSize,
657 SetLineEnding,
658 SetEncoding,
659 ReloadWithEncoding,
660 SetLanguage,
661 ToggleIndentationStyle,
662 ToggleTabIndicators,
663 ToggleWhitespaceIndicators,
664 ResetBufferSettings,
665 AddRuler,
666 RemoveRuler,
667
668 DumpConfig,
670
671 RedrawScreen,
673
674 Search,
676 FindInSelection,
677 FindNext,
678 FindPrevious,
679 FindSelectionNext, FindSelectionPrevious, Replace,
682 QueryReplace, MenuActivate, MenuClose, MenuLeft, MenuRight, MenuUp, MenuDown, MenuExecute, MenuOpen(String), SwitchKeybindingMap(String), PluginAction(String),
699
700 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, SuspendProcess, OpenKeybindingEditor, LoadPluginFromBuffer, InitReload, InitEdit, InitCheck, CompositeNextHunk, CompositePrevHunk, None,
757}
758
759macro_rules! define_action_str_mapping {
772 (
773 $args_name:ident;
774 simple { $($s_name:literal => $s_variant:ident),* $(,)? }
775 alias { $($a_name:literal => $a_variant:ident),* $(,)? }
776 with_char { $($c_name:literal => $c_variant:ident),* $(,)? }
777 custom { $($x_name:literal => $x_variant:ident : $x_body:expr),* $(,)? }
778 ) => {
779 pub fn from_str(s: &str, $args_name: &HashMap<String, serde_json::Value>) -> Option<Self> {
781 Some(match s {
782 $($s_name => Self::$s_variant,)*
783 $($a_name => Self::$a_variant,)*
784 $($c_name => return Self::with_char($args_name, Self::$c_variant),)*
785 $($x_name => $x_body,)*
786 _ => Self::PluginAction(s.to_string()),
789 })
790 }
791
792 pub fn to_action_str(&self) -> String {
795 match self {
796 $(Self::$s_variant => $s_name.to_string(),)*
797 $(Self::$c_variant(_) => $c_name.to_string(),)*
798 $(Self::$x_variant(_) => $x_name.to_string(),)*
799 Self::PluginAction(name) => name.clone(),
800 }
801 }
802
803 pub fn all_action_names() -> Vec<String> {
806 let mut names = vec![
807 $($s_name.to_string(),)*
808 $($a_name.to_string(),)*
809 $($c_name.to_string(),)*
810 $($x_name.to_string(),)*
811 ];
812 names.sort();
813 names
814 }
815 };
816}
817
818impl Action {
819 fn with_char(
820 args: &HashMap<String, serde_json::Value>,
821 make_action: impl FnOnce(char) -> Self,
822 ) -> Option<Self> {
823 if let Some(serde_json::Value::String(value)) = args.get("char") {
824 value.chars().next().map(make_action)
825 } else {
826 None
827 }
828 }
829
830 define_action_str_mapping! {
831 args;
832 simple {
833 "insert_newline" => InsertNewline,
834 "insert_tab" => InsertTab,
835
836 "move_left" => MoveLeft,
837 "move_right" => MoveRight,
838 "move_up" => MoveUp,
839 "move_down" => MoveDown,
840 "move_word_left" => MoveWordLeft,
841 "move_word_right" => MoveWordRight,
842 "move_word_end" => MoveWordEnd,
843 "vi_move_word_end" => ViMoveWordEnd,
844 "move_left_in_line" => MoveLeftInLine,
845 "move_right_in_line" => MoveRightInLine,
846 "move_line_start" => MoveLineStart,
847 "move_line_end" => MoveLineEnd,
848 "move_line_up" => MoveLineUp,
849 "move_line_down" => MoveLineDown,
850 "move_page_up" => MovePageUp,
851 "move_page_down" => MovePageDown,
852 "move_document_start" => MoveDocumentStart,
853 "move_document_end" => MoveDocumentEnd,
854
855 "select_left" => SelectLeft,
856 "select_right" => SelectRight,
857 "select_up" => SelectUp,
858 "select_down" => SelectDown,
859 "select_to_paragraph_up" => SelectToParagraphUp,
860 "select_to_paragraph_down" => SelectToParagraphDown,
861 "select_word_left" => SelectWordLeft,
862 "select_word_right" => SelectWordRight,
863 "select_word_end" => SelectWordEnd,
864 "vi_select_word_end" => ViSelectWordEnd,
865 "select_line_start" => SelectLineStart,
866 "select_line_end" => SelectLineEnd,
867 "select_document_start" => SelectDocumentStart,
868 "select_document_end" => SelectDocumentEnd,
869 "select_page_up" => SelectPageUp,
870 "select_page_down" => SelectPageDown,
871 "select_all" => SelectAll,
872 "select_word" => SelectWord,
873 "select_line" => SelectLine,
874 "expand_selection" => ExpandSelection,
875
876 "block_select_left" => BlockSelectLeft,
877 "block_select_right" => BlockSelectRight,
878 "block_select_up" => BlockSelectUp,
879 "block_select_down" => BlockSelectDown,
880
881 "delete_backward" => DeleteBackward,
882 "delete_forward" => DeleteForward,
883 "delete_word_backward" => DeleteWordBackward,
884 "delete_word_forward" => DeleteWordForward,
885 "delete_line" => DeleteLine,
886 "delete_to_line_end" => DeleteToLineEnd,
887 "delete_to_line_start" => DeleteToLineStart,
888 "delete_vi_word_end" => DeleteViWordEnd,
889 "transpose_chars" => TransposeChars,
890 "open_line" => OpenLine,
891 "duplicate_line" => DuplicateLine,
892 "recenter" => Recenter,
893 "set_mark" => SetMark,
894
895 "copy" => Copy,
896 "cut" => Cut,
897 "paste" => Paste,
898 "copy_file_path" => CopyFilePath,
899 "copy_relative_file_path" => CopyRelativeFilePath,
900
901 "yank_word_forward" => YankWordForward,
902 "yank_word_backward" => YankWordBackward,
903 "yank_to_line_end" => YankToLineEnd,
904 "yank_to_line_start" => YankToLineStart,
905 "yank_vi_word_end" => YankViWordEnd,
906
907 "add_cursor_above" => AddCursorAbove,
908 "add_cursor_below" => AddCursorBelow,
909 "add_cursor_next_match" => AddCursorNextMatch,
910 "remove_secondary_cursors" => RemoveSecondaryCursors,
911
912 "save" => Save,
913 "save_as" => SaveAs,
914 "open" => Open,
915 "switch_project" => SwitchProject,
916 "new" => New,
917 "close" => Close,
918 "close_tab" => CloseTab,
919 "quit" => Quit,
920 "force_quit" => ForceQuit,
921 "detach" => Detach,
922 "revert" => Revert,
923 "toggle_auto_revert" => ToggleAutoRevert,
924 "format_buffer" => FormatBuffer,
925 "trim_trailing_whitespace" => TrimTrailingWhitespace,
926 "ensure_final_newline" => EnsureFinalNewline,
927 "goto_line" => GotoLine,
928 "scan_line_index" => ScanLineIndex,
929 "goto_matching_bracket" => GoToMatchingBracket,
930 "jump_to_next_error" => JumpToNextError,
931 "jump_to_previous_error" => JumpToPreviousError,
932
933 "smart_home" => SmartHome,
934 "dedent_selection" => DedentSelection,
935 "toggle_comment" => ToggleComment,
936 "dabbrev_expand" => DabbrevExpand,
937 "toggle_fold" => ToggleFold,
938
939 "list_bookmarks" => ListBookmarks,
940
941 "toggle_search_case_sensitive" => ToggleSearchCaseSensitive,
942 "toggle_search_whole_word" => ToggleSearchWholeWord,
943 "toggle_search_regex" => ToggleSearchRegex,
944 "toggle_search_confirm_each" => ToggleSearchConfirmEach,
945
946 "start_macro_recording" => StartMacroRecording,
947 "stop_macro_recording" => StopMacroRecording,
948
949 "list_macros" => ListMacros,
950 "prompt_record_macro" => PromptRecordMacro,
951 "prompt_play_macro" => PromptPlayMacro,
952 "play_last_macro" => PlayLastMacro,
953 "prompt_set_bookmark" => PromptSetBookmark,
954 "prompt_jump_to_bookmark" => PromptJumpToBookmark,
955
956 "undo" => Undo,
957 "redo" => Redo,
958
959 "scroll_up" => ScrollUp,
960 "scroll_down" => ScrollDown,
961 "show_help" => ShowHelp,
962 "keyboard_shortcuts" => ShowKeyboardShortcuts,
963 "show_warnings" => ShowWarnings,
964 "show_status_log" => ShowStatusLog,
965 "show_lsp_status" => ShowLspStatus,
966 "show_remote_indicator_menu" => ShowRemoteIndicatorMenu,
967 "clear_warnings" => ClearWarnings,
968 "command_palette" => CommandPalette,
969 "quick_open" => QuickOpen,
970 "quick_open_buffers" => QuickOpenBuffers,
971 "quick_open_files" => QuickOpenFiles,
972 "open_live_grep" => OpenLiveGrep,
973 "resume_live_grep" => ResumeLiveGrep,
974 "live_grep_export_quickfix" => LiveGrepExportQuickfix,
975 "toggle_utility_dock" => ToggleUtilityDock,
976 "open_terminal_in_dock" => OpenTerminalInDock,
977 "cycle_live_grep_provider" => CycleLiveGrepProvider,
978 "toggle_line_wrap" => ToggleLineWrap,
979 "toggle_current_line_highlight" => ToggleCurrentLineHighlight,
980 "toggle_read_only" => ToggleReadOnly,
981 "toggle_page_view" => TogglePageView,
982 "set_page_width" => SetPageWidth,
983
984 "next_buffer" => NextBuffer,
985 "prev_buffer" => PrevBuffer,
986 "switch_to_previous_tab" => SwitchToPreviousTab,
987 "switch_to_tab_by_name" => SwitchToTabByName,
988 "scroll_tabs_left" => ScrollTabsLeft,
989 "scroll_tabs_right" => ScrollTabsRight,
990
991 "navigate_back" => NavigateBack,
992 "navigate_forward" => NavigateForward,
993
994 "split_horizontal" => SplitHorizontal,
995 "split_vertical" => SplitVertical,
996 "close_split" => CloseSplit,
997 "next_split" => NextSplit,
998 "prev_split" => PrevSplit,
999 "increase_split_size" => IncreaseSplitSize,
1000 "decrease_split_size" => DecreaseSplitSize,
1001 "toggle_maximize_split" => ToggleMaximizeSplit,
1002
1003 "prompt_confirm" => PromptConfirm,
1004 "prompt_cancel" => PromptCancel,
1005 "prompt_backspace" => PromptBackspace,
1006 "prompt_move_left" => PromptMoveLeft,
1007 "prompt_move_right" => PromptMoveRight,
1008 "prompt_move_start" => PromptMoveStart,
1009 "prompt_move_end" => PromptMoveEnd,
1010 "prompt_select_prev" => PromptSelectPrev,
1011 "prompt_select_next" => PromptSelectNext,
1012 "prompt_page_up" => PromptPageUp,
1013 "prompt_page_down" => PromptPageDown,
1014 "prompt_accept_suggestion" => PromptAcceptSuggestion,
1015 "prompt_delete_word_forward" => PromptDeleteWordForward,
1016 "prompt_delete_word_backward" => PromptDeleteWordBackward,
1017 "prompt_delete_to_line_end" => PromptDeleteToLineEnd,
1018 "prompt_copy" => PromptCopy,
1019 "prompt_cut" => PromptCut,
1020 "prompt_paste" => PromptPaste,
1021 "prompt_move_left_selecting" => PromptMoveLeftSelecting,
1022 "prompt_move_right_selecting" => PromptMoveRightSelecting,
1023 "prompt_move_home_selecting" => PromptMoveHomeSelecting,
1024 "prompt_move_end_selecting" => PromptMoveEndSelecting,
1025 "prompt_select_word_left" => PromptSelectWordLeft,
1026 "prompt_select_word_right" => PromptSelectWordRight,
1027 "prompt_select_all" => PromptSelectAll,
1028 "file_browser_toggle_hidden" => FileBrowserToggleHidden,
1029 "file_browser_toggle_detect_encoding" => FileBrowserToggleDetectEncoding,
1030 "prompt_move_word_left" => PromptMoveWordLeft,
1031 "prompt_move_word_right" => PromptMoveWordRight,
1032 "prompt_delete" => PromptDelete,
1033
1034 "popup_select_next" => PopupSelectNext,
1035 "popup_select_prev" => PopupSelectPrev,
1036 "popup_page_up" => PopupPageUp,
1037 "popup_page_down" => PopupPageDown,
1038 "popup_confirm" => PopupConfirm,
1039 "popup_cancel" => PopupCancel,
1040 "popup_focus" => PopupFocus,
1041
1042 "completion_accept" => CompletionAccept,
1043 "completion_dismiss" => CompletionDismiss,
1044
1045 "toggle_file_explorer" => ToggleFileExplorer,
1046 "toggle_menu_bar" => ToggleMenuBar,
1047 "toggle_tab_bar" => ToggleTabBar,
1048 "toggle_status_bar" => ToggleStatusBar,
1049 "toggle_prompt_line" => TogglePromptLine,
1050 "toggle_vertical_scrollbar" => ToggleVerticalScrollbar,
1051 "toggle_horizontal_scrollbar" => ToggleHorizontalScrollbar,
1052 "focus_file_explorer" => FocusFileExplorer,
1053 "focus_editor" => FocusEditor,
1054 "file_explorer_up" => FileExplorerUp,
1055 "file_explorer_down" => FileExplorerDown,
1056 "file_explorer_page_up" => FileExplorerPageUp,
1057 "file_explorer_page_down" => FileExplorerPageDown,
1058 "file_explorer_expand" => FileExplorerExpand,
1059 "file_explorer_collapse" => FileExplorerCollapse,
1060 "file_explorer_open" => FileExplorerOpen,
1061 "file_explorer_refresh" => FileExplorerRefresh,
1062 "file_explorer_new_file" => FileExplorerNewFile,
1063 "file_explorer_new_directory" => FileExplorerNewDirectory,
1064 "file_explorer_delete" => FileExplorerDelete,
1065 "file_explorer_rename" => FileExplorerRename,
1066 "file_explorer_toggle_hidden" => FileExplorerToggleHidden,
1067 "file_explorer_toggle_gitignored" => FileExplorerToggleGitignored,
1068 "file_explorer_search_clear" => FileExplorerSearchClear,
1069 "file_explorer_search_backspace" => FileExplorerSearchBackspace,
1070 "file_explorer_copy" => FileExplorerCopy,
1071 "file_explorer_cut" => FileExplorerCut,
1072 "file_explorer_paste" => FileExplorerPaste,
1073 "file_explorer_duplicate" => FileExplorerDuplicate,
1074 "file_explorer_copy_full_path" => FileExplorerCopyFullPath,
1075 "file_explorer_copy_relative_path" => FileExplorerCopyRelativePath,
1076 "file_explorer_extend_selection_up" => FileExplorerExtendSelectionUp,
1077 "file_explorer_extend_selection_down" => FileExplorerExtendSelectionDown,
1078 "file_explorer_toggle_select" => FileExplorerToggleSelect,
1079 "file_explorer_select_all" => FileExplorerSelectAll,
1080
1081 "lsp_completion" => LspCompletion,
1082 "lsp_goto_definition" => LspGotoDefinition,
1083 "lsp_references" => LspReferences,
1084 "lsp_rename" => LspRename,
1085 "lsp_hover" => LspHover,
1086 "lsp_signature_help" => LspSignatureHelp,
1087 "lsp_code_actions" => LspCodeActions,
1088 "lsp_restart" => LspRestart,
1089 "lsp_stop" => LspStop,
1090 "lsp_toggle_for_buffer" => LspToggleForBuffer,
1091 "toggle_inlay_hints" => ToggleInlayHints,
1092 "toggle_mouse_hover" => ToggleMouseHover,
1093
1094 "toggle_line_numbers" => ToggleLineNumbers,
1095 "toggle_scroll_sync" => ToggleScrollSync,
1096 "toggle_mouse_capture" => ToggleMouseCapture,
1097 "toggle_debug_highlights" => ToggleDebugHighlights,
1098 "set_background" => SetBackground,
1099 "set_background_blend" => SetBackgroundBlend,
1100 "inspect_theme_at_cursor" => InspectThemeAtCursor,
1101 "select_theme" => SelectTheme,
1102 "select_keybinding_map" => SelectKeybindingMap,
1103 "select_cursor_style" => SelectCursorStyle,
1104 "select_locale" => SelectLocale,
1105
1106 "set_tab_size" => SetTabSize,
1107 "set_line_ending" => SetLineEnding,
1108 "set_encoding" => SetEncoding,
1109 "reload_with_encoding" => ReloadWithEncoding,
1110 "set_language" => SetLanguage,
1111 "toggle_indentation_style" => ToggleIndentationStyle,
1112 "toggle_tab_indicators" => ToggleTabIndicators,
1113 "toggle_whitespace_indicators" => ToggleWhitespaceIndicators,
1114 "reset_buffer_settings" => ResetBufferSettings,
1115 "add_ruler" => AddRuler,
1116 "remove_ruler" => RemoveRuler,
1117
1118 "dump_config" => DumpConfig,
1119 "redraw_screen" => RedrawScreen,
1120
1121 "search" => Search,
1122 "find_in_selection" => FindInSelection,
1123 "find_next" => FindNext,
1124 "find_previous" => FindPrevious,
1125 "find_selection_next" => FindSelectionNext,
1126 "find_selection_previous" => FindSelectionPrevious,
1127 "replace" => Replace,
1128 "query_replace" => QueryReplace,
1129
1130 "menu_activate" => MenuActivate,
1131 "menu_close" => MenuClose,
1132 "menu_left" => MenuLeft,
1133 "menu_right" => MenuRight,
1134 "menu_up" => MenuUp,
1135 "menu_down" => MenuDown,
1136 "menu_execute" => MenuExecute,
1137
1138 "open_terminal" => OpenTerminal,
1139 "close_terminal" => CloseTerminal,
1140 "focus_terminal" => FocusTerminal,
1141 "terminal_escape" => TerminalEscape,
1142 "toggle_keyboard_capture" => ToggleKeyboardCapture,
1143 "terminal_paste" => TerminalPaste,
1144
1145 "shell_command" => ShellCommand,
1146 "shell_command_replace" => ShellCommandReplace,
1147
1148 "to_upper_case" => ToUpperCase,
1149 "to_lower_case" => ToLowerCase,
1150 "toggle_case" => ToggleCase,
1151 "sort_lines" => SortLines,
1152
1153 "calibrate_input" => CalibrateInput,
1154 "event_debug" => EventDebug,
1155 "suspend_process" => SuspendProcess,
1156 "load_plugin_from_buffer" => LoadPluginFromBuffer,
1157 "init_reload" => InitReload,
1158 "init_edit" => InitEdit,
1159 "init_check" => InitCheck,
1160 "open_keybinding_editor" => OpenKeybindingEditor,
1161
1162 "composite_next_hunk" => CompositeNextHunk,
1163 "composite_prev_hunk" => CompositePrevHunk,
1164
1165 "noop" => None,
1166
1167 "open_settings" => OpenSettings,
1168 "close_settings" => CloseSettings,
1169 "settings_save" => SettingsSave,
1170 "settings_reset" => SettingsReset,
1171 "settings_toggle_focus" => SettingsToggleFocus,
1172 "settings_activate" => SettingsActivate,
1173 "settings_search" => SettingsSearch,
1174 "settings_help" => SettingsHelp,
1175 "settings_increment" => SettingsIncrement,
1176 "settings_decrement" => SettingsDecrement,
1177 "settings_inherit" => SettingsInherit,
1178 }
1179 alias {
1180 "toggle_compose_mode" => TogglePageView,
1181 "set_compose_width" => SetPageWidth,
1182 }
1183 with_char {
1184 "insert_char" => InsertChar,
1185 "set_bookmark" => SetBookmark,
1186 "jump_to_bookmark" => JumpToBookmark,
1187 "clear_bookmark" => ClearBookmark,
1188 "play_macro" => PlayMacro,
1189 "toggle_macro_recording" => ToggleMacroRecording,
1190 "show_macro" => ShowMacro,
1191 }
1192 custom {
1193 "copy_with_theme" => CopyWithTheme : {
1194 let theme = args.get("theme").and_then(|v| v.as_str()).unwrap_or("");
1196 Self::CopyWithTheme(theme.to_string())
1197 },
1198 "menu_open" => MenuOpen : {
1199 let name = args.get("name")?.as_str()?;
1200 Self::MenuOpen(name.to_string())
1201 },
1202 "switch_keybinding_map" => SwitchKeybindingMap : {
1203 let map_name = args.get("map")?.as_str()?;
1204 Self::SwitchKeybindingMap(map_name.to_string())
1205 },
1206 "prompt_confirm_with_text" => PromptConfirmWithText : {
1207 let text = args.get("text")?.as_str()?;
1208 Self::PromptConfirmWithText(text.to_string())
1209 },
1210 }
1211 }
1212
1213 pub fn variant_arg_key(bare_action: &str) -> Option<&'static str> {
1220 match bare_action {
1221 "menu_open" => Some("name"),
1222 "switch_keybinding_map" => Some("map"),
1223 _ => None,
1224 }
1225 }
1226
1227 pub fn qualify_action(bare_action: &str, args: &HashMap<String, serde_json::Value>) -> String {
1231 if let Some(key) = Self::variant_arg_key(bare_action) {
1232 if let Some(v) = args.get(key).and_then(|v| v.as_str()) {
1233 return format!("{}:{}", bare_action, v);
1234 }
1235 }
1236 bare_action.to_string()
1237 }
1238
1239 pub fn to_qualified_action_str(&self) -> String {
1244 match self {
1245 Self::MenuOpen(name) => format!("menu_open:{}", name),
1246 Self::SwitchKeybindingMap(map) => format!("switch_keybinding_map:{}", map),
1247 other => other.to_action_str(),
1248 }
1249 }
1250
1251 pub fn unqualify_action(qualified: &str) -> (String, HashMap<String, serde_json::Value>) {
1256 if let Some((bare, suffix)) = qualified.split_once(':') {
1257 if let Some(arg_key) = Self::variant_arg_key(bare) {
1258 let mut args = HashMap::new();
1259 args.insert(
1260 arg_key.to_string(),
1261 serde_json::Value::String(suffix.to_string()),
1262 );
1263 return (bare.to_string(), args);
1264 }
1265 }
1266 (qualified.to_string(), HashMap::new())
1267 }
1268
1269 pub fn is_movement_or_editing(&self) -> bool {
1272 matches!(
1273 self,
1274 Action::MoveLeft
1276 | Action::MoveRight
1277 | Action::MoveUp
1278 | Action::MoveDown
1279 | Action::MoveWordLeft
1280 | Action::MoveWordRight
1281 | Action::MoveWordEnd
1282 | Action::ViMoveWordEnd
1283 | Action::MoveLeftInLine
1284 | Action::MoveRightInLine
1285 | Action::MoveLineStart
1286 | Action::MoveLineEnd
1287 | Action::MovePageUp
1288 | Action::MovePageDown
1289 | Action::MoveDocumentStart
1290 | Action::MoveDocumentEnd
1291 | Action::SelectLeft
1293 | Action::SelectRight
1294 | Action::SelectUp
1295 | Action::SelectDown
1296 | Action::SelectToParagraphUp
1297 | Action::SelectToParagraphDown
1298 | Action::SelectWordLeft
1299 | Action::SelectWordRight
1300 | Action::SelectWordEnd
1301 | Action::ViSelectWordEnd
1302 | Action::SelectLineStart
1303 | Action::SelectLineEnd
1304 | Action::SelectDocumentStart
1305 | Action::SelectDocumentEnd
1306 | Action::SelectPageUp
1307 | Action::SelectPageDown
1308 | Action::SelectAll
1309 | Action::SelectWord
1310 | Action::SelectLine
1311 | Action::ExpandSelection
1312 | Action::BlockSelectLeft
1314 | Action::BlockSelectRight
1315 | Action::BlockSelectUp
1316 | Action::BlockSelectDown
1317 | Action::InsertChar(_)
1319 | Action::InsertNewline
1320 | Action::InsertTab
1321 | Action::DeleteBackward
1322 | Action::DeleteForward
1323 | Action::DeleteWordBackward
1324 | Action::DeleteWordForward
1325 | Action::DeleteLine
1326 | Action::DeleteToLineEnd
1327 | Action::DeleteToLineStart
1328 | Action::TransposeChars
1329 | Action::OpenLine
1330 | Action::DuplicateLine
1331 | Action::MoveLineUp
1332 | Action::MoveLineDown
1333 | Action::Cut
1335 | Action::Paste
1336 | Action::Undo
1338 | Action::Redo
1339 )
1340 }
1341
1342 pub fn is_editing(&self) -> bool {
1345 matches!(
1346 self,
1347 Action::InsertChar(_)
1348 | Action::InsertNewline
1349 | Action::InsertTab
1350 | Action::DeleteBackward
1351 | Action::DeleteForward
1352 | Action::DeleteWordBackward
1353 | Action::DeleteWordForward
1354 | Action::DeleteLine
1355 | Action::DeleteToLineEnd
1356 | Action::DeleteToLineStart
1357 | Action::DeleteViWordEnd
1358 | Action::TransposeChars
1359 | Action::OpenLine
1360 | Action::DuplicateLine
1361 | Action::MoveLineUp
1362 | Action::MoveLineDown
1363 | Action::Cut
1364 | Action::Paste
1365 )
1366 }
1367}
1368
1369#[derive(Debug, Clone, PartialEq)]
1371pub enum ChordResolution {
1372 Complete(Action),
1374 Partial,
1376 NoMatch,
1378}
1379
1380#[derive(Clone)]
1382pub struct KeybindingResolver {
1383 bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1386
1387 default_bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1389
1390 plugin_defaults: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1393
1394 chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1397
1398 default_chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1400
1401 plugin_chord_defaults: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1403
1404 inheriting_modes: std::collections::HashSet<String>,
1408}
1409
1410impl KeybindingResolver {
1411 pub fn new(config: &Config) -> Self {
1413 let mut resolver = Self {
1414 bindings: HashMap::new(),
1415 default_bindings: HashMap::new(),
1416 plugin_defaults: HashMap::new(),
1417 chord_bindings: HashMap::new(),
1418 default_chord_bindings: HashMap::new(),
1419 plugin_chord_defaults: HashMap::new(),
1420 inheriting_modes: std::collections::HashSet::new(),
1421 };
1422
1423 let map_bindings = config.resolve_keymap(&config.active_keybinding_map);
1425 resolver.load_default_bindings_from_vec(&map_bindings);
1426
1427 resolver.load_bindings_from_vec(&config.keybindings);
1429
1430 resolver
1431 }
1432
1433 fn load_default_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1435 for binding in bindings {
1436 let context = if let Some(ref when) = binding.when {
1438 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1439 } else {
1440 KeyContext::Normal
1441 };
1442
1443 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1444 if !binding.keys.is_empty() {
1446 let mut sequence = Vec::new();
1448 for key_press in &binding.keys {
1449 if let Some(key_code) = Self::parse_key(&key_press.key) {
1450 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1451 sequence.push((key_code, modifiers));
1452 } else {
1453 break;
1455 }
1456 }
1457
1458 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1460 self.default_chord_bindings
1461 .entry(context)
1462 .or_default()
1463 .insert(sequence, action);
1464 }
1465 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1466 let modifiers = Self::parse_modifiers(&binding.modifiers);
1468
1469 self.insert_binding_with_equivalents(
1471 context,
1472 key_code,
1473 modifiers,
1474 action,
1475 &binding.key,
1476 );
1477 }
1478 }
1479 }
1480 }
1481
1482 fn insert_binding_with_equivalents(
1485 &mut self,
1486 context: KeyContext,
1487 key_code: KeyCode,
1488 modifiers: KeyModifiers,
1489 action: Action,
1490 key_name: &str,
1491 ) {
1492 let context_bindings = self.default_bindings.entry(context.clone()).or_default();
1493
1494 context_bindings.insert((key_code, modifiers), action.clone());
1496
1497 let equivalents = terminal_key_equivalents(key_code, modifiers);
1499 for (equiv_key, equiv_mods) in equivalents {
1500 if let Some(existing_action) = context_bindings.get(&(equiv_key, equiv_mods)) {
1502 if existing_action != &action {
1504 let equiv_name = format!("{:?}", equiv_key);
1505 tracing::warn!(
1506 "Terminal key equivalent conflict in {:?} context: {} (equivalent of {}) \
1507 is bound to {:?}, but {} is bound to {:?}. \
1508 The explicit binding takes precedence.",
1509 context,
1510 equiv_name,
1511 key_name,
1512 existing_action,
1513 key_name,
1514 action
1515 );
1516 }
1517 } else {
1519 context_bindings.insert((equiv_key, equiv_mods), action.clone());
1521 }
1522 }
1523 }
1524
1525 fn load_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1527 for binding in bindings {
1528 let context = if let Some(ref when) = binding.when {
1530 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1531 } else {
1532 KeyContext::Normal
1533 };
1534
1535 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1536 if !binding.keys.is_empty() {
1538 let mut sequence = Vec::new();
1540 for key_press in &binding.keys {
1541 if let Some(key_code) = Self::parse_key(&key_press.key) {
1542 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1543 sequence.push((key_code, modifiers));
1544 } else {
1545 break;
1547 }
1548 }
1549
1550 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1552 self.chord_bindings
1553 .entry(context)
1554 .or_default()
1555 .insert(sequence, action);
1556 }
1557 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1558 let modifiers = Self::parse_modifiers(&binding.modifiers);
1560 self.bindings
1561 .entry(context)
1562 .or_default()
1563 .insert((key_code, modifiers), action);
1564 }
1565 }
1566 }
1567 }
1568
1569 pub fn load_plugin_default(
1571 &mut self,
1572 context: KeyContext,
1573 key_code: KeyCode,
1574 modifiers: KeyModifiers,
1575 action: Action,
1576 ) {
1577 self.plugin_defaults
1578 .entry(context)
1579 .or_default()
1580 .insert((key_code, modifiers), action);
1581 }
1582
1583 pub fn load_plugin_chord_default(
1585 &mut self,
1586 context: KeyContext,
1587 sequence: Vec<(KeyCode, KeyModifiers)>,
1588 action: Action,
1589 ) {
1590 self.plugin_chord_defaults
1591 .entry(context)
1592 .or_default()
1593 .insert(sequence, action);
1594 }
1595
1596 pub fn clear_plugin_defaults_for_mode(&mut self, mode_name: &str) {
1598 let context = KeyContext::Mode(mode_name.to_string());
1599 self.plugin_defaults.remove(&context);
1600 self.plugin_chord_defaults.remove(&context);
1601 self.inheriting_modes.remove(mode_name);
1602 }
1603
1604 pub fn set_mode_inherits_normal_bindings(&mut self, mode_name: &str, inherit: bool) {
1607 if inherit {
1608 self.inheriting_modes.insert(mode_name.to_string());
1609 } else {
1610 self.inheriting_modes.remove(mode_name);
1611 }
1612 }
1613
1614 pub fn get_plugin_defaults(
1616 &self,
1617 ) -> &HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>> {
1618 &self.plugin_defaults
1619 }
1620
1621 fn is_application_wide_action(action: &Action) -> bool {
1623 matches!(
1624 action,
1625 Action::Quit
1626 | Action::ForceQuit
1627 | Action::Save
1628 | Action::SaveAs
1629 | Action::ShowHelp
1630 | Action::ShowKeyboardShortcuts
1631 | Action::PromptCancel | Action::PopupCancel )
1634 }
1635
1636 pub fn is_terminal_ui_action(action: &Action) -> bool {
1640 matches!(
1641 action,
1642 Action::CommandPalette
1644 | Action::QuickOpen
1645 | Action::QuickOpenBuffers
1646 | Action::QuickOpenFiles
1647 | Action::OpenLiveGrep
1648 | Action::ResumeLiveGrep
1649 | Action::LiveGrepExportQuickfix
1650 | Action::ToggleUtilityDock
1651 | Action::OpenTerminalInDock
1652 | Action::CycleLiveGrepProvider
1653 | Action::OpenSettings
1654 | Action::MenuActivate
1655 | Action::MenuOpen(_)
1656 | Action::ShowHelp
1657 | Action::ShowKeyboardShortcuts
1658 | Action::Quit
1659 | Action::ForceQuit
1660 | Action::NextSplit
1662 | Action::PrevSplit
1663 | Action::SplitHorizontal
1664 | Action::SplitVertical
1665 | Action::CloseSplit
1666 | Action::ToggleMaximizeSplit
1667 | Action::NextBuffer
1669 | Action::PrevBuffer
1670 | Action::Close
1671 | Action::ScrollTabsLeft
1672 | Action::ScrollTabsRight
1673 | Action::TerminalEscape
1675 | Action::ToggleKeyboardCapture
1676 | Action::OpenTerminal
1677 | Action::CloseTerminal
1678 | Action::TerminalPaste
1679 | Action::ToggleFileExplorer
1681 | Action::ToggleMenuBar
1683 )
1684 }
1685
1686 pub fn resolve_chord(
1692 &self,
1693 chord_state: &[(KeyCode, KeyModifiers)],
1694 event: &KeyEvent,
1695 context: KeyContext,
1696 ) -> ChordResolution {
1697 let mut full_sequence: Vec<(KeyCode, KeyModifiers)> = chord_state
1699 .iter()
1700 .map(|(c, m)| normalize_key(*c, *m))
1701 .collect();
1702 let (norm_code, norm_mods) = normalize_key(event.code, event.modifiers);
1703 full_sequence.push((norm_code, norm_mods));
1704
1705 tracing::trace!(
1706 "KeybindingResolver.resolve_chord: sequence={:?}, context={:?}",
1707 full_sequence,
1708 context
1709 );
1710
1711 let search_order = vec![
1713 (&self.chord_bindings, &KeyContext::Global, "custom global"),
1714 (
1715 &self.default_chord_bindings,
1716 &KeyContext::Global,
1717 "default global",
1718 ),
1719 (&self.chord_bindings, &context, "custom context"),
1720 (&self.default_chord_bindings, &context, "default context"),
1721 (
1722 &self.plugin_chord_defaults,
1723 &context,
1724 "plugin default context",
1725 ),
1726 ];
1727
1728 let mut has_partial_match = false;
1729
1730 for (binding_map, bind_context, label) in search_order {
1731 if let Some(context_chords) = binding_map.get(bind_context) {
1732 if let Some(action) = context_chords.get(&full_sequence) {
1734 tracing::trace!(" -> Complete chord match in {}: {:?}", label, action);
1735 return ChordResolution::Complete(action.clone());
1736 }
1737
1738 for (chord_seq, _) in context_chords.iter() {
1740 if chord_seq.len() > full_sequence.len()
1741 && chord_seq[..full_sequence.len()] == full_sequence[..]
1742 {
1743 tracing::trace!(" -> Partial chord match in {}", label);
1744 has_partial_match = true;
1745 break;
1746 }
1747 }
1748 }
1749 }
1750
1751 if has_partial_match {
1752 ChordResolution::Partial
1753 } else {
1754 tracing::trace!(" -> No chord match");
1755 ChordResolution::NoMatch
1756 }
1757 }
1758
1759 pub fn resolve(&self, event: &KeyEvent, context: KeyContext) -> Action {
1761 let (norm_code, norm_mods) = normalize_key(event.code, event.modifiers);
1764 let norm = &(norm_code, norm_mods);
1765 tracing::trace!(
1766 "KeybindingResolver.resolve: code={:?}, modifiers={:?}, context={:?}",
1767 event.code,
1768 event.modifiers,
1769 context
1770 );
1771
1772 if let Some(global_bindings) = self.bindings.get(&KeyContext::Global) {
1774 if let Some(action) = global_bindings.get(norm) {
1775 tracing::trace!(" -> Found in custom global bindings: {:?}", action);
1776 return action.clone();
1777 }
1778 }
1779
1780 if let Some(global_bindings) = self.default_bindings.get(&KeyContext::Global) {
1781 if let Some(action) = global_bindings.get(norm) {
1782 tracing::trace!(" -> Found in default global bindings: {:?}", action);
1783 return action.clone();
1784 }
1785 }
1786
1787 if let Some(context_bindings) = self.bindings.get(&context) {
1789 if let Some(action) = context_bindings.get(norm) {
1790 tracing::trace!(
1791 " -> Found in custom {} bindings: {:?}",
1792 context.to_when_clause(),
1793 action
1794 );
1795 return action.clone();
1796 }
1797 }
1798
1799 if let Some(context_bindings) = self.default_bindings.get(&context) {
1801 if let Some(action) = context_bindings.get(norm) {
1802 tracing::trace!(
1803 " -> Found in default {} bindings: {:?}",
1804 context.to_when_clause(),
1805 action
1806 );
1807 return action.clone();
1808 }
1809 }
1810
1811 if let Some(plugin_bindings) = self.plugin_defaults.get(&context) {
1813 if let Some(action) = plugin_bindings.get(norm) {
1814 tracing::trace!(
1815 " -> Found in plugin default {} bindings: {:?}",
1816 context.to_when_clause(),
1817 action
1818 );
1819 return action.clone();
1820 }
1821 }
1822
1823 if context != KeyContext::Normal {
1827 let full_fallthrough = context.allows_normal_fallthrough()
1828 || matches!(&context, KeyContext::Mode(name) if self.inheriting_modes.contains(name));
1829
1830 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
1831 if let Some(action) = normal_bindings.get(norm) {
1832 if full_fallthrough || Self::is_application_wide_action(action) {
1833 tracing::trace!(
1834 " -> Found action in custom normal bindings (fallthrough): {:?}",
1835 action
1836 );
1837 return action.clone();
1838 }
1839 }
1840 }
1841
1842 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
1843 if let Some(action) = normal_bindings.get(norm) {
1844 if full_fallthrough || Self::is_application_wide_action(action) {
1845 tracing::trace!(
1846 " -> Found action in default normal bindings (fallthrough): {:?}",
1847 action
1848 );
1849 return action.clone();
1850 }
1851 }
1852 }
1853 }
1854
1855 if context.allows_text_input() && is_text_input_modifier(event.modifiers) {
1857 if let KeyCode::Char(c) = event.code {
1858 tracing::trace!(" -> Character input: '{}'", c);
1859 return Action::InsertChar(c);
1860 }
1861 }
1862
1863 tracing::trace!(" -> No binding found, returning Action::None");
1864 Action::None
1865 }
1866
1867 pub fn resolve_in_context_only(&self, event: &KeyEvent, context: KeyContext) -> Option<Action> {
1872 let norm = normalize_key(event.code, event.modifiers);
1873 if let Some(context_bindings) = self.bindings.get(&context) {
1875 if let Some(action) = context_bindings.get(&norm) {
1876 return Some(action.clone());
1877 }
1878 }
1879
1880 if let Some(context_bindings) = self.default_bindings.get(&context) {
1882 if let Some(action) = context_bindings.get(&norm) {
1883 return Some(action.clone());
1884 }
1885 }
1886
1887 None
1888 }
1889
1890 pub fn resolve_terminal_ui_action(&self, event: &KeyEvent) -> Action {
1894 let norm = normalize_key(event.code, event.modifiers);
1895 tracing::trace!(
1896 "KeybindingResolver.resolve_terminal_ui_action: code={:?}, modifiers={:?}",
1897 event.code,
1898 event.modifiers
1899 );
1900
1901 for bindings in [&self.bindings, &self.default_bindings] {
1903 if let Some(terminal_bindings) = bindings.get(&KeyContext::Terminal) {
1904 if let Some(action) = terminal_bindings.get(&norm) {
1905 if Self::is_terminal_ui_action(action) {
1906 tracing::trace!(" -> Found UI action in terminal bindings: {:?}", action);
1907 return action.clone();
1908 }
1909 }
1910 }
1911 }
1912
1913 for bindings in [&self.bindings, &self.default_bindings] {
1915 if let Some(global_bindings) = bindings.get(&KeyContext::Global) {
1916 if let Some(action) = global_bindings.get(&norm) {
1917 if Self::is_terminal_ui_action(action) {
1918 tracing::trace!(" -> Found UI action in global bindings: {:?}", action);
1919 return action.clone();
1920 }
1921 }
1922 }
1923 }
1924
1925 for bindings in [&self.bindings, &self.default_bindings] {
1927 if let Some(normal_bindings) = bindings.get(&KeyContext::Normal) {
1928 if let Some(action) = normal_bindings.get(&norm) {
1929 if Self::is_terminal_ui_action(action) {
1930 tracing::trace!(" -> Found UI action in normal bindings: {:?}", action);
1931 return action.clone();
1932 }
1933 }
1934 }
1935 }
1936
1937 tracing::trace!(" -> No UI action found");
1938 Action::None
1939 }
1940
1941 pub fn find_keybinding_for_action(
1944 &self,
1945 action_name: &str,
1946 context: KeyContext,
1947 ) -> Option<String> {
1948 let target_action = Action::from_str(action_name, &HashMap::new())?;
1950
1951 let search_maps = vec![
1953 self.bindings.get(&context),
1954 self.bindings.get(&KeyContext::Global),
1955 self.default_bindings.get(&context),
1956 self.default_bindings.get(&KeyContext::Global),
1957 ];
1958
1959 for map in search_maps.into_iter().flatten() {
1960 let mut matches: Vec<(KeyCode, KeyModifiers)> = map
1962 .iter()
1963 .filter(|(_, action)| {
1964 std::mem::discriminant(*action) == std::mem::discriminant(&target_action)
1965 })
1966 .map(|((key_code, modifiers), _)| (*key_code, *modifiers))
1967 .collect();
1968
1969 if !matches.is_empty() {
1970 matches.sort_by(|(key_a, mod_a), (key_b, mod_b)| {
1972 let mod_count_a = mod_a.bits().count_ones();
1974 let mod_count_b = mod_b.bits().count_ones();
1975 match mod_count_a.cmp(&mod_count_b) {
1976 std::cmp::Ordering::Equal => {
1977 match mod_a.bits().cmp(&mod_b.bits()) {
1979 std::cmp::Ordering::Equal => {
1980 Self::key_code_sort_key(key_a)
1982 .cmp(&Self::key_code_sort_key(key_b))
1983 }
1984 other => other,
1985 }
1986 }
1987 other => other,
1988 }
1989 });
1990
1991 let (key_code, modifiers) = matches[0];
1992 return Some(format_keybinding(&key_code, &modifiers));
1993 }
1994 }
1995
1996 None
1997 }
1998
1999 fn key_code_sort_key(key_code: &KeyCode) -> (u8, u32) {
2001 match key_code {
2002 KeyCode::Char(c) => (0, *c as u32),
2003 KeyCode::F(n) => (1, *n as u32),
2004 KeyCode::Enter => (2, 0),
2005 KeyCode::Tab => (2, 1),
2006 KeyCode::Backspace => (2, 2),
2007 KeyCode::Delete => (2, 3),
2008 KeyCode::Esc => (2, 4),
2009 KeyCode::Left => (3, 0),
2010 KeyCode::Right => (3, 1),
2011 KeyCode::Up => (3, 2),
2012 KeyCode::Down => (3, 3),
2013 KeyCode::Home => (3, 4),
2014 KeyCode::End => (3, 5),
2015 KeyCode::PageUp => (3, 6),
2016 KeyCode::PageDown => (3, 7),
2017 _ => (255, 0),
2018 }
2019 }
2020
2021 pub fn find_menu_mnemonic(&self, menu_name: &str) -> Option<char> {
2024 let search_maps = vec![
2026 self.bindings.get(&KeyContext::Normal),
2027 self.bindings.get(&KeyContext::Global),
2028 self.default_bindings.get(&KeyContext::Normal),
2029 self.default_bindings.get(&KeyContext::Global),
2030 ];
2031
2032 for map in search_maps.into_iter().flatten() {
2033 for ((key_code, modifiers), action) in map {
2034 if let Action::MenuOpen(name) = action {
2036 if name.eq_ignore_ascii_case(menu_name) && *modifiers == KeyModifiers::ALT {
2037 if let KeyCode::Char(c) = key_code {
2039 return Some(c.to_ascii_lowercase());
2040 }
2041 }
2042 }
2043 }
2044 }
2045
2046 None
2047 }
2048
2049 fn parse_key(key: &str) -> Option<KeyCode> {
2051 let lower = key.to_lowercase();
2052 match lower.as_str() {
2053 "enter" => Some(KeyCode::Enter),
2054 "backspace" => Some(KeyCode::Backspace),
2055 "delete" | "del" => Some(KeyCode::Delete),
2056 "tab" => Some(KeyCode::Tab),
2057 "backtab" => Some(KeyCode::BackTab),
2058 "esc" | "escape" => Some(KeyCode::Esc),
2059 "space" => Some(KeyCode::Char(' ')),
2060
2061 "left" => Some(KeyCode::Left),
2062 "right" => Some(KeyCode::Right),
2063 "up" => Some(KeyCode::Up),
2064 "down" => Some(KeyCode::Down),
2065 "home" => Some(KeyCode::Home),
2066 "end" => Some(KeyCode::End),
2067 "pageup" => Some(KeyCode::PageUp),
2068 "pagedown" => Some(KeyCode::PageDown),
2069
2070 s if s.len() == 1 => s.chars().next().map(KeyCode::Char),
2071 s if s.starts_with('f') && s.len() >= 2 => s[1..].parse::<u8>().ok().map(KeyCode::F),
2073 _ => None,
2074 }
2075 }
2076
2077 fn parse_modifiers(modifiers: &[String]) -> KeyModifiers {
2079 let mut result = KeyModifiers::empty();
2080 for m in modifiers {
2081 match m.to_lowercase().as_str() {
2082 "ctrl" | "control" => result |= KeyModifiers::CONTROL,
2083 "shift" => result |= KeyModifiers::SHIFT,
2084 "alt" => result |= KeyModifiers::ALT,
2085 "super" | "cmd" | "command" | "meta" => result |= KeyModifiers::SUPER,
2086 _ => {}
2087 }
2088 }
2089 result
2090 }
2091
2092 pub fn get_all_bindings(&self) -> Vec<(String, String)> {
2096 let mut bindings = Vec::new();
2097
2098 for context in &[
2100 KeyContext::Normal,
2101 KeyContext::Prompt,
2102 KeyContext::Popup,
2103 KeyContext::FileExplorer,
2104 KeyContext::Menu,
2105 KeyContext::CompositeBuffer,
2106 ] {
2107 let mut all_keys: HashMap<(KeyCode, KeyModifiers), Action> = HashMap::new();
2108
2109 if let Some(context_defaults) = self.default_bindings.get(context) {
2111 for (key, action) in context_defaults {
2112 all_keys.insert(*key, action.clone());
2113 }
2114 }
2115
2116 if let Some(context_bindings) = self.bindings.get(context) {
2118 for (key, action) in context_bindings {
2119 all_keys.insert(*key, action.clone());
2120 }
2121 }
2122
2123 let context_str = if *context != KeyContext::Normal {
2125 format!("[{}] ", context.to_when_clause())
2126 } else {
2127 String::new()
2128 };
2129
2130 for ((key_code, modifiers), action) in all_keys {
2131 let key_str = Self::format_key(key_code, modifiers);
2132 let action_str = format!("{}{}", context_str, Self::format_action(&action));
2133 bindings.push((key_str, action_str));
2134 }
2135 }
2136
2137 bindings.sort_by(|a, b| a.1.cmp(&b.1));
2139
2140 bindings
2141 }
2142
2143 fn format_key(key_code: KeyCode, modifiers: KeyModifiers) -> String {
2145 format_keybinding(&key_code, &modifiers)
2146 }
2147
2148 pub fn format_action(action: &Action) -> String {
2150 match action {
2151 Action::InsertChar(c) => t!("action.insert_char", char = c),
2152 Action::InsertNewline => t!("action.insert_newline"),
2153 Action::InsertTab => t!("action.insert_tab"),
2154 Action::MoveLeft => t!("action.move_left"),
2155 Action::MoveRight => t!("action.move_right"),
2156 Action::MoveUp => t!("action.move_up"),
2157 Action::MoveDown => t!("action.move_down"),
2158 Action::MoveWordLeft => t!("action.move_word_left"),
2159 Action::MoveWordRight => t!("action.move_word_right"),
2160 Action::MoveWordEnd => t!("action.move_word_end"),
2161 Action::ViMoveWordEnd => t!("action.move_word_end"),
2162 Action::MoveLeftInLine => t!("action.move_left"),
2163 Action::MoveRightInLine => t!("action.move_right"),
2164 Action::MoveLineStart => t!("action.move_line_start"),
2165 Action::MoveLineEnd => t!("action.move_line_end"),
2166 Action::MoveLineUp => t!("action.move_line_up"),
2167 Action::MoveLineDown => t!("action.move_line_down"),
2168 Action::MovePageUp => t!("action.move_page_up"),
2169 Action::MovePageDown => t!("action.move_page_down"),
2170 Action::MoveDocumentStart => t!("action.move_document_start"),
2171 Action::MoveDocumentEnd => t!("action.move_document_end"),
2172 Action::SelectLeft => t!("action.select_left"),
2173 Action::SelectRight => t!("action.select_right"),
2174 Action::SelectUp => t!("action.select_up"),
2175 Action::SelectDown => t!("action.select_down"),
2176 Action::SelectToParagraphUp => t!("action.select_to_paragraph_up"),
2177 Action::SelectToParagraphDown => t!("action.select_to_paragraph_down"),
2178 Action::SelectWordLeft => t!("action.select_word_left"),
2179 Action::SelectWordRight => t!("action.select_word_right"),
2180 Action::SelectWordEnd => t!("action.select_word_end"),
2181 Action::ViSelectWordEnd => t!("action.select_word_end"),
2182 Action::SelectLineStart => t!("action.select_line_start"),
2183 Action::SelectLineEnd => t!("action.select_line_end"),
2184 Action::SelectDocumentStart => t!("action.select_document_start"),
2185 Action::SelectDocumentEnd => t!("action.select_document_end"),
2186 Action::SelectPageUp => t!("action.select_page_up"),
2187 Action::SelectPageDown => t!("action.select_page_down"),
2188 Action::SelectAll => t!("action.select_all"),
2189 Action::SelectWord => t!("action.select_word"),
2190 Action::SelectLine => t!("action.select_line"),
2191 Action::ExpandSelection => t!("action.expand_selection"),
2192 Action::BlockSelectLeft => t!("action.block_select_left"),
2193 Action::BlockSelectRight => t!("action.block_select_right"),
2194 Action::BlockSelectUp => t!("action.block_select_up"),
2195 Action::BlockSelectDown => t!("action.block_select_down"),
2196 Action::DeleteBackward => t!("action.delete_backward"),
2197 Action::DeleteForward => t!("action.delete_forward"),
2198 Action::DeleteWordBackward => t!("action.delete_word_backward"),
2199 Action::DeleteWordForward => t!("action.delete_word_forward"),
2200 Action::DeleteLine => t!("action.delete_line"),
2201 Action::DeleteToLineEnd => t!("action.delete_to_line_end"),
2202 Action::DeleteToLineStart => t!("action.delete_to_line_start"),
2203 Action::DeleteViWordEnd => t!("action.delete_word_forward"),
2204 Action::TransposeChars => t!("action.transpose_chars"),
2205 Action::OpenLine => t!("action.open_line"),
2206 Action::DuplicateLine => t!("action.duplicate_line"),
2207 Action::Recenter => t!("action.recenter"),
2208 Action::SetMark => t!("action.set_mark"),
2209 Action::Copy => t!("action.copy"),
2210 Action::CopyWithTheme(theme) if theme.is_empty() => t!("action.copy_with_formatting"),
2211 Action::CopyWithTheme(theme) => t!("action.copy_with_theme", theme = theme),
2212 Action::Cut => t!("action.cut"),
2213 Action::Paste => t!("action.paste"),
2214 Action::CopyFilePath => t!("action.copy_file_path"),
2215 Action::CopyRelativeFilePath => t!("action.copy_relative_file_path"),
2216 Action::YankWordForward => t!("action.yank_word_forward"),
2217 Action::YankWordBackward => t!("action.yank_word_backward"),
2218 Action::YankToLineEnd => t!("action.yank_to_line_end"),
2219 Action::YankToLineStart => t!("action.yank_to_line_start"),
2220 Action::YankViWordEnd => t!("action.yank_word_forward"),
2221 Action::AddCursorAbove => t!("action.add_cursor_above"),
2222 Action::AddCursorBelow => t!("action.add_cursor_below"),
2223 Action::AddCursorNextMatch => t!("action.add_cursor_next_match"),
2224 Action::RemoveSecondaryCursors => t!("action.remove_secondary_cursors"),
2225 Action::Save => t!("action.save"),
2226 Action::SaveAs => t!("action.save_as"),
2227 Action::Open => t!("action.open"),
2228 Action::SwitchProject => t!("action.switch_project"),
2229 Action::New => t!("action.new"),
2230 Action::Close => t!("action.close"),
2231 Action::CloseTab => t!("action.close_tab"),
2232 Action::Quit => t!("action.quit"),
2233 Action::ForceQuit => t!("action.force_quit"),
2234 Action::Detach => t!("action.detach"),
2235 Action::Revert => t!("action.revert"),
2236 Action::ToggleAutoRevert => t!("action.toggle_auto_revert"),
2237 Action::FormatBuffer => t!("action.format_buffer"),
2238 Action::TrimTrailingWhitespace => t!("action.trim_trailing_whitespace"),
2239 Action::EnsureFinalNewline => t!("action.ensure_final_newline"),
2240 Action::GotoLine => t!("action.goto_line"),
2241 Action::ScanLineIndex => t!("action.scan_line_index"),
2242 Action::GoToMatchingBracket => t!("action.goto_matching_bracket"),
2243 Action::JumpToNextError => t!("action.jump_to_next_error"),
2244 Action::JumpToPreviousError => t!("action.jump_to_previous_error"),
2245 Action::SmartHome => t!("action.smart_home"),
2246 Action::DedentSelection => t!("action.dedent_selection"),
2247 Action::ToggleComment => t!("action.toggle_comment"),
2248 Action::DabbrevExpand => std::borrow::Cow::Borrowed("Expand abbreviation (dabbrev)"),
2249 Action::ToggleFold => t!("action.toggle_fold"),
2250 Action::SetBookmark(c) => t!("action.set_bookmark", key = c),
2251 Action::JumpToBookmark(c) => t!("action.jump_to_bookmark", key = c),
2252 Action::ClearBookmark(c) => t!("action.clear_bookmark", key = c),
2253 Action::ListBookmarks => t!("action.list_bookmarks"),
2254 Action::ToggleSearchCaseSensitive => t!("action.toggle_search_case_sensitive"),
2255 Action::ToggleSearchWholeWord => t!("action.toggle_search_whole_word"),
2256 Action::ToggleSearchRegex => t!("action.toggle_search_regex"),
2257 Action::ToggleSearchConfirmEach => t!("action.toggle_search_confirm_each"),
2258 Action::StartMacroRecording => t!("action.start_macro_recording"),
2259 Action::StopMacroRecording => t!("action.stop_macro_recording"),
2260 Action::PlayMacro(c) => t!("action.play_macro", key = c),
2261 Action::ToggleMacroRecording(c) => t!("action.toggle_macro_recording", key = c),
2262 Action::ShowMacro(c) => t!("action.show_macro", key = c),
2263 Action::ListMacros => t!("action.list_macros"),
2264 Action::PromptRecordMacro => t!("action.prompt_record_macro"),
2265 Action::PromptPlayMacro => t!("action.prompt_play_macro"),
2266 Action::PlayLastMacro => t!("action.play_last_macro"),
2267 Action::PromptSetBookmark => t!("action.prompt_set_bookmark"),
2268 Action::PromptJumpToBookmark => t!("action.prompt_jump_to_bookmark"),
2269 Action::Undo => t!("action.undo"),
2270 Action::Redo => t!("action.redo"),
2271 Action::ScrollUp => t!("action.scroll_up"),
2272 Action::ScrollDown => t!("action.scroll_down"),
2273 Action::ShowHelp => t!("action.show_help"),
2274 Action::ShowKeyboardShortcuts => t!("action.show_keyboard_shortcuts"),
2275 Action::ShowWarnings => t!("action.show_warnings"),
2276 Action::ShowStatusLog => t!("action.show_status_log"),
2277 Action::ShowLspStatus => t!("action.show_lsp_status"),
2278 Action::ShowRemoteIndicatorMenu => t!("action.show_remote_indicator_menu"),
2279 Action::ClearWarnings => t!("action.clear_warnings"),
2280 Action::CommandPalette => t!("action.command_palette"),
2281 Action::QuickOpen => t!("action.quick_open"),
2282 Action::QuickOpenBuffers => t!("action.quick_open_buffers"),
2283 Action::QuickOpenFiles => t!("action.quick_open_files"),
2284 Action::OpenLiveGrep => t!("action.open_live_grep"),
2285 Action::ResumeLiveGrep => t!("action.resume_live_grep"),
2286 Action::LiveGrepExportQuickfix => t!("action.live_grep_export_quickfix"),
2287 Action::ToggleUtilityDock => t!("action.toggle_utility_dock"),
2288 Action::OpenTerminalInDock => t!("action.open_terminal_in_dock"),
2289 Action::CycleLiveGrepProvider => t!("action.cycle_live_grep_provider"),
2290 Action::InspectThemeAtCursor => t!("action.inspect_theme_at_cursor"),
2291 Action::ToggleLineWrap => t!("action.toggle_line_wrap"),
2292 Action::ToggleCurrentLineHighlight => t!("action.toggle_current_line_highlight"),
2293 Action::ToggleReadOnly => t!("action.toggle_read_only"),
2294 Action::TogglePageView => t!("action.toggle_page_view"),
2295 Action::SetPageWidth => t!("action.set_page_width"),
2296 Action::NextBuffer => t!("action.next_buffer"),
2297 Action::PrevBuffer => t!("action.prev_buffer"),
2298 Action::NavigateBack => t!("action.navigate_back"),
2299 Action::NavigateForward => t!("action.navigate_forward"),
2300 Action::SplitHorizontal => t!("action.split_horizontal"),
2301 Action::SplitVertical => t!("action.split_vertical"),
2302 Action::CloseSplit => t!("action.close_split"),
2303 Action::NextSplit => t!("action.next_split"),
2304 Action::PrevSplit => t!("action.prev_split"),
2305 Action::IncreaseSplitSize => t!("action.increase_split_size"),
2306 Action::DecreaseSplitSize => t!("action.decrease_split_size"),
2307 Action::ToggleMaximizeSplit => t!("action.toggle_maximize_split"),
2308 Action::PromptConfirm => t!("action.prompt_confirm"),
2309 Action::PromptConfirmWithText(ref text) => {
2310 format!("{} ({})", t!("action.prompt_confirm"), text).into()
2311 }
2312 Action::PromptCancel => t!("action.prompt_cancel"),
2313 Action::PromptBackspace => t!("action.prompt_backspace"),
2314 Action::PromptDelete => t!("action.prompt_delete"),
2315 Action::PromptMoveLeft => t!("action.prompt_move_left"),
2316 Action::PromptMoveRight => t!("action.prompt_move_right"),
2317 Action::PromptMoveStart => t!("action.prompt_move_start"),
2318 Action::PromptMoveEnd => t!("action.prompt_move_end"),
2319 Action::PromptSelectPrev => t!("action.prompt_select_prev"),
2320 Action::PromptSelectNext => t!("action.prompt_select_next"),
2321 Action::PromptPageUp => t!("action.prompt_page_up"),
2322 Action::PromptPageDown => t!("action.prompt_page_down"),
2323 Action::PromptAcceptSuggestion => t!("action.prompt_accept_suggestion"),
2324 Action::PromptMoveWordLeft => t!("action.prompt_move_word_left"),
2325 Action::PromptMoveWordRight => t!("action.prompt_move_word_right"),
2326 Action::PromptDeleteWordForward => t!("action.prompt_delete_word_forward"),
2327 Action::PromptDeleteWordBackward => t!("action.prompt_delete_word_backward"),
2328 Action::PromptDeleteToLineEnd => t!("action.prompt_delete_to_line_end"),
2329 Action::PromptCopy => t!("action.prompt_copy"),
2330 Action::PromptCut => t!("action.prompt_cut"),
2331 Action::PromptPaste => t!("action.prompt_paste"),
2332 Action::PromptMoveLeftSelecting => t!("action.prompt_move_left_selecting"),
2333 Action::PromptMoveRightSelecting => t!("action.prompt_move_right_selecting"),
2334 Action::PromptMoveHomeSelecting => t!("action.prompt_move_home_selecting"),
2335 Action::PromptMoveEndSelecting => t!("action.prompt_move_end_selecting"),
2336 Action::PromptSelectWordLeft => t!("action.prompt_select_word_left"),
2337 Action::PromptSelectWordRight => t!("action.prompt_select_word_right"),
2338 Action::PromptSelectAll => t!("action.prompt_select_all"),
2339 Action::FileBrowserToggleHidden => t!("action.file_browser_toggle_hidden"),
2340 Action::FileBrowserToggleDetectEncoding => {
2341 t!("action.file_browser_toggle_detect_encoding")
2342 }
2343 Action::PopupSelectNext => t!("action.popup_select_next"),
2344 Action::PopupSelectPrev => t!("action.popup_select_prev"),
2345 Action::PopupPageUp => t!("action.popup_page_up"),
2346 Action::PopupPageDown => t!("action.popup_page_down"),
2347 Action::PopupConfirm => t!("action.popup_confirm"),
2348 Action::PopupCancel => t!("action.popup_cancel"),
2349 Action::PopupFocus => t!("action.popup_focus"),
2350 Action::CompletionAccept => t!("action.completion_accept"),
2351 Action::CompletionDismiss => t!("action.completion_dismiss"),
2352 Action::ToggleFileExplorer => t!("action.toggle_file_explorer"),
2353 Action::ToggleMenuBar => t!("action.toggle_menu_bar"),
2354 Action::ToggleTabBar => t!("action.toggle_tab_bar"),
2355 Action::ToggleStatusBar => t!("action.toggle_status_bar"),
2356 Action::TogglePromptLine => t!("action.toggle_prompt_line"),
2357 Action::ToggleVerticalScrollbar => t!("action.toggle_vertical_scrollbar"),
2358 Action::ToggleHorizontalScrollbar => t!("action.toggle_horizontal_scrollbar"),
2359 Action::FocusFileExplorer => t!("action.focus_file_explorer"),
2360 Action::FocusEditor => t!("action.focus_editor"),
2361 Action::FileExplorerUp => t!("action.file_explorer_up"),
2362 Action::FileExplorerDown => t!("action.file_explorer_down"),
2363 Action::FileExplorerPageUp => t!("action.file_explorer_page_up"),
2364 Action::FileExplorerPageDown => t!("action.file_explorer_page_down"),
2365 Action::FileExplorerExpand => t!("action.file_explorer_expand"),
2366 Action::FileExplorerCollapse => t!("action.file_explorer_collapse"),
2367 Action::FileExplorerOpen => t!("action.file_explorer_open"),
2368 Action::FileExplorerRefresh => t!("action.file_explorer_refresh"),
2369 Action::FileExplorerNewFile => t!("action.file_explorer_new_file"),
2370 Action::FileExplorerNewDirectory => t!("action.file_explorer_new_directory"),
2371 Action::FileExplorerDelete => t!("action.file_explorer_delete"),
2372 Action::FileExplorerRename => t!("action.file_explorer_rename"),
2373 Action::FileExplorerToggleHidden => t!("action.file_explorer_toggle_hidden"),
2374 Action::FileExplorerToggleGitignored => t!("action.file_explorer_toggle_gitignored"),
2375 Action::FileExplorerSearchClear => t!("action.file_explorer_search_clear"),
2376 Action::FileExplorerSearchBackspace => t!("action.file_explorer_search_backspace"),
2377 Action::FileExplorerCopy => t!("action.file_explorer_copy"),
2378 Action::FileExplorerCut => t!("action.file_explorer_cut"),
2379 Action::FileExplorerPaste => t!("action.file_explorer_paste"),
2380 Action::FileExplorerDuplicate => t!("action.file_explorer_duplicate"),
2381 Action::FileExplorerCopyFullPath => t!("action.file_explorer_copy_full_path"),
2382 Action::FileExplorerCopyRelativePath => t!("action.file_explorer_copy_relative_path"),
2383 Action::FileExplorerExtendSelectionUp => t!("action.file_explorer_extend_selection_up"),
2384 Action::FileExplorerExtendSelectionDown => {
2385 t!("action.file_explorer_extend_selection_down")
2386 }
2387 Action::FileExplorerToggleSelect => t!("action.file_explorer_toggle_select"),
2388 Action::FileExplorerSelectAll => t!("action.file_explorer_select_all"),
2389 Action::LspCompletion => t!("action.lsp_completion"),
2390 Action::LspGotoDefinition => t!("action.lsp_goto_definition"),
2391 Action::LspReferences => t!("action.lsp_references"),
2392 Action::LspRename => t!("action.lsp_rename"),
2393 Action::LspHover => t!("action.lsp_hover"),
2394 Action::LspSignatureHelp => t!("action.lsp_signature_help"),
2395 Action::LspCodeActions => t!("action.lsp_code_actions"),
2396 Action::LspRestart => t!("action.lsp_restart"),
2397 Action::LspStop => t!("action.lsp_stop"),
2398 Action::LspToggleForBuffer => t!("action.lsp_toggle_for_buffer"),
2399 Action::ToggleInlayHints => t!("action.toggle_inlay_hints"),
2400 Action::ToggleMouseHover => t!("action.toggle_mouse_hover"),
2401 Action::ToggleLineNumbers => t!("action.toggle_line_numbers"),
2402 Action::ToggleScrollSync => t!("action.toggle_scroll_sync"),
2403 Action::ToggleMouseCapture => t!("action.toggle_mouse_capture"),
2404 Action::ToggleDebugHighlights => t!("action.toggle_debug_highlights"),
2405 Action::SetBackground => t!("action.set_background"),
2406 Action::SetBackgroundBlend => t!("action.set_background_blend"),
2407 Action::AddRuler => t!("action.add_ruler"),
2408 Action::RemoveRuler => t!("action.remove_ruler"),
2409 Action::SetTabSize => t!("action.set_tab_size"),
2410 Action::SetLineEnding => t!("action.set_line_ending"),
2411 Action::SetEncoding => t!("action.set_encoding"),
2412 Action::ReloadWithEncoding => t!("action.reload_with_encoding"),
2413 Action::SetLanguage => t!("action.set_language"),
2414 Action::ToggleIndentationStyle => t!("action.toggle_indentation_style"),
2415 Action::ToggleTabIndicators => t!("action.toggle_tab_indicators"),
2416 Action::ToggleWhitespaceIndicators => t!("action.toggle_whitespace_indicators"),
2417 Action::ResetBufferSettings => t!("action.reset_buffer_settings"),
2418 Action::DumpConfig => t!("action.dump_config"),
2419 Action::RedrawScreen => t!("action.redraw_screen"),
2420 Action::Search => t!("action.search"),
2421 Action::FindInSelection => t!("action.find_in_selection"),
2422 Action::FindNext => t!("action.find_next"),
2423 Action::FindPrevious => t!("action.find_previous"),
2424 Action::FindSelectionNext => t!("action.find_selection_next"),
2425 Action::FindSelectionPrevious => t!("action.find_selection_previous"),
2426 Action::Replace => t!("action.replace"),
2427 Action::QueryReplace => t!("action.query_replace"),
2428 Action::MenuActivate => t!("action.menu_activate"),
2429 Action::MenuClose => t!("action.menu_close"),
2430 Action::MenuLeft => t!("action.menu_left"),
2431 Action::MenuRight => t!("action.menu_right"),
2432 Action::MenuUp => t!("action.menu_up"),
2433 Action::MenuDown => t!("action.menu_down"),
2434 Action::MenuExecute => t!("action.menu_execute"),
2435 Action::MenuOpen(name) => t!("action.menu_open", name = name),
2436 Action::SwitchKeybindingMap(map) => t!("action.switch_keybinding_map", map = map),
2437 Action::PluginAction(name) => t!("action.plugin_action", name = name),
2438 Action::ScrollTabsLeft => t!("action.scroll_tabs_left"),
2439 Action::ScrollTabsRight => t!("action.scroll_tabs_right"),
2440 Action::SelectTheme => t!("action.select_theme"),
2441 Action::SelectKeybindingMap => t!("action.select_keybinding_map"),
2442 Action::SelectCursorStyle => t!("action.select_cursor_style"),
2443 Action::SelectLocale => t!("action.select_locale"),
2444 Action::SwitchToPreviousTab => t!("action.switch_to_previous_tab"),
2445 Action::SwitchToTabByName => t!("action.switch_to_tab_by_name"),
2446 Action::OpenTerminal => t!("action.open_terminal"),
2447 Action::CloseTerminal => t!("action.close_terminal"),
2448 Action::FocusTerminal => t!("action.focus_terminal"),
2449 Action::TerminalEscape => t!("action.terminal_escape"),
2450 Action::ToggleKeyboardCapture => t!("action.toggle_keyboard_capture"),
2451 Action::TerminalPaste => t!("action.terminal_paste"),
2452 Action::OpenSettings => t!("action.open_settings"),
2453 Action::CloseSettings => t!("action.close_settings"),
2454 Action::SettingsSave => t!("action.settings_save"),
2455 Action::SettingsReset => t!("action.settings_reset"),
2456 Action::SettingsToggleFocus => t!("action.settings_toggle_focus"),
2457 Action::SettingsActivate => t!("action.settings_activate"),
2458 Action::SettingsSearch => t!("action.settings_search"),
2459 Action::SettingsHelp => t!("action.settings_help"),
2460 Action::SettingsIncrement => t!("action.settings_increment"),
2461 Action::SettingsDecrement => t!("action.settings_decrement"),
2462 Action::SettingsInherit => t!("action.settings_inherit"),
2463 Action::ShellCommand => t!("action.shell_command"),
2464 Action::ShellCommandReplace => t!("action.shell_command_replace"),
2465 Action::ToUpperCase => t!("action.to_uppercase"),
2466 Action::ToLowerCase => t!("action.to_lowercase"),
2467 Action::ToggleCase => t!("action.to_uppercase"),
2468 Action::SortLines => t!("action.sort_lines"),
2469 Action::CalibrateInput => t!("action.calibrate_input"),
2470 Action::EventDebug => t!("action.event_debug"),
2471 Action::SuspendProcess => t!("action.suspend_process"),
2472 Action::LoadPluginFromBuffer => "Load Plugin from Buffer".into(),
2473 Action::InitReload => "Reload init.ts".into(),
2474 Action::InitEdit => "Edit init.ts".into(),
2475 Action::InitCheck => "Check init.ts".into(),
2476 Action::OpenKeybindingEditor => "Keybinding Editor".into(),
2477 Action::CompositeNextHunk => t!("action.composite_next_hunk"),
2478 Action::CompositePrevHunk => t!("action.composite_prev_hunk"),
2479 Action::None => t!("action.none"),
2480 }
2481 .to_string()
2482 }
2483
2484 pub fn parse_key_public(key: &str) -> Option<KeyCode> {
2486 Self::parse_key(key)
2487 }
2488
2489 pub fn parse_modifiers_public(modifiers: &[String]) -> KeyModifiers {
2491 Self::parse_modifiers(modifiers)
2492 }
2493
2494 pub fn format_action_from_str(action_name: &str) -> String {
2498 Self::format_action_from_str_with_args(action_name, &std::collections::HashMap::new())
2499 }
2500
2501 pub fn format_action_from_str_with_args(
2505 action_name: &str,
2506 args: &std::collections::HashMap<String, serde_json::Value>,
2507 ) -> String {
2508 if let Some(action) = Action::from_str(action_name, args) {
2510 Self::format_action(&action)
2511 } else {
2512 action_name
2514 .split('_')
2515 .map(|word| {
2516 let mut chars = word.chars();
2517 match chars.next() {
2518 Some(c) => {
2519 let upper: String = c.to_uppercase().collect();
2520 format!("{}{}", upper, chars.as_str())
2521 }
2522 None => String::new(),
2523 }
2524 })
2525 .collect::<Vec<_>>()
2526 .join(" ")
2527 }
2528 }
2529
2530 pub fn all_action_names() -> Vec<String> {
2534 Action::all_action_names()
2535 }
2536
2537 pub fn get_keybinding_for_action(
2543 &self,
2544 action: &Action,
2545 context: KeyContext,
2546 ) -> Option<String> {
2547 self.get_keybinding_event_for_action(action, context)
2548 .map(|(k, m)| format_keybinding(&k, &m))
2549 }
2550
2551 pub fn get_keybinding_event_for_action(
2558 &self,
2559 action: &Action,
2560 context: KeyContext,
2561 ) -> Option<(KeyCode, KeyModifiers)> {
2562 fn find_best_keybinding(
2564 bindings: &HashMap<(KeyCode, KeyModifiers), Action>,
2565 action: &Action,
2566 ) -> Option<(KeyCode, KeyModifiers)> {
2567 let matches: Vec<_> = bindings
2568 .iter()
2569 .filter(|(_, a)| *a == action)
2570 .map(|((k, m), _)| (*k, *m))
2571 .collect();
2572
2573 if matches.is_empty() {
2574 return None;
2575 }
2576
2577 let mut sorted = matches;
2580 sorted.sort_by(|(k1, m1), (k2, m2)| {
2581 let score1 = keybinding_priority_score(k1);
2582 let score2 = keybinding_priority_score(k2);
2583 match score1.cmp(&score2) {
2585 std::cmp::Ordering::Equal => {
2586 let s1 = format_keybinding(k1, m1);
2588 let s2 = format_keybinding(k2, m2);
2589 s1.cmp(&s2)
2590 }
2591 other => other,
2592 }
2593 });
2594
2595 sorted.into_iter().next()
2596 }
2597
2598 if let Some(context_bindings) = self.bindings.get(&context) {
2600 if let Some(hit) = find_best_keybinding(context_bindings, action) {
2601 return Some(hit);
2602 }
2603 }
2604
2605 if let Some(context_bindings) = self.default_bindings.get(&context) {
2607 if let Some(hit) = find_best_keybinding(context_bindings, action) {
2608 return Some(hit);
2609 }
2610 }
2611
2612 if context != KeyContext::Normal
2614 && (context.allows_normal_fallthrough() || Self::is_application_wide_action(action))
2615 {
2616 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
2618 if let Some(hit) = find_best_keybinding(normal_bindings, action) {
2619 return Some(hit);
2620 }
2621 }
2622
2623 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
2625 if let Some(hit) = find_best_keybinding(normal_bindings, action) {
2626 return Some(hit);
2627 }
2628 }
2629 }
2630
2631 None
2632 }
2633
2634 pub fn reload(&mut self, config: &Config) {
2636 self.bindings.clear();
2637 for binding in &config.keybindings {
2638 if let Some(key_code) = Self::parse_key(&binding.key) {
2639 let modifiers = Self::parse_modifiers(&binding.modifiers);
2640 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
2641 let context = if let Some(ref when) = binding.when {
2643 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
2644 } else {
2645 KeyContext::Normal
2646 };
2647
2648 self.bindings
2649 .entry(context)
2650 .or_default()
2651 .insert((key_code, modifiers), action);
2652 }
2653 }
2654 }
2655 }
2656}
2657
2658#[cfg(test)]
2659mod tests {
2660 use super::*;
2661
2662 #[test]
2663 fn test_parse_key() {
2664 assert_eq!(KeybindingResolver::parse_key("enter"), Some(KeyCode::Enter));
2665 assert_eq!(
2666 KeybindingResolver::parse_key("backspace"),
2667 Some(KeyCode::Backspace)
2668 );
2669 assert_eq!(KeybindingResolver::parse_key("tab"), Some(KeyCode::Tab));
2670 assert_eq!(
2671 KeybindingResolver::parse_key("backtab"),
2672 Some(KeyCode::BackTab)
2673 );
2674 assert_eq!(
2675 KeybindingResolver::parse_key("BackTab"),
2676 Some(KeyCode::BackTab)
2677 );
2678 assert_eq!(KeybindingResolver::parse_key("a"), Some(KeyCode::Char('a')));
2679 }
2680
2681 #[test]
2682 fn test_parse_modifiers() {
2683 let mods = vec!["ctrl".to_string()];
2684 assert_eq!(
2685 KeybindingResolver::parse_modifiers(&mods),
2686 KeyModifiers::CONTROL
2687 );
2688
2689 let mods = vec!["ctrl".to_string(), "shift".to_string()];
2690 assert_eq!(
2691 KeybindingResolver::parse_modifiers(&mods),
2692 KeyModifiers::CONTROL | KeyModifiers::SHIFT
2693 );
2694 }
2695
2696 #[test]
2697 fn test_format_action_from_str_distinguishes_menu_open_by_name() {
2698 let mut file_args = HashMap::new();
2701 file_args.insert(
2702 "name".to_string(),
2703 serde_json::Value::String("File".to_string()),
2704 );
2705 let mut edit_args = HashMap::new();
2706 edit_args.insert(
2707 "name".to_string(),
2708 serde_json::Value::String("Edit".to_string()),
2709 );
2710
2711 let file_display =
2712 KeybindingResolver::format_action_from_str_with_args("menu_open", &file_args);
2713 let edit_display =
2714 KeybindingResolver::format_action_from_str_with_args("menu_open", &edit_args);
2715 let no_args_display = KeybindingResolver::format_action_from_str("menu_open");
2716
2717 assert_ne!(
2718 file_display, edit_display,
2719 "menu_open with different names should produce different descriptions"
2720 );
2721 assert!(
2722 file_display.contains("File"),
2723 "expected the File menu description to contain \"File\", got {file_display:?}"
2724 );
2725 assert!(
2726 edit_display.contains("Edit"),
2727 "expected the Edit menu description to contain \"Edit\", got {edit_display:?}"
2728 );
2729 assert_eq!(no_args_display, "Menu Open");
2733 }
2734
2735 #[test]
2736 fn test_qualify_and_unqualify_roundtrip_menu_open() {
2737 let mut args = HashMap::new();
2738 args.insert(
2739 "name".to_string(),
2740 serde_json::Value::String("File".to_string()),
2741 );
2742
2743 let qualified = Action::qualify_action("menu_open", &args);
2744 assert_eq!(qualified, "menu_open:File");
2745
2746 let (bare, parsed_args) = Action::unqualify_action(&qualified);
2747 assert_eq!(bare, "menu_open");
2748 assert_eq!(
2749 parsed_args.get("name").and_then(|v| v.as_str()),
2750 Some("File")
2751 );
2752 }
2753
2754 #[test]
2755 fn test_qualify_action_passthrough_for_unparameterised() {
2756 let args = HashMap::new();
2758 assert_eq!(Action::qualify_action("save", &args), "save");
2759 let (bare, parsed) = Action::unqualify_action("save");
2760 assert_eq!(bare, "save");
2761 assert!(parsed.is_empty());
2762 }
2763
2764 #[test]
2765 fn test_qualify_action_no_suffix_when_arg_missing() {
2766 let args = HashMap::new();
2769 assert_eq!(Action::qualify_action("menu_open", &args), "menu_open");
2770 }
2771
2772 #[test]
2773 fn test_unqualify_action_ignores_colon_on_unknown_action() {
2774 let (bare, parsed) = Action::unqualify_action("my_plugin:action_with:colons");
2777 assert_eq!(bare, "my_plugin:action_with:colons");
2778 assert!(parsed.is_empty());
2779 }
2780
2781 #[test]
2782 fn test_to_qualified_action_str_for_menu_open() {
2783 let action = Action::MenuOpen("Edit".to_string());
2784 assert_eq!(action.to_qualified_action_str(), "menu_open:Edit");
2785 }
2786
2787 #[test]
2788 fn test_resolve_basic() {
2789 let config = Config::default();
2790 let resolver = KeybindingResolver::new(&config);
2791
2792 let event = KeyEvent::new(KeyCode::Left, KeyModifiers::empty());
2793 assert_eq!(
2794 resolver.resolve(&event, KeyContext::Normal),
2795 Action::MoveLeft
2796 );
2797
2798 let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2799 assert_eq!(
2800 resolver.resolve(&event, KeyContext::Normal),
2801 Action::InsertChar('a')
2802 );
2803 }
2804
2805 #[test]
2806 fn test_shift_backspace_matches_backspace() {
2807 let config = Config::default();
2811 let resolver = KeybindingResolver::new(&config);
2812
2813 let backspace = KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty());
2814 let shift_backspace = KeyEvent::new(KeyCode::Backspace, KeyModifiers::SHIFT);
2815
2816 assert_eq!(
2818 resolver.resolve(&backspace, KeyContext::Normal),
2819 Action::DeleteBackward,
2820 "Backspace should resolve to DeleteBackward in Normal context"
2821 );
2822 assert_eq!(
2823 resolver.resolve(&shift_backspace, KeyContext::Normal),
2824 Action::DeleteBackward,
2825 "Shift+Backspace should resolve to DeleteBackward (same as Backspace) in Normal context"
2826 );
2827
2828 assert_eq!(
2830 resolver.resolve(&backspace, KeyContext::Prompt),
2831 Action::PromptBackspace,
2832 "Backspace should resolve to PromptBackspace in Prompt context"
2833 );
2834 assert_eq!(
2835 resolver.resolve(&shift_backspace, KeyContext::Prompt),
2836 Action::PromptBackspace,
2837 "Shift+Backspace should resolve to PromptBackspace (same as Backspace) in Prompt context"
2838 );
2839
2840 assert_eq!(
2842 resolver.resolve(&backspace, KeyContext::FileExplorer),
2843 Action::FileExplorerSearchBackspace,
2844 "Backspace should resolve to FileExplorerSearchBackspace in FileExplorer context"
2845 );
2846 assert_eq!(
2847 resolver.resolve(&shift_backspace, KeyContext::FileExplorer),
2848 Action::FileExplorerSearchBackspace,
2849 "Shift+Backspace should resolve to FileExplorerSearchBackspace (same as Backspace) in FileExplorer context"
2850 );
2851 }
2852
2853 #[test]
2854 fn test_action_from_str() {
2855 let args = HashMap::new();
2856 assert_eq!(Action::from_str("move_left", &args), Some(Action::MoveLeft));
2857 assert_eq!(Action::from_str("save", &args), Some(Action::Save));
2858 assert_eq!(
2860 Action::from_str("unknown", &args),
2861 Some(Action::PluginAction("unknown".to_string()))
2862 );
2863
2864 assert_eq!(
2866 Action::from_str("keyboard_shortcuts", &args),
2867 Some(Action::ShowKeyboardShortcuts)
2868 );
2869 assert_eq!(
2870 Action::from_str("prompt_confirm", &args),
2871 Some(Action::PromptConfirm)
2872 );
2873 assert_eq!(
2874 Action::from_str("popup_cancel", &args),
2875 Some(Action::PopupCancel)
2876 );
2877
2878 assert_eq!(
2880 Action::from_str("calibrate_input", &args),
2881 Some(Action::CalibrateInput)
2882 );
2883 }
2884
2885 #[test]
2886 fn test_key_context_from_when_clause() {
2887 assert_eq!(
2888 KeyContext::from_when_clause("normal"),
2889 Some(KeyContext::Normal)
2890 );
2891 assert_eq!(
2892 KeyContext::from_when_clause("prompt"),
2893 Some(KeyContext::Prompt)
2894 );
2895 assert_eq!(
2896 KeyContext::from_when_clause("popup"),
2897 Some(KeyContext::Popup)
2898 );
2899 assert_eq!(KeyContext::from_when_clause("help"), None);
2900 assert_eq!(KeyContext::from_when_clause(" help "), None); assert_eq!(KeyContext::from_when_clause("unknown"), None);
2902 assert_eq!(KeyContext::from_when_clause(""), None);
2903 }
2904
2905 #[test]
2906 fn test_key_context_to_when_clause() {
2907 assert_eq!(KeyContext::Normal.to_when_clause(), "normal");
2908 assert_eq!(KeyContext::Prompt.to_when_clause(), "prompt");
2909 assert_eq!(KeyContext::Popup.to_when_clause(), "popup");
2910 }
2911
2912 #[test]
2913 fn test_context_specific_bindings() {
2914 let config = Config::default();
2915 let resolver = KeybindingResolver::new(&config);
2916
2917 let enter_event = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
2919 assert_eq!(
2920 resolver.resolve(&enter_event, KeyContext::Prompt),
2921 Action::PromptConfirm
2922 );
2923 assert_eq!(
2924 resolver.resolve(&enter_event, KeyContext::Normal),
2925 Action::InsertNewline
2926 );
2927
2928 let up_event = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
2930 assert_eq!(
2931 resolver.resolve(&up_event, KeyContext::Popup),
2932 Action::PopupSelectPrev
2933 );
2934 assert_eq!(
2935 resolver.resolve(&up_event, KeyContext::Normal),
2936 Action::MoveUp
2937 );
2938 }
2939
2940 #[test]
2941 fn test_context_fallback_to_normal() {
2942 let config = Config::default();
2943 let resolver = KeybindingResolver::new(&config);
2944
2945 let save_event = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL);
2947 assert_eq!(
2948 resolver.resolve(&save_event, KeyContext::Normal),
2949 Action::Save
2950 );
2951 assert_eq!(
2952 resolver.resolve(&save_event, KeyContext::Popup),
2953 Action::Save
2954 );
2955 }
2957
2958 #[test]
2959 fn test_context_priority_resolution() {
2960 use crate::config::Keybinding;
2961
2962 let mut config = Config::default();
2964 config.keybindings.push(Keybinding {
2965 key: "esc".to_string(),
2966 modifiers: vec![],
2967 keys: vec![],
2968 action: "quit".to_string(), args: HashMap::new(),
2970 when: Some("popup".to_string()),
2971 });
2972
2973 let resolver = KeybindingResolver::new(&config);
2974 let esc_event = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
2975
2976 assert_eq!(
2978 resolver.resolve(&esc_event, KeyContext::Popup),
2979 Action::Quit
2980 );
2981
2982 assert_eq!(
2984 resolver.resolve(&esc_event, KeyContext::Normal),
2985 Action::RemoveSecondaryCursors
2986 );
2987 }
2988
2989 #[test]
2990 fn test_character_input_in_contexts() {
2991 let config = Config::default();
2992 let resolver = KeybindingResolver::new(&config);
2993
2994 let char_event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2995
2996 assert_eq!(
2998 resolver.resolve(&char_event, KeyContext::Normal),
2999 Action::InsertChar('a')
3000 );
3001 assert_eq!(
3002 resolver.resolve(&char_event, KeyContext::Prompt),
3003 Action::InsertChar('a')
3004 );
3005
3006 assert_eq!(
3008 resolver.resolve(&char_event, KeyContext::Popup),
3009 Action::None
3010 );
3011 }
3012
3013 #[test]
3014 fn test_custom_keybinding_loading() {
3015 use crate::config::Keybinding;
3016
3017 let mut config = Config::default();
3018
3019 config.keybindings.push(Keybinding {
3021 key: "f".to_string(),
3022 modifiers: vec!["ctrl".to_string()],
3023 keys: vec![],
3024 action: "command_palette".to_string(),
3025 args: HashMap::new(),
3026 when: None, });
3028
3029 let resolver = KeybindingResolver::new(&config);
3030
3031 let ctrl_f = KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL);
3033 assert_eq!(
3034 resolver.resolve(&ctrl_f, KeyContext::Normal),
3035 Action::CommandPalette
3036 );
3037
3038 let ctrl_k = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
3040 assert_eq!(
3041 resolver.resolve(&ctrl_k, KeyContext::Prompt),
3042 Action::PromptDeleteToLineEnd
3043 );
3044 assert_eq!(
3045 resolver.resolve(&ctrl_k, KeyContext::Normal),
3046 Action::DeleteToLineEnd
3047 );
3048 }
3049
3050 #[test]
3051 fn test_all_context_default_bindings_exist() {
3052 let config = Config::default();
3053 let resolver = KeybindingResolver::new(&config);
3054
3055 assert!(resolver.default_bindings.contains_key(&KeyContext::Normal));
3057 assert!(resolver.default_bindings.contains_key(&KeyContext::Prompt));
3058 assert!(resolver.default_bindings.contains_key(&KeyContext::Popup));
3059 assert!(resolver
3060 .default_bindings
3061 .contains_key(&KeyContext::FileExplorer));
3062 assert!(resolver.default_bindings.contains_key(&KeyContext::Menu));
3063
3064 assert!(!resolver.default_bindings[&KeyContext::Normal].is_empty());
3066 assert!(!resolver.default_bindings[&KeyContext::Prompt].is_empty());
3067 assert!(!resolver.default_bindings[&KeyContext::Popup].is_empty());
3068 assert!(!resolver.default_bindings[&KeyContext::FileExplorer].is_empty());
3069 assert!(!resolver.default_bindings[&KeyContext::Menu].is_empty());
3070 }
3071
3072 #[test]
3076 fn test_all_builtin_keymaps_have_valid_action_names() {
3077 let known_actions: std::collections::HashSet<String> =
3078 Action::all_action_names().into_iter().collect();
3079
3080 let config = Config::default();
3081
3082 for map_name in crate::config::KeybindingMapName::BUILTIN_OPTIONS {
3083 let bindings = config.resolve_keymap(map_name);
3084 for binding in &bindings {
3085 assert!(
3086 known_actions.contains(&binding.action),
3087 "Keymap '{}' contains unknown action '{}' (key: '{}', when: {:?}). \
3088 This will be treated as a plugin action at runtime. \
3089 Check for typos in the keymap JSON file.",
3090 map_name,
3091 binding.action,
3092 binding.key,
3093 binding.when,
3094 );
3095 }
3096 }
3097 }
3098
3099 #[test]
3100 fn test_resolve_determinism() {
3101 let config = Config::default();
3103 let resolver = KeybindingResolver::new(&config);
3104
3105 let test_cases = vec![
3106 (KeyCode::Left, KeyModifiers::empty(), KeyContext::Normal),
3107 (
3108 KeyCode::Esc,
3109 KeyModifiers::empty(),
3110 KeyContext::FileExplorer,
3111 ),
3112 (KeyCode::Enter, KeyModifiers::empty(), KeyContext::Prompt),
3113 (KeyCode::Down, KeyModifiers::empty(), KeyContext::Popup),
3114 ];
3115
3116 for (key_code, modifiers, context) in test_cases {
3117 let event = KeyEvent::new(key_code, modifiers);
3118 let action1 = resolver.resolve(&event, context.clone());
3119 let action2 = resolver.resolve(&event, context.clone());
3120 let action3 = resolver.resolve(&event, context);
3121
3122 assert_eq!(action1, action2, "Resolve should be deterministic");
3123 assert_eq!(action2, action3, "Resolve should be deterministic");
3124 }
3125 }
3126
3127 #[test]
3128 fn test_modifier_combinations() {
3129 let config = Config::default();
3130 let resolver = KeybindingResolver::new(&config);
3131
3132 let char_s = KeyCode::Char('s');
3134
3135 let no_mod = KeyEvent::new(char_s, KeyModifiers::empty());
3136 let ctrl = KeyEvent::new(char_s, KeyModifiers::CONTROL);
3137 let shift = KeyEvent::new(char_s, KeyModifiers::SHIFT);
3138 let ctrl_shift = KeyEvent::new(char_s, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
3139
3140 let action_no_mod = resolver.resolve(&no_mod, KeyContext::Normal);
3141 let action_ctrl = resolver.resolve(&ctrl, KeyContext::Normal);
3142 let action_shift = resolver.resolve(&shift, KeyContext::Normal);
3143 let action_ctrl_shift = resolver.resolve(&ctrl_shift, KeyContext::Normal);
3144
3145 assert_eq!(action_no_mod, Action::InsertChar('s'));
3147 assert_eq!(action_ctrl, Action::Save);
3148 assert_eq!(action_shift, Action::InsertChar('s')); assert_eq!(action_ctrl_shift, Action::None);
3151 }
3152
3153 #[test]
3154 fn test_scroll_keybindings() {
3155 let config = Config::default();
3156 let resolver = KeybindingResolver::new(&config);
3157
3158 let ctrl_up = KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL);
3160 assert_eq!(
3161 resolver.resolve(&ctrl_up, KeyContext::Normal),
3162 Action::ScrollUp,
3163 "Ctrl+Up should resolve to ScrollUp"
3164 );
3165
3166 let ctrl_down = KeyEvent::new(KeyCode::Down, KeyModifiers::CONTROL);
3168 assert_eq!(
3169 resolver.resolve(&ctrl_down, KeyContext::Normal),
3170 Action::ScrollDown,
3171 "Ctrl+Down should resolve to ScrollDown"
3172 );
3173 }
3174
3175 #[test]
3176 fn test_lsp_completion_keybinding() {
3177 let config = Config::default();
3178 let resolver = KeybindingResolver::new(&config);
3179
3180 let ctrl_space = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::CONTROL);
3182 assert_eq!(
3183 resolver.resolve(&ctrl_space, KeyContext::Normal),
3184 Action::LspCompletion,
3185 "Ctrl+Space should resolve to LspCompletion"
3186 );
3187 }
3188
3189 #[test]
3190 fn test_terminal_key_equivalents() {
3191 let ctrl = KeyModifiers::CONTROL;
3193
3194 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
3196 assert_eq!(slash_equivs, vec![(KeyCode::Char('7'), ctrl)]);
3197
3198 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
3199 assert_eq!(seven_equivs, vec![(KeyCode::Char('/'), ctrl)]);
3200
3201 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
3203 assert_eq!(backspace_equivs, vec![(KeyCode::Char('h'), ctrl)]);
3204
3205 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
3206 assert_eq!(h_equivs, vec![(KeyCode::Backspace, ctrl)]);
3207
3208 let a_equivs = terminal_key_equivalents(KeyCode::Char('a'), ctrl);
3210 assert!(a_equivs.is_empty());
3211
3212 let slash_no_ctrl = terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty());
3214 assert!(slash_no_ctrl.is_empty());
3215 }
3216
3217 #[test]
3218 fn test_terminal_key_equivalents_auto_binding() {
3219 let config = Config::default();
3220 let resolver = KeybindingResolver::new(&config);
3221
3222 let ctrl_slash = KeyEvent::new(KeyCode::Char('/'), KeyModifiers::CONTROL);
3224 let action_slash = resolver.resolve(&ctrl_slash, KeyContext::Normal);
3225 assert_eq!(
3226 action_slash,
3227 Action::ToggleComment,
3228 "Ctrl+/ should resolve to ToggleComment"
3229 );
3230
3231 let ctrl_7 = KeyEvent::new(KeyCode::Char('7'), KeyModifiers::CONTROL);
3233 let action_7 = resolver.resolve(&ctrl_7, KeyContext::Normal);
3234 assert_eq!(
3235 action_7,
3236 Action::ToggleComment,
3237 "Ctrl+7 should resolve to ToggleComment (terminal equivalent of Ctrl+/)"
3238 );
3239 }
3240
3241 #[test]
3242 fn test_terminal_key_equivalents_normalization() {
3243 let ctrl = KeyModifiers::CONTROL;
3248
3249 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
3252 assert_eq!(
3253 slash_equivs,
3254 vec![(KeyCode::Char('7'), ctrl)],
3255 "Ctrl+/ should map to Ctrl+7"
3256 );
3257 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
3258 assert_eq!(
3259 seven_equivs,
3260 vec![(KeyCode::Char('/'), ctrl)],
3261 "Ctrl+7 should map back to Ctrl+/"
3262 );
3263
3264 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
3267 assert_eq!(
3268 backspace_equivs,
3269 vec![(KeyCode::Char('h'), ctrl)],
3270 "Ctrl+Backspace should map to Ctrl+H"
3271 );
3272 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
3273 assert_eq!(
3274 h_equivs,
3275 vec![(KeyCode::Backspace, ctrl)],
3276 "Ctrl+H should map back to Ctrl+Backspace"
3277 );
3278
3279 let space_equivs = terminal_key_equivalents(KeyCode::Char(' '), ctrl);
3282 assert_eq!(
3283 space_equivs,
3284 vec![(KeyCode::Char('@'), ctrl)],
3285 "Ctrl+Space should map to Ctrl+@"
3286 );
3287 let at_equivs = terminal_key_equivalents(KeyCode::Char('@'), ctrl);
3288 assert_eq!(
3289 at_equivs,
3290 vec![(KeyCode::Char(' '), ctrl)],
3291 "Ctrl+@ should map back to Ctrl+Space"
3292 );
3293
3294 let minus_equivs = terminal_key_equivalents(KeyCode::Char('-'), ctrl);
3297 assert_eq!(
3298 minus_equivs,
3299 vec![(KeyCode::Char('_'), ctrl)],
3300 "Ctrl+- should map to Ctrl+_"
3301 );
3302 let underscore_equivs = terminal_key_equivalents(KeyCode::Char('_'), ctrl);
3303 assert_eq!(
3304 underscore_equivs,
3305 vec![(KeyCode::Char('-'), ctrl)],
3306 "Ctrl+_ should map back to Ctrl+-"
3307 );
3308
3309 assert!(
3311 terminal_key_equivalents(KeyCode::Char('a'), ctrl).is_empty(),
3312 "Ctrl+A should have no terminal equivalents"
3313 );
3314 assert!(
3315 terminal_key_equivalents(KeyCode::Char('z'), ctrl).is_empty(),
3316 "Ctrl+Z should have no terminal equivalents"
3317 );
3318 assert!(
3319 terminal_key_equivalents(KeyCode::Enter, ctrl).is_empty(),
3320 "Ctrl+Enter should have no terminal equivalents"
3321 );
3322
3323 assert!(
3325 terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty()).is_empty(),
3326 "/ without Ctrl should have no equivalents"
3327 );
3328 assert!(
3329 terminal_key_equivalents(KeyCode::Char('7'), KeyModifiers::SHIFT).is_empty(),
3330 "Shift+7 should have no equivalents"
3331 );
3332 assert!(
3333 terminal_key_equivalents(KeyCode::Char('h'), KeyModifiers::ALT).is_empty(),
3334 "Alt+H should have no equivalents"
3335 );
3336
3337 let ctrl_shift = KeyModifiers::CONTROL | KeyModifiers::SHIFT;
3340 let ctrl_shift_h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl_shift);
3341 assert!(
3342 ctrl_shift_h_equivs.is_empty(),
3343 "Ctrl+Shift+H should NOT map to Ctrl+Shift+Backspace"
3344 );
3345 }
3346
3347 #[test]
3348 fn test_no_duplicate_keybindings_in_keymaps() {
3349 use std::collections::HashMap;
3352
3353 let keymaps: &[(&str, &str)] = &[
3354 ("default", include_str!("../../keymaps/default.json")),
3355 ("macos", include_str!("../../keymaps/macos.json")),
3356 ];
3357
3358 for (keymap_name, json_content) in keymaps {
3359 let keymap: crate::config::KeymapConfig = serde_json::from_str(json_content)
3360 .unwrap_or_else(|e| panic!("Failed to parse keymap '{}': {}", keymap_name, e));
3361
3362 let mut seen: HashMap<(String, Vec<String>, String), String> = HashMap::new();
3364 let mut duplicates: Vec<String> = Vec::new();
3365
3366 for binding in &keymap.bindings {
3367 let when = binding.when.clone().unwrap_or_default();
3368 let key_id = (binding.key.clone(), binding.modifiers.clone(), when.clone());
3369
3370 if let Some(existing_action) = seen.get(&key_id) {
3371 duplicates.push(format!(
3372 "Duplicate in '{}': key='{}', modifiers={:?}, when='{}' -> '{}' vs '{}'",
3373 keymap_name,
3374 binding.key,
3375 binding.modifiers,
3376 when,
3377 existing_action,
3378 binding.action
3379 ));
3380 } else {
3381 seen.insert(key_id, binding.action.clone());
3382 }
3383 }
3384
3385 assert!(
3386 duplicates.is_empty(),
3387 "Found duplicate keybindings:\n{}",
3388 duplicates.join("\n")
3389 );
3390 }
3391 }
3392}