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
7static FORCE_LINUX_KEYBINDINGS: AtomicBool = AtomicBool::new(false);
10
11pub fn set_force_linux_keybindings(force: bool) {
14 FORCE_LINUX_KEYBINDINGS.store(force, Ordering::SeqCst);
15}
16
17fn use_macos_symbols() -> bool {
19 if FORCE_LINUX_KEYBINDINGS.load(Ordering::SeqCst) {
20 return false;
21 }
22 cfg!(target_os = "macos")
23}
24
25fn is_text_input_modifier(modifiers: KeyModifiers) -> bool {
36 if modifiers.is_empty() || modifiers == KeyModifiers::SHIFT {
37 return true;
38 }
39
40 #[cfg(windows)]
44 if modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT)
45 || modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT)
46 {
47 return true;
48 }
49
50 false
51}
52
53pub fn format_keybinding(keycode: &KeyCode, modifiers: &KeyModifiers) -> String {
57 let mut result = String::new();
58
59 let (ctrl_label, alt_label, shift_label, super_label) = if use_macos_symbols() {
61 ("⌃", "⌥", "⇧", "⌘")
62 } else {
63 ("Ctrl", "Alt", "Shift", "Super")
64 };
65
66 let use_plus = !use_macos_symbols();
67
68 if modifiers.contains(KeyModifiers::SUPER) {
69 result.push_str(super_label);
70 if use_plus {
71 result.push('+');
72 }
73 }
74 if modifiers.contains(KeyModifiers::CONTROL) {
75 result.push_str(ctrl_label);
76 if use_plus {
77 result.push('+');
78 }
79 }
80 if modifiers.contains(KeyModifiers::ALT) {
81 result.push_str(alt_label);
82 if use_plus {
83 result.push('+');
84 }
85 }
86 if modifiers.contains(KeyModifiers::SHIFT) {
87 result.push_str(shift_label);
88 if use_plus {
89 result.push('+');
90 }
91 }
92
93 match keycode {
94 KeyCode::Enter => result.push_str("Enter"),
95 KeyCode::Backspace => result.push_str("Backspace"),
96 KeyCode::Delete => result.push_str("Del"),
97 KeyCode::Tab => result.push_str("Tab"),
98 KeyCode::Esc => result.push_str("Esc"),
99 KeyCode::Left => result.push('←'),
100 KeyCode::Right => result.push('→'),
101 KeyCode::Up => result.push('↑'),
102 KeyCode::Down => result.push('↓'),
103 KeyCode::Home => result.push_str("Home"),
104 KeyCode::End => result.push_str("End"),
105 KeyCode::PageUp => result.push_str("PgUp"),
106 KeyCode::PageDown => result.push_str("PgDn"),
107 KeyCode::Char(' ') => result.push_str("Space"),
108 KeyCode::Char(c) => result.push_str(&c.to_uppercase().to_string()),
109 KeyCode::F(n) => result.push_str(&format!("F{}", n)),
110 _ => return String::new(),
111 }
112
113 result
114}
115
116fn keybinding_priority_score(key: &KeyCode) -> u32 {
120 match key {
121 KeyCode::Char('@') => 100, KeyCode::Char('7') => 100, KeyCode::Char('_') => 100, _ => 0,
128 }
129}
130
131pub fn terminal_key_equivalents(
142 key: KeyCode,
143 modifiers: KeyModifiers,
144) -> Vec<(KeyCode, KeyModifiers)> {
145 let mut equivalents = Vec::new();
146
147 if modifiers.contains(KeyModifiers::CONTROL) {
149 let base_modifiers = modifiers; match key {
152 KeyCode::Char('/') => {
154 equivalents.push((KeyCode::Char('7'), base_modifiers));
155 }
156 KeyCode::Char('7') => {
157 equivalents.push((KeyCode::Char('/'), base_modifiers));
158 }
159
160 KeyCode::Backspace => {
162 equivalents.push((KeyCode::Char('h'), base_modifiers));
163 }
164 KeyCode::Char('h') if modifiers == KeyModifiers::CONTROL => {
165 equivalents.push((KeyCode::Backspace, base_modifiers));
167 }
168
169 KeyCode::Char(' ') => {
171 equivalents.push((KeyCode::Char('@'), base_modifiers));
172 }
173 KeyCode::Char('@') => {
174 equivalents.push((KeyCode::Char(' '), base_modifiers));
175 }
176
177 KeyCode::Char('-') => {
179 equivalents.push((KeyCode::Char('_'), base_modifiers));
180 }
181 KeyCode::Char('_') => {
182 equivalents.push((KeyCode::Char('-'), base_modifiers));
183 }
184
185 _ => {}
186 }
187 }
188
189 equivalents
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
194pub enum KeyContext {
195 Global,
197 Normal,
199 Prompt,
201 Popup,
203 FileExplorer,
205 Menu,
207 Terminal,
209 Settings,
211}
212
213impl KeyContext {
214 pub fn allows_text_input(&self) -> bool {
216 matches!(self, Self::Normal | Self::Prompt | Self::FileExplorer)
217 }
218
219 pub fn from_when_clause(when: &str) -> Option<Self> {
221 Some(match when.trim() {
222 "global" => Self::Global,
223 "prompt" => Self::Prompt,
224 "popup" => Self::Popup,
225 "fileExplorer" | "file_explorer" => Self::FileExplorer,
226 "normal" => Self::Normal,
227 "menu" => Self::Menu,
228 "terminal" => Self::Terminal,
229 "settings" => Self::Settings,
230 _ => return None,
231 })
232 }
233
234 pub fn to_when_clause(self) -> &'static str {
236 match self {
237 Self::Global => "global",
238 Self::Normal => "normal",
239 Self::Prompt => "prompt",
240 Self::Popup => "popup",
241 Self::FileExplorer => "fileExplorer",
242 Self::Menu => "menu",
243 Self::Terminal => "terminal",
244 Self::Settings => "settings",
245 }
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
251pub enum Action {
252 InsertChar(char),
254 InsertNewline,
255 InsertTab,
256
257 MoveLeft,
259 MoveRight,
260 MoveUp,
261 MoveDown,
262 MoveWordLeft,
263 MoveWordRight,
264 MoveWordEnd, MoveLineStart,
266 MoveLineEnd,
267 MoveLineUp,
268 MoveLineDown,
269 MovePageUp,
270 MovePageDown,
271 MoveDocumentStart,
272 MoveDocumentEnd,
273
274 SelectLeft,
276 SelectRight,
277 SelectUp,
278 SelectDown,
279 SelectToParagraphUp, SelectToParagraphDown, SelectWordLeft,
282 SelectWordRight,
283 SelectWordEnd, SelectLineStart,
285 SelectLineEnd,
286 SelectDocumentStart,
287 SelectDocumentEnd,
288 SelectPageUp,
289 SelectPageDown,
290 SelectAll,
291 SelectWord,
292 SelectLine,
293 ExpandSelection,
294
295 BlockSelectLeft,
297 BlockSelectRight,
298 BlockSelectUp,
299 BlockSelectDown,
300
301 DeleteBackward,
303 DeleteForward,
304 DeleteWordBackward,
305 DeleteWordForward,
306 DeleteLine,
307 DeleteToLineEnd,
308 DeleteToLineStart,
309 TransposeChars,
310 OpenLine,
311 DuplicateLine,
312
313 Recenter,
315
316 SetMark,
318
319 Copy,
321 CopyWithTheme(String),
322 Cut,
323 Paste,
324
325 YankWordForward,
327 YankWordBackward,
328 YankToLineEnd,
329 YankToLineStart,
330
331 AddCursorAbove,
333 AddCursorBelow,
334 AddCursorNextMatch,
335 RemoveSecondaryCursors,
336
337 Save,
339 SaveAs,
340 Open,
341 SwitchProject,
342 New,
343 Close,
344 CloseTab,
345 Quit,
346 ForceQuit,
347 Detach,
348 Revert,
349 ToggleAutoRevert,
350 FormatBuffer,
351 TrimTrailingWhitespace,
352 EnsureFinalNewline,
353
354 GotoLine,
356 ScanLineIndex,
357 GoToMatchingBracket,
358 JumpToNextError,
359 JumpToPreviousError,
360
361 SmartHome,
363 DedentSelection,
364 ToggleComment,
365 ToggleFold,
366
367 SetBookmark(char),
369 JumpToBookmark(char),
370 ClearBookmark(char),
371 ListBookmarks,
372
373 ToggleSearchCaseSensitive,
375 ToggleSearchWholeWord,
376 ToggleSearchRegex,
377 ToggleSearchConfirmEach,
378
379 StartMacroRecording,
381 StopMacroRecording,
382 PlayMacro(char),
383 ToggleMacroRecording(char),
384 ShowMacro(char),
385 ListMacros,
386 PromptRecordMacro,
387 PromptPlayMacro,
388 PlayLastMacro,
389
390 PromptSetBookmark,
392 PromptJumpToBookmark,
393
394 Undo,
396 Redo,
397
398 ScrollUp,
400 ScrollDown,
401 ShowHelp,
402 ShowKeyboardShortcuts,
403 ShowWarnings,
404 ShowStatusLog,
405 ShowLspStatus,
406 ClearWarnings,
407 CommandPalette, QuickOpen,
410 ToggleLineWrap,
411 ToggleReadOnly,
412 ToggleComposeMode,
413 SetComposeWidth,
414 InspectThemeAtCursor,
415 SelectTheme,
416 SelectKeybindingMap,
417 SelectCursorStyle,
418 SelectLocale,
419
420 NextBuffer,
422 PrevBuffer,
423 SwitchToPreviousTab,
424 SwitchToTabByName,
425
426 ScrollTabsLeft,
428 ScrollTabsRight,
429
430 NavigateBack,
432 NavigateForward,
433
434 SplitHorizontal,
436 SplitVertical,
437 CloseSplit,
438 NextSplit,
439 PrevSplit,
440 IncreaseSplitSize,
441 DecreaseSplitSize,
442 ToggleMaximizeSplit,
443
444 PromptConfirm,
446 PromptConfirmWithText(String),
448 PromptCancel,
449 PromptBackspace,
450 PromptDelete,
451 PromptMoveLeft,
452 PromptMoveRight,
453 PromptMoveStart,
454 PromptMoveEnd,
455 PromptSelectPrev,
456 PromptSelectNext,
457 PromptPageUp,
458 PromptPageDown,
459 PromptAcceptSuggestion,
460 PromptMoveWordLeft,
461 PromptMoveWordRight,
462 PromptDeleteWordForward,
464 PromptDeleteWordBackward,
465 PromptDeleteToLineEnd,
466 PromptCopy,
467 PromptCut,
468 PromptPaste,
469 PromptMoveLeftSelecting,
471 PromptMoveRightSelecting,
472 PromptMoveHomeSelecting,
473 PromptMoveEndSelecting,
474 PromptSelectWordLeft,
475 PromptSelectWordRight,
476 PromptSelectAll,
477
478 FileBrowserToggleHidden,
480 FileBrowserToggleDetectEncoding,
481
482 PopupSelectNext,
484 PopupSelectPrev,
485 PopupPageUp,
486 PopupPageDown,
487 PopupConfirm,
488 PopupCancel,
489
490 ToggleFileExplorer,
492 ToggleMenuBar,
494 ToggleTabBar,
496 ToggleStatusBar,
498 ToggleVerticalScrollbar,
500 ToggleHorizontalScrollbar,
501 FocusFileExplorer,
502 FocusEditor,
503 FileExplorerUp,
504 FileExplorerDown,
505 FileExplorerPageUp,
506 FileExplorerPageDown,
507 FileExplorerExpand,
508 FileExplorerCollapse,
509 FileExplorerOpen,
510 FileExplorerRefresh,
511 FileExplorerNewFile,
512 FileExplorerNewDirectory,
513 FileExplorerDelete,
514 FileExplorerRename,
515 FileExplorerToggleHidden,
516 FileExplorerToggleGitignored,
517 FileExplorerSearchClear,
518 FileExplorerSearchBackspace,
519
520 LspCompletion,
522 LspGotoDefinition,
523 LspReferences,
524 LspRename,
525 LspHover,
526 LspSignatureHelp,
527 LspCodeActions,
528 LspRestart,
529 LspStop,
530 LspToggleForBuffer,
531 ToggleInlayHints,
532 ToggleMouseHover,
533
534 ToggleLineNumbers,
536 ToggleScrollSync,
537 ToggleMouseCapture,
538 ToggleDebugHighlights, SetBackground,
540 SetBackgroundBlend,
541
542 SetTabSize,
544 SetLineEnding,
545 SetEncoding,
546 ReloadWithEncoding,
547 SetLanguage,
548 ToggleIndentationStyle,
549 ToggleTabIndicators,
550 ToggleWhitespaceIndicators,
551 ResetBufferSettings,
552 AddRuler,
553 RemoveRuler,
554
555 DumpConfig,
557
558 Search,
560 FindInSelection,
561 FindNext,
562 FindPrevious,
563 FindSelectionNext, FindSelectionPrevious, Replace,
566 QueryReplace, MenuActivate, MenuClose, MenuLeft, MenuRight, MenuUp, MenuDown, MenuExecute, MenuOpen(String), SwitchKeybindingMap(String), PluginAction(String),
583
584 OpenSettings, CloseSettings, SettingsSave, SettingsReset, SettingsToggleFocus, SettingsActivate, SettingsSearch, SettingsHelp, SettingsIncrement, SettingsDecrement, OpenTerminal, CloseTerminal, FocusTerminal, TerminalEscape, ToggleKeyboardCapture, TerminalPaste, ShellCommand, ShellCommandReplace, ToUpperCase, ToLowerCase, SortLines, CalibrateInput, EventDebug, OpenKeybindingEditor, LoadPluginFromBuffer, None,
627}
628
629macro_rules! define_action_str_mapping {
641 (
642 $args_name:ident;
643 simple { $($s_name:literal => $s_variant:ident),* $(,)? }
644 with_char { $($c_name:literal => $c_variant:ident),* $(,)? }
645 custom { $($x_name:literal => $x_body:expr),* $(,)? }
646 ) => {
647 pub fn from_str(s: &str, $args_name: &HashMap<String, serde_json::Value>) -> Option<Self> {
649 Some(match s {
650 $($s_name => Self::$s_variant,)*
651 $($c_name => return Self::with_char($args_name, Self::$c_variant),)*
652 $($x_name => $x_body,)*
653 _ => return None,
654 })
655 }
656
657 pub fn all_action_names() -> Vec<String> {
660 let mut names = vec![
661 $($s_name.to_string(),)*
662 $($c_name.to_string(),)*
663 $($x_name.to_string(),)*
664 ];
665 names.sort();
666 names
667 }
668 };
669}
670
671impl Action {
672 fn with_char(
673 args: &HashMap<String, serde_json::Value>,
674 make_action: impl FnOnce(char) -> Self,
675 ) -> Option<Self> {
676 if let Some(serde_json::Value::String(value)) = args.get("char") {
677 value.chars().next().map(make_action)
678 } else {
679 None
680 }
681 }
682
683 define_action_str_mapping! {
684 args;
685 simple {
686 "insert_newline" => InsertNewline,
687 "insert_tab" => InsertTab,
688
689 "move_left" => MoveLeft,
690 "move_right" => MoveRight,
691 "move_up" => MoveUp,
692 "move_down" => MoveDown,
693 "move_word_left" => MoveWordLeft,
694 "move_word_right" => MoveWordRight,
695 "move_word_end" => MoveWordEnd,
696 "move_line_start" => MoveLineStart,
697 "move_line_end" => MoveLineEnd,
698 "move_line_up" => MoveLineUp,
699 "move_line_down" => MoveLineDown,
700 "move_page_up" => MovePageUp,
701 "move_page_down" => MovePageDown,
702 "move_document_start" => MoveDocumentStart,
703 "move_document_end" => MoveDocumentEnd,
704
705 "select_left" => SelectLeft,
706 "select_right" => SelectRight,
707 "select_up" => SelectUp,
708 "select_down" => SelectDown,
709 "select_to_paragraph_up" => SelectToParagraphUp,
710 "select_to_paragraph_down" => SelectToParagraphDown,
711 "select_word_left" => SelectWordLeft,
712 "select_word_right" => SelectWordRight,
713 "select_word_end" => SelectWordEnd,
714 "select_line_start" => SelectLineStart,
715 "select_line_end" => SelectLineEnd,
716 "select_document_start" => SelectDocumentStart,
717 "select_document_end" => SelectDocumentEnd,
718 "select_page_up" => SelectPageUp,
719 "select_page_down" => SelectPageDown,
720 "select_all" => SelectAll,
721 "select_word" => SelectWord,
722 "select_line" => SelectLine,
723 "expand_selection" => ExpandSelection,
724
725 "block_select_left" => BlockSelectLeft,
726 "block_select_right" => BlockSelectRight,
727 "block_select_up" => BlockSelectUp,
728 "block_select_down" => BlockSelectDown,
729
730 "delete_backward" => DeleteBackward,
731 "delete_forward" => DeleteForward,
732 "delete_word_backward" => DeleteWordBackward,
733 "delete_word_forward" => DeleteWordForward,
734 "delete_line" => DeleteLine,
735 "delete_to_line_end" => DeleteToLineEnd,
736 "delete_to_line_start" => DeleteToLineStart,
737 "transpose_chars" => TransposeChars,
738 "open_line" => OpenLine,
739 "duplicate_line" => DuplicateLine,
740 "recenter" => Recenter,
741 "set_mark" => SetMark,
742
743 "copy" => Copy,
744 "cut" => Cut,
745 "paste" => Paste,
746
747 "yank_word_forward" => YankWordForward,
748 "yank_word_backward" => YankWordBackward,
749 "yank_to_line_end" => YankToLineEnd,
750 "yank_to_line_start" => YankToLineStart,
751
752 "add_cursor_above" => AddCursorAbove,
753 "add_cursor_below" => AddCursorBelow,
754 "add_cursor_next_match" => AddCursorNextMatch,
755 "remove_secondary_cursors" => RemoveSecondaryCursors,
756
757 "save" => Save,
758 "save_as" => SaveAs,
759 "open" => Open,
760 "switch_project" => SwitchProject,
761 "new" => New,
762 "close" => Close,
763 "close_tab" => CloseTab,
764 "quit" => Quit,
765 "force_quit" => ForceQuit,
766 "detach" => Detach,
767 "revert" => Revert,
768 "toggle_auto_revert" => ToggleAutoRevert,
769 "format_buffer" => FormatBuffer,
770 "goto_line" => GotoLine,
771 "scan_line_index" => ScanLineIndex,
772 "goto_matching_bracket" => GoToMatchingBracket,
773 "jump_to_next_error" => JumpToNextError,
774 "jump_to_previous_error" => JumpToPreviousError,
775
776 "smart_home" => SmartHome,
777 "dedent_selection" => DedentSelection,
778 "toggle_comment" => ToggleComment,
779 "toggle_fold" => ToggleFold,
780
781 "list_bookmarks" => ListBookmarks,
782
783 "toggle_search_case_sensitive" => ToggleSearchCaseSensitive,
784 "toggle_search_whole_word" => ToggleSearchWholeWord,
785 "toggle_search_regex" => ToggleSearchRegex,
786 "toggle_search_confirm_each" => ToggleSearchConfirmEach,
787
788 "start_macro_recording" => StartMacroRecording,
789 "stop_macro_recording" => StopMacroRecording,
790
791 "list_macros" => ListMacros,
792 "prompt_record_macro" => PromptRecordMacro,
793 "prompt_play_macro" => PromptPlayMacro,
794 "play_last_macro" => PlayLastMacro,
795 "prompt_set_bookmark" => PromptSetBookmark,
796 "prompt_jump_to_bookmark" => PromptJumpToBookmark,
797
798 "undo" => Undo,
799 "redo" => Redo,
800
801 "scroll_up" => ScrollUp,
802 "scroll_down" => ScrollDown,
803 "show_help" => ShowHelp,
804 "keyboard_shortcuts" => ShowKeyboardShortcuts,
805 "show_warnings" => ShowWarnings,
806 "show_status_log" => ShowStatusLog,
807 "show_lsp_status" => ShowLspStatus,
808 "clear_warnings" => ClearWarnings,
809 "command_palette" => CommandPalette,
810 "quick_open" => QuickOpen,
811 "toggle_line_wrap" => ToggleLineWrap,
812 "toggle_read_only" => ToggleReadOnly,
813 "toggle_compose_mode" => ToggleComposeMode,
814 "set_compose_width" => SetComposeWidth,
815
816 "next_buffer" => NextBuffer,
817 "prev_buffer" => PrevBuffer,
818
819 "navigate_back" => NavigateBack,
820 "navigate_forward" => NavigateForward,
821
822 "split_horizontal" => SplitHorizontal,
823 "split_vertical" => SplitVertical,
824 "close_split" => CloseSplit,
825 "next_split" => NextSplit,
826 "prev_split" => PrevSplit,
827 "increase_split_size" => IncreaseSplitSize,
828 "decrease_split_size" => DecreaseSplitSize,
829 "toggle_maximize_split" => ToggleMaximizeSplit,
830
831 "prompt_confirm" => PromptConfirm,
832 "prompt_cancel" => PromptCancel,
833 "prompt_backspace" => PromptBackspace,
834 "prompt_move_left" => PromptMoveLeft,
835 "prompt_move_right" => PromptMoveRight,
836 "prompt_move_start" => PromptMoveStart,
837 "prompt_move_end" => PromptMoveEnd,
838 "prompt_select_prev" => PromptSelectPrev,
839 "prompt_select_next" => PromptSelectNext,
840 "prompt_page_up" => PromptPageUp,
841 "prompt_page_down" => PromptPageDown,
842 "prompt_accept_suggestion" => PromptAcceptSuggestion,
843 "prompt_delete_word_forward" => PromptDeleteWordForward,
844 "prompt_delete_word_backward" => PromptDeleteWordBackward,
845 "prompt_delete_to_line_end" => PromptDeleteToLineEnd,
846 "prompt_copy" => PromptCopy,
847 "prompt_cut" => PromptCut,
848 "prompt_paste" => PromptPaste,
849 "prompt_move_left_selecting" => PromptMoveLeftSelecting,
850 "prompt_move_right_selecting" => PromptMoveRightSelecting,
851 "prompt_move_home_selecting" => PromptMoveHomeSelecting,
852 "prompt_move_end_selecting" => PromptMoveEndSelecting,
853 "prompt_select_word_left" => PromptSelectWordLeft,
854 "prompt_select_word_right" => PromptSelectWordRight,
855 "prompt_select_all" => PromptSelectAll,
856 "file_browser_toggle_hidden" => FileBrowserToggleHidden,
857 "file_browser_toggle_detect_encoding" => FileBrowserToggleDetectEncoding,
858 "prompt_move_word_left" => PromptMoveWordLeft,
859 "prompt_move_word_right" => PromptMoveWordRight,
860 "prompt_delete" => PromptDelete,
861
862 "popup_select_next" => PopupSelectNext,
863 "popup_select_prev" => PopupSelectPrev,
864 "popup_page_up" => PopupPageUp,
865 "popup_page_down" => PopupPageDown,
866 "popup_confirm" => PopupConfirm,
867 "popup_cancel" => PopupCancel,
868
869 "toggle_file_explorer" => ToggleFileExplorer,
870 "toggle_menu_bar" => ToggleMenuBar,
871 "toggle_tab_bar" => ToggleTabBar,
872 "toggle_vertical_scrollbar" => ToggleVerticalScrollbar,
873 "toggle_horizontal_scrollbar" => ToggleHorizontalScrollbar,
874 "focus_file_explorer" => FocusFileExplorer,
875 "focus_editor" => FocusEditor,
876 "file_explorer_up" => FileExplorerUp,
877 "file_explorer_down" => FileExplorerDown,
878 "file_explorer_page_up" => FileExplorerPageUp,
879 "file_explorer_page_down" => FileExplorerPageDown,
880 "file_explorer_expand" => FileExplorerExpand,
881 "file_explorer_collapse" => FileExplorerCollapse,
882 "file_explorer_open" => FileExplorerOpen,
883 "file_explorer_refresh" => FileExplorerRefresh,
884 "file_explorer_new_file" => FileExplorerNewFile,
885 "file_explorer_new_directory" => FileExplorerNewDirectory,
886 "file_explorer_delete" => FileExplorerDelete,
887 "file_explorer_rename" => FileExplorerRename,
888 "file_explorer_toggle_hidden" => FileExplorerToggleHidden,
889 "file_explorer_toggle_gitignored" => FileExplorerToggleGitignored,
890 "file_explorer_search_clear" => FileExplorerSearchClear,
891 "file_explorer_search_backspace" => FileExplorerSearchBackspace,
892
893 "lsp_completion" => LspCompletion,
894 "lsp_goto_definition" => LspGotoDefinition,
895 "lsp_references" => LspReferences,
896 "lsp_rename" => LspRename,
897 "lsp_hover" => LspHover,
898 "lsp_signature_help" => LspSignatureHelp,
899 "lsp_code_actions" => LspCodeActions,
900 "lsp_restart" => LspRestart,
901 "lsp_stop" => LspStop,
902 "lsp_toggle_for_buffer" => LspToggleForBuffer,
903 "toggle_inlay_hints" => ToggleInlayHints,
904 "toggle_mouse_hover" => ToggleMouseHover,
905
906 "toggle_line_numbers" => ToggleLineNumbers,
907 "toggle_scroll_sync" => ToggleScrollSync,
908 "toggle_mouse_capture" => ToggleMouseCapture,
909 "toggle_debug_highlights" => ToggleDebugHighlights,
910 "set_background" => SetBackground,
911 "set_background_blend" => SetBackgroundBlend,
912 "inspect_theme_at_cursor" => InspectThemeAtCursor,
913 "select_theme" => SelectTheme,
914 "select_keybinding_map" => SelectKeybindingMap,
915 "select_locale" => SelectLocale,
916
917 "set_tab_size" => SetTabSize,
918 "set_line_ending" => SetLineEnding,
919 "set_encoding" => SetEncoding,
920 "reload_with_encoding" => ReloadWithEncoding,
921 "toggle_indentation_style" => ToggleIndentationStyle,
922 "toggle_tab_indicators" => ToggleTabIndicators,
923 "toggle_whitespace_indicators" => ToggleWhitespaceIndicators,
924 "reset_buffer_settings" => ResetBufferSettings,
925
926 "dump_config" => DumpConfig,
927
928 "search" => Search,
929 "find_in_selection" => FindInSelection,
930 "find_next" => FindNext,
931 "find_previous" => FindPrevious,
932 "find_selection_next" => FindSelectionNext,
933 "find_selection_previous" => FindSelectionPrevious,
934 "replace" => Replace,
935 "query_replace" => QueryReplace,
936
937 "menu_activate" => MenuActivate,
938 "menu_close" => MenuClose,
939 "menu_left" => MenuLeft,
940 "menu_right" => MenuRight,
941 "menu_up" => MenuUp,
942 "menu_down" => MenuDown,
943 "menu_execute" => MenuExecute,
944
945 "open_terminal" => OpenTerminal,
946 "close_terminal" => CloseTerminal,
947 "focus_terminal" => FocusTerminal,
948 "terminal_escape" => TerminalEscape,
949 "toggle_keyboard_capture" => ToggleKeyboardCapture,
950 "terminal_paste" => TerminalPaste,
951
952 "shell_command" => ShellCommand,
953 "shell_command_replace" => ShellCommandReplace,
954
955 "to_upper_case" => ToUpperCase,
956 "to_lower_case" => ToLowerCase,
957 "sort_lines" => SortLines,
958
959 "calibrate_input" => CalibrateInput,
960 "event_debug" => EventDebug,
961 "load_plugin_from_buffer" => LoadPluginFromBuffer,
962 "open_keybinding_editor" => OpenKeybindingEditor,
963
964 "noop" => None,
965
966 "open_settings" => OpenSettings,
967 "close_settings" => CloseSettings,
968 "settings_save" => SettingsSave,
969 "settings_reset" => SettingsReset,
970 "settings_toggle_focus" => SettingsToggleFocus,
971 "settings_activate" => SettingsActivate,
972 "settings_search" => SettingsSearch,
973 "settings_help" => SettingsHelp,
974 "settings_increment" => SettingsIncrement,
975 "settings_decrement" => SettingsDecrement,
976 }
977 with_char {
978 "insert_char" => InsertChar,
979 "set_bookmark" => SetBookmark,
980 "jump_to_bookmark" => JumpToBookmark,
981 "clear_bookmark" => ClearBookmark,
982 "play_macro" => PlayMacro,
983 "toggle_macro_recording" => ToggleMacroRecording,
984 "show_macro" => ShowMacro,
985 }
986 custom {
987 "copy_with_theme" => {
988 let theme = args.get("theme").and_then(|v| v.as_str()).unwrap_or("");
990 Self::CopyWithTheme(theme.to_string())
991 },
992 "menu_open" => {
993 let name = args.get("name")?.as_str()?;
994 Self::MenuOpen(name.to_string())
995 },
996 "switch_keybinding_map" => {
997 let map_name = args.get("map")?.as_str()?;
998 Self::SwitchKeybindingMap(map_name.to_string())
999 },
1000 }
1001 }
1002
1003 pub fn is_movement_or_editing(&self) -> bool {
1006 matches!(
1007 self,
1008 Action::MoveLeft
1010 | Action::MoveRight
1011 | Action::MoveUp
1012 | Action::MoveDown
1013 | Action::MoveWordLeft
1014 | Action::MoveWordRight
1015 | Action::MoveWordEnd
1016 | Action::MoveLineStart
1017 | Action::MoveLineEnd
1018 | Action::MovePageUp
1019 | Action::MovePageDown
1020 | Action::MoveDocumentStart
1021 | Action::MoveDocumentEnd
1022 | Action::SelectLeft
1024 | Action::SelectRight
1025 | Action::SelectUp
1026 | Action::SelectDown
1027 | Action::SelectToParagraphUp
1028 | Action::SelectToParagraphDown
1029 | Action::SelectWordLeft
1030 | Action::SelectWordRight
1031 | Action::SelectWordEnd
1032 | Action::SelectLineStart
1033 | Action::SelectLineEnd
1034 | Action::SelectDocumentStart
1035 | Action::SelectDocumentEnd
1036 | Action::SelectPageUp
1037 | Action::SelectPageDown
1038 | Action::SelectAll
1039 | Action::SelectWord
1040 | Action::SelectLine
1041 | Action::ExpandSelection
1042 | Action::BlockSelectLeft
1044 | Action::BlockSelectRight
1045 | Action::BlockSelectUp
1046 | Action::BlockSelectDown
1047 | Action::InsertChar(_)
1049 | Action::InsertNewline
1050 | Action::InsertTab
1051 | Action::DeleteBackward
1052 | Action::DeleteForward
1053 | Action::DeleteWordBackward
1054 | Action::DeleteWordForward
1055 | Action::DeleteLine
1056 | Action::DeleteToLineEnd
1057 | Action::DeleteToLineStart
1058 | Action::TransposeChars
1059 | Action::OpenLine
1060 | Action::DuplicateLine
1061 | Action::MoveLineUp
1062 | Action::MoveLineDown
1063 | Action::Cut
1065 | Action::Paste
1066 | Action::Undo
1068 | Action::Redo
1069 )
1070 }
1071
1072 pub fn is_editing(&self) -> bool {
1075 matches!(
1076 self,
1077 Action::InsertChar(_)
1078 | Action::InsertNewline
1079 | Action::InsertTab
1080 | Action::DeleteBackward
1081 | Action::DeleteForward
1082 | Action::DeleteWordBackward
1083 | Action::DeleteWordForward
1084 | Action::DeleteLine
1085 | Action::DeleteToLineEnd
1086 | Action::DeleteToLineStart
1087 | Action::TransposeChars
1088 | Action::OpenLine
1089 | Action::DuplicateLine
1090 | Action::MoveLineUp
1091 | Action::MoveLineDown
1092 | Action::Cut
1093 | Action::Paste
1094 )
1095 }
1096}
1097
1098#[derive(Debug, Clone, PartialEq)]
1100pub enum ChordResolution {
1101 Complete(Action),
1103 Partial,
1105 NoMatch,
1107}
1108
1109#[derive(Clone)]
1111pub struct KeybindingResolver {
1112 bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1115
1116 default_bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
1118
1119 chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1122
1123 default_chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
1125}
1126
1127impl KeybindingResolver {
1128 pub fn new(config: &Config) -> Self {
1130 let mut resolver = Self {
1131 bindings: HashMap::new(),
1132 default_bindings: HashMap::new(),
1133 chord_bindings: HashMap::new(),
1134 default_chord_bindings: HashMap::new(),
1135 };
1136
1137 let map_bindings = config.resolve_keymap(&config.active_keybinding_map);
1139 resolver.load_default_bindings_from_vec(&map_bindings);
1140
1141 resolver.load_bindings_from_vec(&config.keybindings);
1143
1144 resolver
1145 }
1146
1147 fn load_default_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1149 for binding in bindings {
1150 let context = if let Some(ref when) = binding.when {
1152 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1153 } else {
1154 KeyContext::Normal
1155 };
1156
1157 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1158 if !binding.keys.is_empty() {
1160 let mut sequence = Vec::new();
1162 for key_press in &binding.keys {
1163 if let Some(key_code) = Self::parse_key(&key_press.key) {
1164 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1165 sequence.push((key_code, modifiers));
1166 } else {
1167 break;
1169 }
1170 }
1171
1172 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1174 self.default_chord_bindings
1175 .entry(context)
1176 .or_default()
1177 .insert(sequence, action);
1178 }
1179 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1180 let modifiers = Self::parse_modifiers(&binding.modifiers);
1182
1183 self.insert_binding_with_equivalents(
1185 context,
1186 key_code,
1187 modifiers,
1188 action,
1189 &binding.key,
1190 );
1191 }
1192 }
1193 }
1194 }
1195
1196 fn insert_binding_with_equivalents(
1199 &mut self,
1200 context: KeyContext,
1201 key_code: KeyCode,
1202 modifiers: KeyModifiers,
1203 action: Action,
1204 key_name: &str,
1205 ) {
1206 let context_bindings = self.default_bindings.entry(context).or_default();
1207
1208 context_bindings.insert((key_code, modifiers), action.clone());
1210
1211 let equivalents = terminal_key_equivalents(key_code, modifiers);
1213 for (equiv_key, equiv_mods) in equivalents {
1214 if let Some(existing_action) = context_bindings.get(&(equiv_key, equiv_mods)) {
1216 if existing_action != &action {
1218 let equiv_name = format!("{:?}", equiv_key);
1219 tracing::warn!(
1220 "Terminal key equivalent conflict in {:?} context: {} (equivalent of {}) \
1221 is bound to {:?}, but {} is bound to {:?}. \
1222 The explicit binding takes precedence.",
1223 context,
1224 equiv_name,
1225 key_name,
1226 existing_action,
1227 key_name,
1228 action
1229 );
1230 }
1231 } else {
1233 context_bindings.insert((equiv_key, equiv_mods), action.clone());
1235 }
1236 }
1237 }
1238
1239 fn load_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1241 for binding in bindings {
1242 let context = if let Some(ref when) = binding.when {
1244 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1245 } else {
1246 KeyContext::Normal
1247 };
1248
1249 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1250 if !binding.keys.is_empty() {
1252 let mut sequence = Vec::new();
1254 for key_press in &binding.keys {
1255 if let Some(key_code) = Self::parse_key(&key_press.key) {
1256 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1257 sequence.push((key_code, modifiers));
1258 } else {
1259 break;
1261 }
1262 }
1263
1264 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1266 self.chord_bindings
1267 .entry(context)
1268 .or_default()
1269 .insert(sequence, action);
1270 }
1271 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1272 let modifiers = Self::parse_modifiers(&binding.modifiers);
1274 self.bindings
1275 .entry(context)
1276 .or_default()
1277 .insert((key_code, modifiers), action);
1278 }
1279 }
1280 }
1281 }
1282
1283 fn is_application_wide_action(action: &Action) -> bool {
1285 matches!(
1286 action,
1287 Action::Quit
1288 | Action::ForceQuit
1289 | Action::Save
1290 | Action::SaveAs
1291 | Action::ShowHelp
1292 | Action::ShowKeyboardShortcuts
1293 | Action::PromptCancel | Action::PopupCancel )
1296 }
1297
1298 pub fn is_terminal_ui_action(action: &Action) -> bool {
1302 matches!(
1303 action,
1304 Action::CommandPalette
1306 | Action::QuickOpen
1307 | Action::OpenSettings
1308 | Action::MenuActivate
1309 | Action::MenuOpen(_)
1310 | Action::ShowHelp
1311 | Action::ShowKeyboardShortcuts
1312 | Action::Quit
1313 | Action::ForceQuit
1314 | Action::NextSplit
1316 | Action::PrevSplit
1317 | Action::SplitHorizontal
1318 | Action::SplitVertical
1319 | Action::CloseSplit
1320 | Action::ToggleMaximizeSplit
1321 | Action::NextBuffer
1323 | Action::PrevBuffer
1324 | Action::Close
1325 | Action::ScrollTabsLeft
1326 | Action::ScrollTabsRight
1327 | Action::TerminalEscape
1329 | Action::ToggleKeyboardCapture
1330 | Action::OpenTerminal
1331 | Action::CloseTerminal
1332 | Action::TerminalPaste
1333 | Action::ToggleFileExplorer
1335 | Action::ToggleMenuBar
1337 )
1338 }
1339
1340 pub fn resolve_chord(
1346 &self,
1347 chord_state: &[(KeyCode, KeyModifiers)],
1348 event: &KeyEvent,
1349 context: KeyContext,
1350 ) -> ChordResolution {
1351 let mut full_sequence = chord_state.to_vec();
1353 full_sequence.push((event.code, event.modifiers));
1354
1355 tracing::trace!(
1356 "KeybindingResolver.resolve_chord: sequence={:?}, context={:?}",
1357 full_sequence,
1358 context
1359 );
1360
1361 let search_order = vec![
1363 (&self.chord_bindings, &KeyContext::Global, "custom global"),
1364 (
1365 &self.default_chord_bindings,
1366 &KeyContext::Global,
1367 "default global",
1368 ),
1369 (&self.chord_bindings, &context, "custom context"),
1370 (&self.default_chord_bindings, &context, "default context"),
1371 ];
1372
1373 let mut has_partial_match = false;
1374
1375 for (binding_map, bind_context, label) in search_order {
1376 if let Some(context_chords) = binding_map.get(bind_context) {
1377 if let Some(action) = context_chords.get(&full_sequence) {
1379 tracing::trace!(" -> Complete chord match in {}: {:?}", label, action);
1380 return ChordResolution::Complete(action.clone());
1381 }
1382
1383 for (chord_seq, _) in context_chords.iter() {
1385 if chord_seq.len() > full_sequence.len()
1386 && chord_seq[..full_sequence.len()] == full_sequence[..]
1387 {
1388 tracing::trace!(" -> Partial chord match in {}", label);
1389 has_partial_match = true;
1390 break;
1391 }
1392 }
1393 }
1394 }
1395
1396 if has_partial_match {
1397 ChordResolution::Partial
1398 } else {
1399 tracing::trace!(" -> No chord match");
1400 ChordResolution::NoMatch
1401 }
1402 }
1403
1404 pub fn resolve(&self, event: &KeyEvent, context: KeyContext) -> Action {
1406 tracing::trace!(
1407 "KeybindingResolver.resolve: code={:?}, modifiers={:?}, context={:?}",
1408 event.code,
1409 event.modifiers,
1410 context
1411 );
1412
1413 if let Some(global_bindings) = self.bindings.get(&KeyContext::Global) {
1415 if let Some(action) = global_bindings.get(&(event.code, event.modifiers)) {
1416 tracing::trace!(" -> Found in custom global bindings: {:?}", action);
1417 return action.clone();
1418 }
1419 }
1420
1421 if let Some(global_bindings) = self.default_bindings.get(&KeyContext::Global) {
1422 if let Some(action) = global_bindings.get(&(event.code, event.modifiers)) {
1423 tracing::trace!(" -> Found in default global bindings: {:?}", action);
1424 return action.clone();
1425 }
1426 }
1427
1428 if let Some(context_bindings) = self.bindings.get(&context) {
1430 if let Some(action) = context_bindings.get(&(event.code, event.modifiers)) {
1431 tracing::trace!(
1432 " -> Found in custom {} bindings: {:?}",
1433 context.to_when_clause(),
1434 action
1435 );
1436 return action.clone();
1437 }
1438 }
1439
1440 if let Some(context_bindings) = self.default_bindings.get(&context) {
1442 if let Some(action) = context_bindings.get(&(event.code, event.modifiers)) {
1443 tracing::trace!(
1444 " -> Found in default {} bindings: {:?}",
1445 context.to_when_clause(),
1446 action
1447 );
1448 return action.clone();
1449 }
1450 }
1451
1452 if context != KeyContext::Normal {
1455 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
1456 if let Some(action) = normal_bindings.get(&(event.code, event.modifiers)) {
1457 if Self::is_application_wide_action(action) {
1458 tracing::trace!(
1459 " -> Found application-wide action in custom normal bindings: {:?}",
1460 action
1461 );
1462 return action.clone();
1463 }
1464 }
1465 }
1466
1467 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
1468 if let Some(action) = normal_bindings.get(&(event.code, event.modifiers)) {
1469 if Self::is_application_wide_action(action) {
1470 tracing::trace!(
1471 " -> Found application-wide action in default normal bindings: {:?}",
1472 action
1473 );
1474 return action.clone();
1475 }
1476 }
1477 }
1478 }
1479
1480 if context.allows_text_input() && is_text_input_modifier(event.modifiers) {
1482 if let KeyCode::Char(c) = event.code {
1483 tracing::trace!(" -> Character input: '{}'", c);
1484 return Action::InsertChar(c);
1485 }
1486 }
1487
1488 tracing::trace!(" -> No binding found, returning Action::None");
1489 Action::None
1490 }
1491
1492 pub fn resolve_in_context_only(&self, event: &KeyEvent, context: KeyContext) -> Option<Action> {
1497 if let Some(context_bindings) = self.bindings.get(&context) {
1499 if let Some(action) = context_bindings.get(&(event.code, event.modifiers)) {
1500 return Some(action.clone());
1501 }
1502 }
1503
1504 if let Some(context_bindings) = self.default_bindings.get(&context) {
1506 if let Some(action) = context_bindings.get(&(event.code, event.modifiers)) {
1507 return Some(action.clone());
1508 }
1509 }
1510
1511 None
1512 }
1513
1514 pub fn resolve_terminal_ui_action(&self, event: &KeyEvent) -> Action {
1518 tracing::trace!(
1519 "KeybindingResolver.resolve_terminal_ui_action: code={:?}, modifiers={:?}",
1520 event.code,
1521 event.modifiers
1522 );
1523
1524 for bindings in [&self.bindings, &self.default_bindings] {
1526 if let Some(terminal_bindings) = bindings.get(&KeyContext::Terminal) {
1527 if let Some(action) = terminal_bindings.get(&(event.code, event.modifiers)) {
1528 if Self::is_terminal_ui_action(action) {
1529 tracing::trace!(" -> Found UI action in terminal bindings: {:?}", action);
1530 return action.clone();
1531 }
1532 }
1533 }
1534 }
1535
1536 for bindings in [&self.bindings, &self.default_bindings] {
1538 if let Some(global_bindings) = bindings.get(&KeyContext::Global) {
1539 if let Some(action) = global_bindings.get(&(event.code, event.modifiers)) {
1540 if Self::is_terminal_ui_action(action) {
1541 tracing::trace!(" -> Found UI action in global bindings: {:?}", action);
1542 return action.clone();
1543 }
1544 }
1545 }
1546 }
1547
1548 for bindings in [&self.bindings, &self.default_bindings] {
1550 if let Some(normal_bindings) = bindings.get(&KeyContext::Normal) {
1551 if let Some(action) = normal_bindings.get(&(event.code, event.modifiers)) {
1552 if Self::is_terminal_ui_action(action) {
1553 tracing::trace!(" -> Found UI action in normal bindings: {:?}", action);
1554 return action.clone();
1555 }
1556 }
1557 }
1558 }
1559
1560 tracing::trace!(" -> No UI action found");
1561 Action::None
1562 }
1563
1564 pub fn find_keybinding_for_action(
1567 &self,
1568 action_name: &str,
1569 context: KeyContext,
1570 ) -> Option<String> {
1571 let target_action = Action::from_str(action_name, &HashMap::new())?;
1573
1574 let search_maps = vec![
1576 self.bindings.get(&context),
1577 self.bindings.get(&KeyContext::Global),
1578 self.default_bindings.get(&context),
1579 self.default_bindings.get(&KeyContext::Global),
1580 ];
1581
1582 for map in search_maps.into_iter().flatten() {
1583 let mut matches: Vec<(KeyCode, KeyModifiers)> = map
1585 .iter()
1586 .filter(|(_, action)| {
1587 std::mem::discriminant(*action) == std::mem::discriminant(&target_action)
1588 })
1589 .map(|((key_code, modifiers), _)| (*key_code, *modifiers))
1590 .collect();
1591
1592 if !matches.is_empty() {
1593 matches.sort_by(|(key_a, mod_a), (key_b, mod_b)| {
1595 let mod_count_a = mod_a.bits().count_ones();
1597 let mod_count_b = mod_b.bits().count_ones();
1598 match mod_count_a.cmp(&mod_count_b) {
1599 std::cmp::Ordering::Equal => {
1600 match mod_a.bits().cmp(&mod_b.bits()) {
1602 std::cmp::Ordering::Equal => {
1603 Self::key_code_sort_key(key_a)
1605 .cmp(&Self::key_code_sort_key(key_b))
1606 }
1607 other => other,
1608 }
1609 }
1610 other => other,
1611 }
1612 });
1613
1614 let (key_code, modifiers) = matches[0];
1615 return Some(format_keybinding(&key_code, &modifiers));
1616 }
1617 }
1618
1619 None
1620 }
1621
1622 fn key_code_sort_key(key_code: &KeyCode) -> (u8, u32) {
1624 match key_code {
1625 KeyCode::Char(c) => (0, *c as u32),
1626 KeyCode::F(n) => (1, *n as u32),
1627 KeyCode::Enter => (2, 0),
1628 KeyCode::Tab => (2, 1),
1629 KeyCode::Backspace => (2, 2),
1630 KeyCode::Delete => (2, 3),
1631 KeyCode::Esc => (2, 4),
1632 KeyCode::Left => (3, 0),
1633 KeyCode::Right => (3, 1),
1634 KeyCode::Up => (3, 2),
1635 KeyCode::Down => (3, 3),
1636 KeyCode::Home => (3, 4),
1637 KeyCode::End => (3, 5),
1638 KeyCode::PageUp => (3, 6),
1639 KeyCode::PageDown => (3, 7),
1640 _ => (255, 0),
1641 }
1642 }
1643
1644 pub fn find_menu_mnemonic(&self, menu_name: &str) -> Option<char> {
1647 let search_maps = vec![
1649 self.bindings.get(&KeyContext::Normal),
1650 self.bindings.get(&KeyContext::Global),
1651 self.default_bindings.get(&KeyContext::Normal),
1652 self.default_bindings.get(&KeyContext::Global),
1653 ];
1654
1655 for map in search_maps.into_iter().flatten() {
1656 for ((key_code, modifiers), action) in map {
1657 if let Action::MenuOpen(name) = action {
1659 if name.eq_ignore_ascii_case(menu_name) && *modifiers == KeyModifiers::ALT {
1660 if let KeyCode::Char(c) = key_code {
1662 return Some(c.to_ascii_lowercase());
1663 }
1664 }
1665 }
1666 }
1667 }
1668
1669 None
1670 }
1671
1672 fn parse_key(key: &str) -> Option<KeyCode> {
1674 let lower = key.to_lowercase();
1675 match lower.as_str() {
1676 "enter" => Some(KeyCode::Enter),
1677 "backspace" => Some(KeyCode::Backspace),
1678 "delete" | "del" => Some(KeyCode::Delete),
1679 "tab" => Some(KeyCode::Tab),
1680 "backtab" => Some(KeyCode::BackTab),
1681 "esc" | "escape" => Some(KeyCode::Esc),
1682 "space" => Some(KeyCode::Char(' ')),
1683
1684 "left" => Some(KeyCode::Left),
1685 "right" => Some(KeyCode::Right),
1686 "up" => Some(KeyCode::Up),
1687 "down" => Some(KeyCode::Down),
1688 "home" => Some(KeyCode::Home),
1689 "end" => Some(KeyCode::End),
1690 "pageup" => Some(KeyCode::PageUp),
1691 "pagedown" => Some(KeyCode::PageDown),
1692
1693 s if s.len() == 1 => s.chars().next().map(KeyCode::Char),
1694 s if s.starts_with('f') && s.len() >= 2 => s[1..].parse::<u8>().ok().map(KeyCode::F),
1696 _ => None,
1697 }
1698 }
1699
1700 fn parse_modifiers(modifiers: &[String]) -> KeyModifiers {
1702 let mut result = KeyModifiers::empty();
1703 for m in modifiers {
1704 match m.to_lowercase().as_str() {
1705 "ctrl" | "control" => result |= KeyModifiers::CONTROL,
1706 "shift" => result |= KeyModifiers::SHIFT,
1707 "alt" => result |= KeyModifiers::ALT,
1708 "super" | "cmd" | "command" | "meta" => result |= KeyModifiers::SUPER,
1709 _ => {}
1710 }
1711 }
1712 result
1713 }
1714
1715 pub fn get_all_bindings(&self) -> Vec<(String, String)> {
1719 let mut bindings = Vec::new();
1720
1721 for context in &[
1723 KeyContext::Normal,
1724 KeyContext::Prompt,
1725 KeyContext::Popup,
1726 KeyContext::FileExplorer,
1727 KeyContext::Menu,
1728 ] {
1729 let mut all_keys: HashMap<(KeyCode, KeyModifiers), Action> = HashMap::new();
1730
1731 if let Some(context_defaults) = self.default_bindings.get(context) {
1733 for (key, action) in context_defaults {
1734 all_keys.insert(*key, action.clone());
1735 }
1736 }
1737
1738 if let Some(context_bindings) = self.bindings.get(context) {
1740 for (key, action) in context_bindings {
1741 all_keys.insert(*key, action.clone());
1742 }
1743 }
1744
1745 let context_str = if *context != KeyContext::Normal {
1747 format!("[{}] ", context.to_when_clause())
1748 } else {
1749 String::new()
1750 };
1751
1752 for ((key_code, modifiers), action) in all_keys {
1753 let key_str = Self::format_key(key_code, modifiers);
1754 let action_str = format!("{}{}", context_str, Self::format_action(&action));
1755 bindings.push((key_str, action_str));
1756 }
1757 }
1758
1759 bindings.sort_by(|a, b| a.1.cmp(&b.1));
1761
1762 bindings
1763 }
1764
1765 fn format_key(key_code: KeyCode, modifiers: KeyModifiers) -> String {
1767 format_keybinding(&key_code, &modifiers)
1768 }
1769
1770 fn format_action(action: &Action) -> String {
1772 match action {
1773 Action::InsertChar(c) => t!("action.insert_char", char = c),
1774 Action::InsertNewline => t!("action.insert_newline"),
1775 Action::InsertTab => t!("action.insert_tab"),
1776 Action::MoveLeft => t!("action.move_left"),
1777 Action::MoveRight => t!("action.move_right"),
1778 Action::MoveUp => t!("action.move_up"),
1779 Action::MoveDown => t!("action.move_down"),
1780 Action::MoveWordLeft => t!("action.move_word_left"),
1781 Action::MoveWordRight => t!("action.move_word_right"),
1782 Action::MoveWordEnd => t!("action.move_word_end"),
1783 Action::MoveLineStart => t!("action.move_line_start"),
1784 Action::MoveLineEnd => t!("action.move_line_end"),
1785 Action::MoveLineUp => t!("action.move_line_up"),
1786 Action::MoveLineDown => t!("action.move_line_down"),
1787 Action::MovePageUp => t!("action.move_page_up"),
1788 Action::MovePageDown => t!("action.move_page_down"),
1789 Action::MoveDocumentStart => t!("action.move_document_start"),
1790 Action::MoveDocumentEnd => t!("action.move_document_end"),
1791 Action::SelectLeft => t!("action.select_left"),
1792 Action::SelectRight => t!("action.select_right"),
1793 Action::SelectUp => t!("action.select_up"),
1794 Action::SelectDown => t!("action.select_down"),
1795 Action::SelectToParagraphUp => t!("action.select_to_paragraph_up"),
1796 Action::SelectToParagraphDown => t!("action.select_to_paragraph_down"),
1797 Action::SelectWordLeft => t!("action.select_word_left"),
1798 Action::SelectWordRight => t!("action.select_word_right"),
1799 Action::SelectWordEnd => t!("action.select_word_end"),
1800 Action::SelectLineStart => t!("action.select_line_start"),
1801 Action::SelectLineEnd => t!("action.select_line_end"),
1802 Action::SelectDocumentStart => t!("action.select_document_start"),
1803 Action::SelectDocumentEnd => t!("action.select_document_end"),
1804 Action::SelectPageUp => t!("action.select_page_up"),
1805 Action::SelectPageDown => t!("action.select_page_down"),
1806 Action::SelectAll => t!("action.select_all"),
1807 Action::SelectWord => t!("action.select_word"),
1808 Action::SelectLine => t!("action.select_line"),
1809 Action::ExpandSelection => t!("action.expand_selection"),
1810 Action::BlockSelectLeft => t!("action.block_select_left"),
1811 Action::BlockSelectRight => t!("action.block_select_right"),
1812 Action::BlockSelectUp => t!("action.block_select_up"),
1813 Action::BlockSelectDown => t!("action.block_select_down"),
1814 Action::DeleteBackward => t!("action.delete_backward"),
1815 Action::DeleteForward => t!("action.delete_forward"),
1816 Action::DeleteWordBackward => t!("action.delete_word_backward"),
1817 Action::DeleteWordForward => t!("action.delete_word_forward"),
1818 Action::DeleteLine => t!("action.delete_line"),
1819 Action::DeleteToLineEnd => t!("action.delete_to_line_end"),
1820 Action::DeleteToLineStart => t!("action.delete_to_line_start"),
1821 Action::TransposeChars => t!("action.transpose_chars"),
1822 Action::OpenLine => t!("action.open_line"),
1823 Action::DuplicateLine => t!("action.duplicate_line"),
1824 Action::Recenter => t!("action.recenter"),
1825 Action::SetMark => t!("action.set_mark"),
1826 Action::Copy => t!("action.copy"),
1827 Action::CopyWithTheme(theme) if theme.is_empty() => t!("action.copy_with_formatting"),
1828 Action::CopyWithTheme(theme) => t!("action.copy_with_theme", theme = theme),
1829 Action::Cut => t!("action.cut"),
1830 Action::Paste => t!("action.paste"),
1831 Action::YankWordForward => t!("action.yank_word_forward"),
1832 Action::YankWordBackward => t!("action.yank_word_backward"),
1833 Action::YankToLineEnd => t!("action.yank_to_line_end"),
1834 Action::YankToLineStart => t!("action.yank_to_line_start"),
1835 Action::AddCursorAbove => t!("action.add_cursor_above"),
1836 Action::AddCursorBelow => t!("action.add_cursor_below"),
1837 Action::AddCursorNextMatch => t!("action.add_cursor_next_match"),
1838 Action::RemoveSecondaryCursors => t!("action.remove_secondary_cursors"),
1839 Action::Save => t!("action.save"),
1840 Action::SaveAs => t!("action.save_as"),
1841 Action::Open => t!("action.open"),
1842 Action::SwitchProject => t!("action.switch_project"),
1843 Action::New => t!("action.new"),
1844 Action::Close => t!("action.close"),
1845 Action::CloseTab => t!("action.close_tab"),
1846 Action::Quit => t!("action.quit"),
1847 Action::ForceQuit => t!("action.force_quit"),
1848 Action::Detach => t!("action.detach"),
1849 Action::Revert => t!("action.revert"),
1850 Action::ToggleAutoRevert => t!("action.toggle_auto_revert"),
1851 Action::FormatBuffer => t!("action.format_buffer"),
1852 Action::TrimTrailingWhitespace => t!("action.trim_trailing_whitespace"),
1853 Action::EnsureFinalNewline => t!("action.ensure_final_newline"),
1854 Action::GotoLine => t!("action.goto_line"),
1855 Action::ScanLineIndex => t!("action.scan_line_index"),
1856 Action::GoToMatchingBracket => t!("action.goto_matching_bracket"),
1857 Action::JumpToNextError => t!("action.jump_to_next_error"),
1858 Action::JumpToPreviousError => t!("action.jump_to_previous_error"),
1859 Action::SmartHome => t!("action.smart_home"),
1860 Action::DedentSelection => t!("action.dedent_selection"),
1861 Action::ToggleComment => t!("action.toggle_comment"),
1862 Action::ToggleFold => t!("action.toggle_fold"),
1863 Action::SetBookmark(c) => t!("action.set_bookmark", key = c),
1864 Action::JumpToBookmark(c) => t!("action.jump_to_bookmark", key = c),
1865 Action::ClearBookmark(c) => t!("action.clear_bookmark", key = c),
1866 Action::ListBookmarks => t!("action.list_bookmarks"),
1867 Action::ToggleSearchCaseSensitive => t!("action.toggle_search_case_sensitive"),
1868 Action::ToggleSearchWholeWord => t!("action.toggle_search_whole_word"),
1869 Action::ToggleSearchRegex => t!("action.toggle_search_regex"),
1870 Action::ToggleSearchConfirmEach => t!("action.toggle_search_confirm_each"),
1871 Action::StartMacroRecording => t!("action.start_macro_recording"),
1872 Action::StopMacroRecording => t!("action.stop_macro_recording"),
1873 Action::PlayMacro(c) => t!("action.play_macro", key = c),
1874 Action::ToggleMacroRecording(c) => t!("action.toggle_macro_recording", key = c),
1875 Action::ShowMacro(c) => t!("action.show_macro", key = c),
1876 Action::ListMacros => t!("action.list_macros"),
1877 Action::PromptRecordMacro => t!("action.prompt_record_macro"),
1878 Action::PromptPlayMacro => t!("action.prompt_play_macro"),
1879 Action::PlayLastMacro => t!("action.play_last_macro"),
1880 Action::PromptSetBookmark => t!("action.prompt_set_bookmark"),
1881 Action::PromptJumpToBookmark => t!("action.prompt_jump_to_bookmark"),
1882 Action::Undo => t!("action.undo"),
1883 Action::Redo => t!("action.redo"),
1884 Action::ScrollUp => t!("action.scroll_up"),
1885 Action::ScrollDown => t!("action.scroll_down"),
1886 Action::ShowHelp => t!("action.show_help"),
1887 Action::ShowKeyboardShortcuts => t!("action.show_keyboard_shortcuts"),
1888 Action::ShowWarnings => t!("action.show_warnings"),
1889 Action::ShowStatusLog => t!("action.show_status_log"),
1890 Action::ShowLspStatus => t!("action.show_lsp_status"),
1891 Action::ClearWarnings => t!("action.clear_warnings"),
1892 Action::CommandPalette => t!("action.command_palette"),
1893 Action::QuickOpen => t!("action.quick_open"),
1894 Action::InspectThemeAtCursor => t!("action.inspect_theme_at_cursor"),
1895 Action::ToggleLineWrap => t!("action.toggle_line_wrap"),
1896 Action::ToggleReadOnly => t!("action.toggle_read_only"),
1897 Action::ToggleComposeMode => t!("action.toggle_compose_mode"),
1898 Action::SetComposeWidth => t!("action.set_compose_width"),
1899 Action::NextBuffer => t!("action.next_buffer"),
1900 Action::PrevBuffer => t!("action.prev_buffer"),
1901 Action::NavigateBack => t!("action.navigate_back"),
1902 Action::NavigateForward => t!("action.navigate_forward"),
1903 Action::SplitHorizontal => t!("action.split_horizontal"),
1904 Action::SplitVertical => t!("action.split_vertical"),
1905 Action::CloseSplit => t!("action.close_split"),
1906 Action::NextSplit => t!("action.next_split"),
1907 Action::PrevSplit => t!("action.prev_split"),
1908 Action::IncreaseSplitSize => t!("action.increase_split_size"),
1909 Action::DecreaseSplitSize => t!("action.decrease_split_size"),
1910 Action::ToggleMaximizeSplit => t!("action.toggle_maximize_split"),
1911 Action::PromptConfirm => t!("action.prompt_confirm"),
1912 Action::PromptConfirmWithText(ref text) => {
1913 format!("{} ({})", t!("action.prompt_confirm"), text).into()
1914 }
1915 Action::PromptCancel => t!("action.prompt_cancel"),
1916 Action::PromptBackspace => t!("action.prompt_backspace"),
1917 Action::PromptDelete => t!("action.prompt_delete"),
1918 Action::PromptMoveLeft => t!("action.prompt_move_left"),
1919 Action::PromptMoveRight => t!("action.prompt_move_right"),
1920 Action::PromptMoveStart => t!("action.prompt_move_start"),
1921 Action::PromptMoveEnd => t!("action.prompt_move_end"),
1922 Action::PromptSelectPrev => t!("action.prompt_select_prev"),
1923 Action::PromptSelectNext => t!("action.prompt_select_next"),
1924 Action::PromptPageUp => t!("action.prompt_page_up"),
1925 Action::PromptPageDown => t!("action.prompt_page_down"),
1926 Action::PromptAcceptSuggestion => t!("action.prompt_accept_suggestion"),
1927 Action::PromptMoveWordLeft => t!("action.prompt_move_word_left"),
1928 Action::PromptMoveWordRight => t!("action.prompt_move_word_right"),
1929 Action::PromptDeleteWordForward => t!("action.prompt_delete_word_forward"),
1930 Action::PromptDeleteWordBackward => t!("action.prompt_delete_word_backward"),
1931 Action::PromptDeleteToLineEnd => t!("action.prompt_delete_to_line_end"),
1932 Action::PromptCopy => t!("action.prompt_copy"),
1933 Action::PromptCut => t!("action.prompt_cut"),
1934 Action::PromptPaste => t!("action.prompt_paste"),
1935 Action::PromptMoveLeftSelecting => t!("action.prompt_move_left_selecting"),
1936 Action::PromptMoveRightSelecting => t!("action.prompt_move_right_selecting"),
1937 Action::PromptMoveHomeSelecting => t!("action.prompt_move_home_selecting"),
1938 Action::PromptMoveEndSelecting => t!("action.prompt_move_end_selecting"),
1939 Action::PromptSelectWordLeft => t!("action.prompt_select_word_left"),
1940 Action::PromptSelectWordRight => t!("action.prompt_select_word_right"),
1941 Action::PromptSelectAll => t!("action.prompt_select_all"),
1942 Action::FileBrowserToggleHidden => t!("action.file_browser_toggle_hidden"),
1943 Action::FileBrowserToggleDetectEncoding => {
1944 t!("action.file_browser_toggle_detect_encoding")
1945 }
1946 Action::PopupSelectNext => t!("action.popup_select_next"),
1947 Action::PopupSelectPrev => t!("action.popup_select_prev"),
1948 Action::PopupPageUp => t!("action.popup_page_up"),
1949 Action::PopupPageDown => t!("action.popup_page_down"),
1950 Action::PopupConfirm => t!("action.popup_confirm"),
1951 Action::PopupCancel => t!("action.popup_cancel"),
1952 Action::ToggleFileExplorer => t!("action.toggle_file_explorer"),
1953 Action::ToggleMenuBar => t!("action.toggle_menu_bar"),
1954 Action::ToggleTabBar => t!("action.toggle_tab_bar"),
1955 Action::ToggleStatusBar => t!("action.toggle_status_bar"),
1956 Action::ToggleVerticalScrollbar => t!("action.toggle_vertical_scrollbar"),
1957 Action::ToggleHorizontalScrollbar => t!("action.toggle_horizontal_scrollbar"),
1958 Action::FocusFileExplorer => t!("action.focus_file_explorer"),
1959 Action::FocusEditor => t!("action.focus_editor"),
1960 Action::FileExplorerUp => t!("action.file_explorer_up"),
1961 Action::FileExplorerDown => t!("action.file_explorer_down"),
1962 Action::FileExplorerPageUp => t!("action.file_explorer_page_up"),
1963 Action::FileExplorerPageDown => t!("action.file_explorer_page_down"),
1964 Action::FileExplorerExpand => t!("action.file_explorer_expand"),
1965 Action::FileExplorerCollapse => t!("action.file_explorer_collapse"),
1966 Action::FileExplorerOpen => t!("action.file_explorer_open"),
1967 Action::FileExplorerRefresh => t!("action.file_explorer_refresh"),
1968 Action::FileExplorerNewFile => t!("action.file_explorer_new_file"),
1969 Action::FileExplorerNewDirectory => t!("action.file_explorer_new_directory"),
1970 Action::FileExplorerDelete => t!("action.file_explorer_delete"),
1971 Action::FileExplorerRename => t!("action.file_explorer_rename"),
1972 Action::FileExplorerToggleHidden => t!("action.file_explorer_toggle_hidden"),
1973 Action::FileExplorerToggleGitignored => t!("action.file_explorer_toggle_gitignored"),
1974 Action::FileExplorerSearchClear => t!("action.file_explorer_search_clear"),
1975 Action::FileExplorerSearchBackspace => t!("action.file_explorer_search_backspace"),
1976 Action::LspCompletion => t!("action.lsp_completion"),
1977 Action::LspGotoDefinition => t!("action.lsp_goto_definition"),
1978 Action::LspReferences => t!("action.lsp_references"),
1979 Action::LspRename => t!("action.lsp_rename"),
1980 Action::LspHover => t!("action.lsp_hover"),
1981 Action::LspSignatureHelp => t!("action.lsp_signature_help"),
1982 Action::LspCodeActions => t!("action.lsp_code_actions"),
1983 Action::LspRestart => t!("action.lsp_restart"),
1984 Action::LspStop => t!("action.lsp_stop"),
1985 Action::LspToggleForBuffer => t!("action.lsp_toggle_for_buffer"),
1986 Action::ToggleInlayHints => t!("action.toggle_inlay_hints"),
1987 Action::ToggleMouseHover => t!("action.toggle_mouse_hover"),
1988 Action::ToggleLineNumbers => t!("action.toggle_line_numbers"),
1989 Action::ToggleScrollSync => t!("action.toggle_scroll_sync"),
1990 Action::ToggleMouseCapture => t!("action.toggle_mouse_capture"),
1991 Action::ToggleDebugHighlights => t!("action.toggle_debug_highlights"),
1992 Action::SetBackground => t!("action.set_background"),
1993 Action::SetBackgroundBlend => t!("action.set_background_blend"),
1994 Action::AddRuler => t!("action.add_ruler"),
1995 Action::RemoveRuler => t!("action.remove_ruler"),
1996 Action::SetTabSize => t!("action.set_tab_size"),
1997 Action::SetLineEnding => t!("action.set_line_ending"),
1998 Action::SetEncoding => t!("action.set_encoding"),
1999 Action::ReloadWithEncoding => t!("action.reload_with_encoding"),
2000 Action::SetLanguage => t!("action.set_language"),
2001 Action::ToggleIndentationStyle => t!("action.toggle_indentation_style"),
2002 Action::ToggleTabIndicators => t!("action.toggle_tab_indicators"),
2003 Action::ToggleWhitespaceIndicators => t!("action.toggle_whitespace_indicators"),
2004 Action::ResetBufferSettings => t!("action.reset_buffer_settings"),
2005 Action::DumpConfig => t!("action.dump_config"),
2006 Action::Search => t!("action.search"),
2007 Action::FindInSelection => t!("action.find_in_selection"),
2008 Action::FindNext => t!("action.find_next"),
2009 Action::FindPrevious => t!("action.find_previous"),
2010 Action::FindSelectionNext => t!("action.find_selection_next"),
2011 Action::FindSelectionPrevious => t!("action.find_selection_previous"),
2012 Action::Replace => t!("action.replace"),
2013 Action::QueryReplace => t!("action.query_replace"),
2014 Action::MenuActivate => t!("action.menu_activate"),
2015 Action::MenuClose => t!("action.menu_close"),
2016 Action::MenuLeft => t!("action.menu_left"),
2017 Action::MenuRight => t!("action.menu_right"),
2018 Action::MenuUp => t!("action.menu_up"),
2019 Action::MenuDown => t!("action.menu_down"),
2020 Action::MenuExecute => t!("action.menu_execute"),
2021 Action::MenuOpen(name) => t!("action.menu_open", name = name),
2022 Action::SwitchKeybindingMap(map) => t!("action.switch_keybinding_map", map = map),
2023 Action::PluginAction(name) => t!("action.plugin_action", name = name),
2024 Action::ScrollTabsLeft => t!("action.scroll_tabs_left"),
2025 Action::ScrollTabsRight => t!("action.scroll_tabs_right"),
2026 Action::SelectTheme => t!("action.select_theme"),
2027 Action::SelectKeybindingMap => t!("action.select_keybinding_map"),
2028 Action::SelectCursorStyle => t!("action.select_cursor_style"),
2029 Action::SelectLocale => t!("action.select_locale"),
2030 Action::SwitchToPreviousTab => t!("action.switch_to_previous_tab"),
2031 Action::SwitchToTabByName => t!("action.switch_to_tab_by_name"),
2032 Action::OpenTerminal => t!("action.open_terminal"),
2033 Action::CloseTerminal => t!("action.close_terminal"),
2034 Action::FocusTerminal => t!("action.focus_terminal"),
2035 Action::TerminalEscape => t!("action.terminal_escape"),
2036 Action::ToggleKeyboardCapture => t!("action.toggle_keyboard_capture"),
2037 Action::TerminalPaste => t!("action.terminal_paste"),
2038 Action::OpenSettings => t!("action.open_settings"),
2039 Action::CloseSettings => t!("action.close_settings"),
2040 Action::SettingsSave => t!("action.settings_save"),
2041 Action::SettingsReset => t!("action.settings_reset"),
2042 Action::SettingsToggleFocus => t!("action.settings_toggle_focus"),
2043 Action::SettingsActivate => t!("action.settings_activate"),
2044 Action::SettingsSearch => t!("action.settings_search"),
2045 Action::SettingsHelp => t!("action.settings_help"),
2046 Action::SettingsIncrement => t!("action.settings_increment"),
2047 Action::SettingsDecrement => t!("action.settings_decrement"),
2048 Action::ShellCommand => t!("action.shell_command"),
2049 Action::ShellCommandReplace => t!("action.shell_command_replace"),
2050 Action::ToUpperCase => t!("action.to_uppercase"),
2051 Action::ToLowerCase => t!("action.to_lowercase"),
2052 Action::SortLines => t!("action.sort_lines"),
2053 Action::CalibrateInput => t!("action.calibrate_input"),
2054 Action::EventDebug => t!("action.event_debug"),
2055 Action::LoadPluginFromBuffer => "Load Plugin from Buffer".into(),
2056 Action::OpenKeybindingEditor => "Keybinding Editor".into(),
2057 Action::None => t!("action.none"),
2058 }
2059 .to_string()
2060 }
2061
2062 pub fn parse_key_public(key: &str) -> Option<KeyCode> {
2064 Self::parse_key(key)
2065 }
2066
2067 pub fn parse_modifiers_public(modifiers: &[String]) -> KeyModifiers {
2069 Self::parse_modifiers(modifiers)
2070 }
2071
2072 pub fn format_action_from_str(action_name: &str) -> String {
2076 if let Some(action) = Action::from_str(action_name, &std::collections::HashMap::new()) {
2078 Self::format_action(&action)
2079 } else {
2080 action_name
2082 .split('_')
2083 .map(|word| {
2084 let mut chars = word.chars();
2085 match chars.next() {
2086 Some(c) => {
2087 let upper: String = c.to_uppercase().collect();
2088 format!("{}{}", upper, chars.as_str())
2089 }
2090 None => String::new(),
2091 }
2092 })
2093 .collect::<Vec<_>>()
2094 .join(" ")
2095 }
2096 }
2097
2098 pub fn all_action_names() -> Vec<String> {
2102 Action::all_action_names()
2103 }
2104
2105 pub fn get_keybinding_for_action(
2111 &self,
2112 action: &Action,
2113 context: KeyContext,
2114 ) -> Option<String> {
2115 fn find_best_keybinding(
2117 bindings: &HashMap<(KeyCode, KeyModifiers), Action>,
2118 action: &Action,
2119 ) -> Option<(KeyCode, KeyModifiers)> {
2120 let matches: Vec<_> = bindings
2121 .iter()
2122 .filter(|(_, a)| *a == action)
2123 .map(|((k, m), _)| (*k, *m))
2124 .collect();
2125
2126 if matches.is_empty() {
2127 return None;
2128 }
2129
2130 let mut sorted = matches;
2133 sorted.sort_by(|(k1, m1), (k2, m2)| {
2134 let score1 = keybinding_priority_score(k1);
2135 let score2 = keybinding_priority_score(k2);
2136 match score1.cmp(&score2) {
2138 std::cmp::Ordering::Equal => {
2139 let s1 = format_keybinding(k1, m1);
2141 let s2 = format_keybinding(k2, m2);
2142 s1.cmp(&s2)
2143 }
2144 other => other,
2145 }
2146 });
2147
2148 sorted.into_iter().next()
2149 }
2150
2151 if let Some(context_bindings) = self.bindings.get(&context) {
2153 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
2154 return Some(format_keybinding(&keycode, &modifiers));
2155 }
2156 }
2157
2158 if let Some(context_bindings) = self.default_bindings.get(&context) {
2160 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
2161 return Some(format_keybinding(&keycode, &modifiers));
2162 }
2163 }
2164
2165 if context != KeyContext::Normal && Self::is_application_wide_action(action) {
2167 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
2169 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
2170 return Some(format_keybinding(&keycode, &modifiers));
2171 }
2172 }
2173
2174 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
2176 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
2177 return Some(format_keybinding(&keycode, &modifiers));
2178 }
2179 }
2180 }
2181
2182 None
2183 }
2184
2185 pub fn reload(&mut self, config: &Config) {
2187 self.bindings.clear();
2188 for binding in &config.keybindings {
2189 if let Some(key_code) = Self::parse_key(&binding.key) {
2190 let modifiers = Self::parse_modifiers(&binding.modifiers);
2191 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
2192 let context = if let Some(ref when) = binding.when {
2194 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
2195 } else {
2196 KeyContext::Normal
2197 };
2198
2199 self.bindings
2200 .entry(context)
2201 .or_default()
2202 .insert((key_code, modifiers), action);
2203 }
2204 }
2205 }
2206 }
2207}
2208
2209#[cfg(test)]
2210mod tests {
2211 use super::*;
2212
2213 #[test]
2214 fn test_parse_key() {
2215 assert_eq!(KeybindingResolver::parse_key("enter"), Some(KeyCode::Enter));
2216 assert_eq!(
2217 KeybindingResolver::parse_key("backspace"),
2218 Some(KeyCode::Backspace)
2219 );
2220 assert_eq!(KeybindingResolver::parse_key("tab"), Some(KeyCode::Tab));
2221 assert_eq!(
2222 KeybindingResolver::parse_key("backtab"),
2223 Some(KeyCode::BackTab)
2224 );
2225 assert_eq!(
2226 KeybindingResolver::parse_key("BackTab"),
2227 Some(KeyCode::BackTab)
2228 );
2229 assert_eq!(KeybindingResolver::parse_key("a"), Some(KeyCode::Char('a')));
2230 }
2231
2232 #[test]
2233 fn test_parse_modifiers() {
2234 let mods = vec!["ctrl".to_string()];
2235 assert_eq!(
2236 KeybindingResolver::parse_modifiers(&mods),
2237 KeyModifiers::CONTROL
2238 );
2239
2240 let mods = vec!["ctrl".to_string(), "shift".to_string()];
2241 assert_eq!(
2242 KeybindingResolver::parse_modifiers(&mods),
2243 KeyModifiers::CONTROL | KeyModifiers::SHIFT
2244 );
2245 }
2246
2247 #[test]
2248 fn test_resolve_basic() {
2249 let config = Config::default();
2250 let resolver = KeybindingResolver::new(&config);
2251
2252 let event = KeyEvent::new(KeyCode::Left, KeyModifiers::empty());
2253 assert_eq!(
2254 resolver.resolve(&event, KeyContext::Normal),
2255 Action::MoveLeft
2256 );
2257
2258 let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2259 assert_eq!(
2260 resolver.resolve(&event, KeyContext::Normal),
2261 Action::InsertChar('a')
2262 );
2263 }
2264
2265 #[test]
2266 fn test_action_from_str() {
2267 let args = HashMap::new();
2268 assert_eq!(Action::from_str("move_left", &args), Some(Action::MoveLeft));
2269 assert_eq!(Action::from_str("save", &args), Some(Action::Save));
2270 assert_eq!(Action::from_str("unknown", &args), None);
2271
2272 assert_eq!(
2274 Action::from_str("keyboard_shortcuts", &args),
2275 Some(Action::ShowKeyboardShortcuts)
2276 );
2277 assert_eq!(
2278 Action::from_str("prompt_confirm", &args),
2279 Some(Action::PromptConfirm)
2280 );
2281 assert_eq!(
2282 Action::from_str("popup_cancel", &args),
2283 Some(Action::PopupCancel)
2284 );
2285
2286 assert_eq!(
2288 Action::from_str("calibrate_input", &args),
2289 Some(Action::CalibrateInput)
2290 );
2291 }
2292
2293 #[test]
2294 fn test_key_context_from_when_clause() {
2295 assert_eq!(
2296 KeyContext::from_when_clause("normal"),
2297 Some(KeyContext::Normal)
2298 );
2299 assert_eq!(
2300 KeyContext::from_when_clause("prompt"),
2301 Some(KeyContext::Prompt)
2302 );
2303 assert_eq!(
2304 KeyContext::from_when_clause("popup"),
2305 Some(KeyContext::Popup)
2306 );
2307 assert_eq!(KeyContext::from_when_clause("help"), None);
2308 assert_eq!(KeyContext::from_when_clause(" help "), None); assert_eq!(KeyContext::from_when_clause("unknown"), None);
2310 assert_eq!(KeyContext::from_when_clause(""), None);
2311 }
2312
2313 #[test]
2314 fn test_key_context_to_when_clause() {
2315 assert_eq!(KeyContext::Normal.to_when_clause(), "normal");
2316 assert_eq!(KeyContext::Prompt.to_when_clause(), "prompt");
2317 assert_eq!(KeyContext::Popup.to_when_clause(), "popup");
2318 }
2319
2320 #[test]
2321 fn test_context_specific_bindings() {
2322 let config = Config::default();
2323 let resolver = KeybindingResolver::new(&config);
2324
2325 let enter_event = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
2327 assert_eq!(
2328 resolver.resolve(&enter_event, KeyContext::Prompt),
2329 Action::PromptConfirm
2330 );
2331 assert_eq!(
2332 resolver.resolve(&enter_event, KeyContext::Normal),
2333 Action::InsertNewline
2334 );
2335
2336 let up_event = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
2338 assert_eq!(
2339 resolver.resolve(&up_event, KeyContext::Popup),
2340 Action::PopupSelectPrev
2341 );
2342 assert_eq!(
2343 resolver.resolve(&up_event, KeyContext::Normal),
2344 Action::MoveUp
2345 );
2346 }
2347
2348 #[test]
2349 fn test_context_fallback_to_normal() {
2350 let config = Config::default();
2351 let resolver = KeybindingResolver::new(&config);
2352
2353 let save_event = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL);
2355 assert_eq!(
2356 resolver.resolve(&save_event, KeyContext::Normal),
2357 Action::Save
2358 );
2359 assert_eq!(
2360 resolver.resolve(&save_event, KeyContext::Popup),
2361 Action::Save
2362 );
2363 }
2365
2366 #[test]
2367 fn test_context_priority_resolution() {
2368 use crate::config::Keybinding;
2369
2370 let mut config = Config::default();
2372 config.keybindings.push(Keybinding {
2373 key: "esc".to_string(),
2374 modifiers: vec![],
2375 keys: vec![],
2376 action: "quit".to_string(), args: HashMap::new(),
2378 when: Some("popup".to_string()),
2379 });
2380
2381 let resolver = KeybindingResolver::new(&config);
2382 let esc_event = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
2383
2384 assert_eq!(
2386 resolver.resolve(&esc_event, KeyContext::Popup),
2387 Action::Quit
2388 );
2389
2390 assert_eq!(
2392 resolver.resolve(&esc_event, KeyContext::Normal),
2393 Action::RemoveSecondaryCursors
2394 );
2395 }
2396
2397 #[test]
2398 fn test_character_input_in_contexts() {
2399 let config = Config::default();
2400 let resolver = KeybindingResolver::new(&config);
2401
2402 let char_event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2403
2404 assert_eq!(
2406 resolver.resolve(&char_event, KeyContext::Normal),
2407 Action::InsertChar('a')
2408 );
2409 assert_eq!(
2410 resolver.resolve(&char_event, KeyContext::Prompt),
2411 Action::InsertChar('a')
2412 );
2413
2414 assert_eq!(
2416 resolver.resolve(&char_event, KeyContext::Popup),
2417 Action::None
2418 );
2419 }
2420
2421 #[test]
2422 fn test_custom_keybinding_loading() {
2423 use crate::config::Keybinding;
2424
2425 let mut config = Config::default();
2426
2427 config.keybindings.push(Keybinding {
2429 key: "f".to_string(),
2430 modifiers: vec!["ctrl".to_string()],
2431 keys: vec![],
2432 action: "command_palette".to_string(),
2433 args: HashMap::new(),
2434 when: None, });
2436
2437 let resolver = KeybindingResolver::new(&config);
2438
2439 let ctrl_f = KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL);
2441 assert_eq!(
2442 resolver.resolve(&ctrl_f, KeyContext::Normal),
2443 Action::CommandPalette
2444 );
2445
2446 let ctrl_k = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
2448 assert_eq!(
2449 resolver.resolve(&ctrl_k, KeyContext::Prompt),
2450 Action::PromptDeleteToLineEnd
2451 );
2452 assert_eq!(
2453 resolver.resolve(&ctrl_k, KeyContext::Normal),
2454 Action::DeleteToLineEnd
2455 );
2456 }
2457
2458 #[test]
2459 fn test_all_context_default_bindings_exist() {
2460 let config = Config::default();
2461 let resolver = KeybindingResolver::new(&config);
2462
2463 assert!(resolver.default_bindings.contains_key(&KeyContext::Normal));
2465 assert!(resolver.default_bindings.contains_key(&KeyContext::Prompt));
2466 assert!(resolver.default_bindings.contains_key(&KeyContext::Popup));
2467 assert!(resolver
2468 .default_bindings
2469 .contains_key(&KeyContext::FileExplorer));
2470 assert!(resolver.default_bindings.contains_key(&KeyContext::Menu));
2471
2472 assert!(!resolver.default_bindings[&KeyContext::Normal].is_empty());
2474 assert!(!resolver.default_bindings[&KeyContext::Prompt].is_empty());
2475 assert!(!resolver.default_bindings[&KeyContext::Popup].is_empty());
2476 assert!(!resolver.default_bindings[&KeyContext::FileExplorer].is_empty());
2477 assert!(!resolver.default_bindings[&KeyContext::Menu].is_empty());
2478 }
2479
2480 #[test]
2481 fn test_resolve_determinism() {
2482 let config = Config::default();
2484 let resolver = KeybindingResolver::new(&config);
2485
2486 let test_cases = vec![
2487 (KeyCode::Left, KeyModifiers::empty(), KeyContext::Normal),
2488 (
2489 KeyCode::Esc,
2490 KeyModifiers::empty(),
2491 KeyContext::FileExplorer,
2492 ),
2493 (KeyCode::Enter, KeyModifiers::empty(), KeyContext::Prompt),
2494 (KeyCode::Down, KeyModifiers::empty(), KeyContext::Popup),
2495 ];
2496
2497 for (key_code, modifiers, context) in test_cases {
2498 let event = KeyEvent::new(key_code, modifiers);
2499 let action1 = resolver.resolve(&event, context);
2500 let action2 = resolver.resolve(&event, context);
2501 let action3 = resolver.resolve(&event, context);
2502
2503 assert_eq!(action1, action2, "Resolve should be deterministic");
2504 assert_eq!(action2, action3, "Resolve should be deterministic");
2505 }
2506 }
2507
2508 #[test]
2509 fn test_modifier_combinations() {
2510 let config = Config::default();
2511 let resolver = KeybindingResolver::new(&config);
2512
2513 let char_s = KeyCode::Char('s');
2515
2516 let no_mod = KeyEvent::new(char_s, KeyModifiers::empty());
2517 let ctrl = KeyEvent::new(char_s, KeyModifiers::CONTROL);
2518 let shift = KeyEvent::new(char_s, KeyModifiers::SHIFT);
2519 let ctrl_shift = KeyEvent::new(char_s, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
2520
2521 let action_no_mod = resolver.resolve(&no_mod, KeyContext::Normal);
2522 let action_ctrl = resolver.resolve(&ctrl, KeyContext::Normal);
2523 let action_shift = resolver.resolve(&shift, KeyContext::Normal);
2524 let action_ctrl_shift = resolver.resolve(&ctrl_shift, KeyContext::Normal);
2525
2526 assert_eq!(action_no_mod, Action::InsertChar('s'));
2528 assert_eq!(action_ctrl, Action::Save);
2529 assert_eq!(action_shift, Action::InsertChar('s')); assert_eq!(action_ctrl_shift, Action::None);
2532 }
2533
2534 #[test]
2535 fn test_scroll_keybindings() {
2536 let config = Config::default();
2537 let resolver = KeybindingResolver::new(&config);
2538
2539 let ctrl_up = KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL);
2541 assert_eq!(
2542 resolver.resolve(&ctrl_up, KeyContext::Normal),
2543 Action::ScrollUp,
2544 "Ctrl+Up should resolve to ScrollUp"
2545 );
2546
2547 let ctrl_down = KeyEvent::new(KeyCode::Down, KeyModifiers::CONTROL);
2549 assert_eq!(
2550 resolver.resolve(&ctrl_down, KeyContext::Normal),
2551 Action::ScrollDown,
2552 "Ctrl+Down should resolve to ScrollDown"
2553 );
2554 }
2555
2556 #[test]
2557 fn test_lsp_completion_keybinding() {
2558 let config = Config::default();
2559 let resolver = KeybindingResolver::new(&config);
2560
2561 let ctrl_space = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::CONTROL);
2563 assert_eq!(
2564 resolver.resolve(&ctrl_space, KeyContext::Normal),
2565 Action::LspCompletion,
2566 "Ctrl+Space should resolve to LspCompletion"
2567 );
2568 }
2569
2570 #[test]
2571 fn test_terminal_key_equivalents() {
2572 let ctrl = KeyModifiers::CONTROL;
2574
2575 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2577 assert_eq!(slash_equivs, vec![(KeyCode::Char('7'), ctrl)]);
2578
2579 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2580 assert_eq!(seven_equivs, vec![(KeyCode::Char('/'), ctrl)]);
2581
2582 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2584 assert_eq!(backspace_equivs, vec![(KeyCode::Char('h'), ctrl)]);
2585
2586 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2587 assert_eq!(h_equivs, vec![(KeyCode::Backspace, ctrl)]);
2588
2589 let a_equivs = terminal_key_equivalents(KeyCode::Char('a'), ctrl);
2591 assert!(a_equivs.is_empty());
2592
2593 let slash_no_ctrl = terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty());
2595 assert!(slash_no_ctrl.is_empty());
2596 }
2597
2598 #[test]
2599 fn test_terminal_key_equivalents_auto_binding() {
2600 let config = Config::default();
2601 let resolver = KeybindingResolver::new(&config);
2602
2603 let ctrl_slash = KeyEvent::new(KeyCode::Char('/'), KeyModifiers::CONTROL);
2605 let action_slash = resolver.resolve(&ctrl_slash, KeyContext::Normal);
2606 assert_eq!(
2607 action_slash,
2608 Action::ToggleComment,
2609 "Ctrl+/ should resolve to ToggleComment"
2610 );
2611
2612 let ctrl_7 = KeyEvent::new(KeyCode::Char('7'), KeyModifiers::CONTROL);
2614 let action_7 = resolver.resolve(&ctrl_7, KeyContext::Normal);
2615 assert_eq!(
2616 action_7,
2617 Action::ToggleComment,
2618 "Ctrl+7 should resolve to ToggleComment (terminal equivalent of Ctrl+/)"
2619 );
2620 }
2621
2622 #[test]
2623 fn test_terminal_key_equivalents_normalization() {
2624 let ctrl = KeyModifiers::CONTROL;
2629
2630 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2633 assert_eq!(
2634 slash_equivs,
2635 vec![(KeyCode::Char('7'), ctrl)],
2636 "Ctrl+/ should map to Ctrl+7"
2637 );
2638 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2639 assert_eq!(
2640 seven_equivs,
2641 vec![(KeyCode::Char('/'), ctrl)],
2642 "Ctrl+7 should map back to Ctrl+/"
2643 );
2644
2645 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2648 assert_eq!(
2649 backspace_equivs,
2650 vec![(KeyCode::Char('h'), ctrl)],
2651 "Ctrl+Backspace should map to Ctrl+H"
2652 );
2653 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2654 assert_eq!(
2655 h_equivs,
2656 vec![(KeyCode::Backspace, ctrl)],
2657 "Ctrl+H should map back to Ctrl+Backspace"
2658 );
2659
2660 let space_equivs = terminal_key_equivalents(KeyCode::Char(' '), ctrl);
2663 assert_eq!(
2664 space_equivs,
2665 vec![(KeyCode::Char('@'), ctrl)],
2666 "Ctrl+Space should map to Ctrl+@"
2667 );
2668 let at_equivs = terminal_key_equivalents(KeyCode::Char('@'), ctrl);
2669 assert_eq!(
2670 at_equivs,
2671 vec![(KeyCode::Char(' '), ctrl)],
2672 "Ctrl+@ should map back to Ctrl+Space"
2673 );
2674
2675 let minus_equivs = terminal_key_equivalents(KeyCode::Char('-'), ctrl);
2678 assert_eq!(
2679 minus_equivs,
2680 vec![(KeyCode::Char('_'), ctrl)],
2681 "Ctrl+- should map to Ctrl+_"
2682 );
2683 let underscore_equivs = terminal_key_equivalents(KeyCode::Char('_'), ctrl);
2684 assert_eq!(
2685 underscore_equivs,
2686 vec![(KeyCode::Char('-'), ctrl)],
2687 "Ctrl+_ should map back to Ctrl+-"
2688 );
2689
2690 assert!(
2692 terminal_key_equivalents(KeyCode::Char('a'), ctrl).is_empty(),
2693 "Ctrl+A should have no terminal equivalents"
2694 );
2695 assert!(
2696 terminal_key_equivalents(KeyCode::Char('z'), ctrl).is_empty(),
2697 "Ctrl+Z should have no terminal equivalents"
2698 );
2699 assert!(
2700 terminal_key_equivalents(KeyCode::Enter, ctrl).is_empty(),
2701 "Ctrl+Enter should have no terminal equivalents"
2702 );
2703
2704 assert!(
2706 terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty()).is_empty(),
2707 "/ without Ctrl should have no equivalents"
2708 );
2709 assert!(
2710 terminal_key_equivalents(KeyCode::Char('7'), KeyModifiers::SHIFT).is_empty(),
2711 "Shift+7 should have no equivalents"
2712 );
2713 assert!(
2714 terminal_key_equivalents(KeyCode::Char('h'), KeyModifiers::ALT).is_empty(),
2715 "Alt+H should have no equivalents"
2716 );
2717
2718 let ctrl_shift = KeyModifiers::CONTROL | KeyModifiers::SHIFT;
2721 let ctrl_shift_h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl_shift);
2722 assert!(
2723 ctrl_shift_h_equivs.is_empty(),
2724 "Ctrl+Shift+H should NOT map to Ctrl+Shift+Backspace"
2725 );
2726 }
2727
2728 #[test]
2729 fn test_no_duplicate_keybindings_in_keymaps() {
2730 use std::collections::HashMap;
2733
2734 let keymaps: &[(&str, &str)] = &[
2735 ("default", include_str!("../../keymaps/default.json")),
2736 ("macos", include_str!("../../keymaps/macos.json")),
2737 ];
2738
2739 for (keymap_name, json_content) in keymaps {
2740 let keymap: crate::config::KeymapConfig = serde_json::from_str(json_content)
2741 .unwrap_or_else(|e| panic!("Failed to parse keymap '{}': {}", keymap_name, e));
2742
2743 let mut seen: HashMap<(String, Vec<String>, String), String> = HashMap::new();
2745 let mut duplicates: Vec<String> = Vec::new();
2746
2747 for binding in &keymap.bindings {
2748 let when = binding.when.clone().unwrap_or_default();
2749 let key_id = (binding.key.clone(), binding.modifiers.clone(), when.clone());
2750
2751 if let Some(existing_action) = seen.get(&key_id) {
2752 duplicates.push(format!(
2753 "Duplicate in '{}': key='{}', modifiers={:?}, when='{}' -> '{}' vs '{}'",
2754 keymap_name,
2755 binding.key,
2756 binding.modifiers,
2757 when,
2758 existing_action,
2759 binding.action
2760 ));
2761 } else {
2762 seen.insert(key_id, binding.action.clone());
2763 }
2764 }
2765
2766 assert!(
2767 duplicates.is_empty(),
2768 "Found duplicate keybindings:\n{}",
2769 duplicates.join("\n")
2770 );
2771 }
2772 }
2773}