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
25pub fn format_keybinding(keycode: &KeyCode, modifiers: &KeyModifiers) -> String {
29 let mut result = String::new();
30
31 let (ctrl_label, alt_label, shift_label) = if use_macos_symbols() {
33 ("⌃", "⌥", "⇧")
34 } else {
35 ("Ctrl", "Alt", "Shift")
36 };
37
38 let use_plus = !use_macos_symbols();
39
40 if modifiers.contains(KeyModifiers::CONTROL) {
41 result.push_str(ctrl_label);
42 if use_plus {
43 result.push('+');
44 }
45 }
46 if modifiers.contains(KeyModifiers::ALT) {
47 result.push_str(alt_label);
48 if use_plus {
49 result.push('+');
50 }
51 }
52 if modifiers.contains(KeyModifiers::SHIFT) {
53 result.push_str(shift_label);
54 if use_plus {
55 result.push('+');
56 }
57 }
58
59 match keycode {
60 KeyCode::Enter => result.push_str("Enter"),
61 KeyCode::Backspace => result.push_str("Backspace"),
62 KeyCode::Delete => result.push_str("Del"),
63 KeyCode::Tab => result.push_str("Tab"),
64 KeyCode::Esc => result.push_str("Esc"),
65 KeyCode::Left => result.push('←'),
66 KeyCode::Right => result.push('→'),
67 KeyCode::Up => result.push('↑'),
68 KeyCode::Down => result.push('↓'),
69 KeyCode::Home => result.push_str("Home"),
70 KeyCode::End => result.push_str("End"),
71 KeyCode::PageUp => result.push_str("PgUp"),
72 KeyCode::PageDown => result.push_str("PgDn"),
73 KeyCode::Char(' ') => result.push_str("Space"),
74 KeyCode::Char(c) => result.push_str(&c.to_uppercase().to_string()),
75 KeyCode::F(n) => result.push_str(&format!("F{}", n)),
76 _ => return String::new(),
77 }
78
79 result
80}
81
82fn keybinding_priority_score(key: &KeyCode) -> u32 {
86 match key {
87 KeyCode::Char('@') => 100, KeyCode::Char('7') => 100, KeyCode::Char('_') => 100, _ => 0,
94 }
95}
96
97pub fn terminal_key_equivalents(
108 key: KeyCode,
109 modifiers: KeyModifiers,
110) -> Vec<(KeyCode, KeyModifiers)> {
111 let mut equivalents = Vec::new();
112
113 if modifiers.contains(KeyModifiers::CONTROL) {
115 let base_modifiers = modifiers; match key {
118 KeyCode::Char('/') => {
120 equivalents.push((KeyCode::Char('7'), base_modifiers));
121 }
122 KeyCode::Char('7') => {
123 equivalents.push((KeyCode::Char('/'), base_modifiers));
124 }
125
126 KeyCode::Backspace => {
128 equivalents.push((KeyCode::Char('h'), base_modifiers));
129 }
130 KeyCode::Char('h') if modifiers == KeyModifiers::CONTROL => {
131 equivalents.push((KeyCode::Backspace, base_modifiers));
133 }
134
135 KeyCode::Char(' ') => {
137 equivalents.push((KeyCode::Char('@'), base_modifiers));
138 }
139 KeyCode::Char('@') => {
140 equivalents.push((KeyCode::Char(' '), base_modifiers));
141 }
142
143 KeyCode::Char('-') => {
145 equivalents.push((KeyCode::Char('_'), base_modifiers));
146 }
147 KeyCode::Char('_') => {
148 equivalents.push((KeyCode::Char('-'), base_modifiers));
149 }
150
151 _ => {}
152 }
153 }
154
155 equivalents
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
160pub enum KeyContext {
161 Global,
163 Normal,
165 Prompt,
167 Popup,
169 FileExplorer,
171 Menu,
173 Terminal,
175 Settings,
177}
178
179impl KeyContext {
180 pub fn allows_text_input(&self) -> bool {
182 matches!(self, Self::Normal | Self::Prompt)
183 }
184
185 pub fn from_when_clause(when: &str) -> Option<Self> {
187 Some(match when.trim() {
188 "global" => Self::Global,
189 "prompt" => Self::Prompt,
190 "popup" => Self::Popup,
191 "fileExplorer" | "file_explorer" => Self::FileExplorer,
192 "normal" => Self::Normal,
193 "menu" => Self::Menu,
194 "terminal" => Self::Terminal,
195 "settings" => Self::Settings,
196 _ => return None,
197 })
198 }
199
200 pub fn to_when_clause(self) -> &'static str {
202 match self {
203 Self::Global => "global",
204 Self::Normal => "normal",
205 Self::Prompt => "prompt",
206 Self::Popup => "popup",
207 Self::FileExplorer => "fileExplorer",
208 Self::Menu => "menu",
209 Self::Terminal => "terminal",
210 Self::Settings => "settings",
211 }
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
217pub enum Action {
218 InsertChar(char),
220 InsertNewline,
221 InsertTab,
222
223 MoveLeft,
225 MoveRight,
226 MoveUp,
227 MoveDown,
228 MoveWordLeft,
229 MoveWordRight,
230 MoveWordEnd, MoveLineStart,
232 MoveLineEnd,
233 MovePageUp,
234 MovePageDown,
235 MoveDocumentStart,
236 MoveDocumentEnd,
237
238 SelectLeft,
240 SelectRight,
241 SelectUp,
242 SelectDown,
243 SelectWordLeft,
244 SelectWordRight,
245 SelectWordEnd, SelectLineStart,
247 SelectLineEnd,
248 SelectDocumentStart,
249 SelectDocumentEnd,
250 SelectPageUp,
251 SelectPageDown,
252 SelectAll,
253 SelectWord,
254 SelectLine,
255 ExpandSelection,
256
257 BlockSelectLeft,
259 BlockSelectRight,
260 BlockSelectUp,
261 BlockSelectDown,
262
263 DeleteBackward,
265 DeleteForward,
266 DeleteWordBackward,
267 DeleteWordForward,
268 DeleteLine,
269 DeleteToLineEnd,
270 DeleteToLineStart,
271 TransposeChars,
272 OpenLine,
273
274 Recenter,
276
277 SetMark,
279
280 Copy,
282 CopyWithTheme(String),
283 Cut,
284 Paste,
285
286 YankWordForward,
288 YankWordBackward,
289 YankToLineEnd,
290 YankToLineStart,
291
292 AddCursorAbove,
294 AddCursorBelow,
295 AddCursorNextMatch,
296 RemoveSecondaryCursors,
297
298 Save,
300 SaveAs,
301 Open,
302 SwitchProject,
303 New,
304 Close,
305 CloseTab,
306 Quit,
307 ForceQuit,
308 Revert,
309 ToggleAutoRevert,
310 FormatBuffer,
311 TrimTrailingWhitespace,
312 EnsureFinalNewline,
313
314 GotoLine,
316 GoToMatchingBracket,
317 JumpToNextError,
318 JumpToPreviousError,
319
320 SmartHome,
322 DedentSelection,
323 ToggleComment,
324
325 SetBookmark(char),
327 JumpToBookmark(char),
328 ClearBookmark(char),
329 ListBookmarks,
330
331 ToggleSearchCaseSensitive,
333 ToggleSearchWholeWord,
334 ToggleSearchRegex,
335 ToggleSearchConfirmEach,
336
337 StartMacroRecording,
339 StopMacroRecording,
340 PlayMacro(char),
341 ToggleMacroRecording(char),
342 ShowMacro(char),
343 ListMacros,
344 PromptRecordMacro,
345 PromptPlayMacro,
346 PlayLastMacro,
347
348 PromptSetBookmark,
350 PromptJumpToBookmark,
351
352 Undo,
354 Redo,
355
356 ScrollUp,
358 ScrollDown,
359 ShowHelp,
360 ShowKeyboardShortcuts,
361 ShowWarnings,
362 ShowStatusLog,
363 ShowLspStatus,
364 ClearWarnings,
365 CommandPalette, QuickOpen,
368 ToggleLineWrap,
369 ToggleComposeMode,
370 SetComposeWidth,
371 SelectTheme,
372 SelectKeybindingMap,
373 SelectCursorStyle,
374 SelectLocale,
375
376 NextBuffer,
378 PrevBuffer,
379 SwitchToPreviousTab,
380 SwitchToTabByName,
381
382 ScrollTabsLeft,
384 ScrollTabsRight,
385
386 NavigateBack,
388 NavigateForward,
389
390 SplitHorizontal,
392 SplitVertical,
393 CloseSplit,
394 NextSplit,
395 PrevSplit,
396 IncreaseSplitSize,
397 DecreaseSplitSize,
398 ToggleMaximizeSplit,
399
400 PromptConfirm,
402 PromptConfirmWithText(String),
404 PromptCancel,
405 PromptBackspace,
406 PromptDelete,
407 PromptMoveLeft,
408 PromptMoveRight,
409 PromptMoveStart,
410 PromptMoveEnd,
411 PromptSelectPrev,
412 PromptSelectNext,
413 PromptPageUp,
414 PromptPageDown,
415 PromptAcceptSuggestion,
416 PromptMoveWordLeft,
417 PromptMoveWordRight,
418 PromptDeleteWordForward,
420 PromptDeleteWordBackward,
421 PromptDeleteToLineEnd,
422 PromptCopy,
423 PromptCut,
424 PromptPaste,
425 PromptMoveLeftSelecting,
427 PromptMoveRightSelecting,
428 PromptMoveHomeSelecting,
429 PromptMoveEndSelecting,
430 PromptSelectWordLeft,
431 PromptSelectWordRight,
432 PromptSelectAll,
433
434 FileBrowserToggleHidden,
436
437 PopupSelectNext,
439 PopupSelectPrev,
440 PopupPageUp,
441 PopupPageDown,
442 PopupConfirm,
443 PopupCancel,
444
445 ToggleFileExplorer,
447 ToggleMenuBar,
449 ToggleTabBar,
451 FocusFileExplorer,
452 FocusEditor,
453 FileExplorerUp,
454 FileExplorerDown,
455 FileExplorerPageUp,
456 FileExplorerPageDown,
457 FileExplorerExpand,
458 FileExplorerCollapse,
459 FileExplorerOpen,
460 FileExplorerRefresh,
461 FileExplorerNewFile,
462 FileExplorerNewDirectory,
463 FileExplorerDelete,
464 FileExplorerRename,
465 FileExplorerToggleHidden,
466 FileExplorerToggleGitignored,
467
468 LspCompletion,
470 LspGotoDefinition,
471 LspReferences,
472 LspRename,
473 LspHover,
474 LspSignatureHelp,
475 LspCodeActions,
476 LspRestart,
477 LspStop,
478 ToggleInlayHints,
479 ToggleMouseHover,
480
481 ToggleLineNumbers,
483 ToggleMouseCapture,
484 ToggleDebugHighlights, SetBackground,
486 SetBackgroundBlend,
487
488 SetTabSize,
490 SetLineEnding,
491 SetLanguage,
492 ToggleIndentationStyle,
493 ToggleTabIndicators,
494 ResetBufferSettings,
495
496 DumpConfig,
498
499 Search,
501 FindInSelection,
502 FindNext,
503 FindPrevious,
504 FindSelectionNext, FindSelectionPrevious, Replace,
507 QueryReplace, MenuActivate, MenuClose, MenuLeft, MenuRight, MenuUp, MenuDown, MenuExecute, MenuOpen(String), SwitchKeybindingMap(String), PluginAction(String),
524
525 OpenSettings, CloseSettings, SettingsSave, SettingsReset, SettingsToggleFocus, SettingsActivate, SettingsSearch, SettingsHelp, SettingsIncrement, SettingsDecrement, OpenTerminal, CloseTerminal, FocusTerminal, TerminalEscape, ToggleKeyboardCapture, TerminalPaste, ShellCommand, ShellCommandReplace, ToUpperCase, ToLowerCase, CalibrateInput, EventDebug, None,
561}
562
563impl Action {
564 fn with_char(
565 args: &HashMap<String, serde_json::Value>,
566 make_action: impl FnOnce(char) -> Self,
567 ) -> Option<Self> {
568 if let Some(serde_json::Value::String(value)) = args.get("char") {
569 value.chars().next().map(make_action)
570 } else {
571 None
572 }
573 }
574
575 pub fn from_str(s: &str, args: &HashMap<String, serde_json::Value>) -> Option<Self> {
577 Some(match s {
578 "insert_char" => return Self::with_char(args, Self::InsertChar),
579 "insert_newline" => Self::InsertNewline,
580 "insert_tab" => Self::InsertTab,
581
582 "move_left" => Self::MoveLeft,
583 "move_right" => Self::MoveRight,
584 "move_up" => Self::MoveUp,
585 "move_down" => Self::MoveDown,
586 "move_word_left" => Self::MoveWordLeft,
587 "move_word_right" => Self::MoveWordRight,
588 "move_word_end" => Self::MoveWordEnd,
589 "move_line_start" => Self::MoveLineStart,
590 "move_line_end" => Self::MoveLineEnd,
591 "move_page_up" => Self::MovePageUp,
592 "move_page_down" => Self::MovePageDown,
593 "move_document_start" => Self::MoveDocumentStart,
594 "move_document_end" => Self::MoveDocumentEnd,
595
596 "select_left" => Self::SelectLeft,
597 "select_right" => Self::SelectRight,
598 "select_up" => Self::SelectUp,
599 "select_down" => Self::SelectDown,
600 "select_word_left" => Self::SelectWordLeft,
601 "select_word_right" => Self::SelectWordRight,
602 "select_word_end" => Self::SelectWordEnd,
603 "select_line_start" => Self::SelectLineStart,
604 "select_line_end" => Self::SelectLineEnd,
605 "select_document_start" => Self::SelectDocumentStart,
606 "select_document_end" => Self::SelectDocumentEnd,
607 "select_page_up" => Self::SelectPageUp,
608 "select_page_down" => Self::SelectPageDown,
609 "select_all" => Self::SelectAll,
610 "select_word" => Self::SelectWord,
611 "select_line" => Self::SelectLine,
612 "expand_selection" => Self::ExpandSelection,
613
614 "block_select_left" => Self::BlockSelectLeft,
616 "block_select_right" => Self::BlockSelectRight,
617 "block_select_up" => Self::BlockSelectUp,
618 "block_select_down" => Self::BlockSelectDown,
619
620 "delete_backward" => Self::DeleteBackward,
621 "delete_forward" => Self::DeleteForward,
622 "delete_word_backward" => Self::DeleteWordBackward,
623 "delete_word_forward" => Self::DeleteWordForward,
624 "delete_line" => Self::DeleteLine,
625 "delete_to_line_end" => Self::DeleteToLineEnd,
626 "delete_to_line_start" => Self::DeleteToLineStart,
627 "transpose_chars" => Self::TransposeChars,
628 "open_line" => Self::OpenLine,
629 "recenter" => Self::Recenter,
630 "set_mark" => Self::SetMark,
631
632 "copy" => Self::Copy,
633 "copy_with_theme" => {
634 let theme = args.get("theme").and_then(|v| v.as_str()).unwrap_or("");
636 Self::CopyWithTheme(theme.to_string())
637 }
638 "cut" => Self::Cut,
639 "paste" => Self::Paste,
640
641 "yank_word_forward" => Self::YankWordForward,
643 "yank_word_backward" => Self::YankWordBackward,
644 "yank_to_line_end" => Self::YankToLineEnd,
645 "yank_to_line_start" => Self::YankToLineStart,
646
647 "add_cursor_above" => Self::AddCursorAbove,
648 "add_cursor_below" => Self::AddCursorBelow,
649 "add_cursor_next_match" => Self::AddCursorNextMatch,
650 "remove_secondary_cursors" => Self::RemoveSecondaryCursors,
651
652 "save" => Self::Save,
653 "save_as" => Self::SaveAs,
654 "open" => Self::Open,
655 "switch_project" => Self::SwitchProject,
656 "new" => Self::New,
657 "close" => Self::Close,
658 "close_tab" => Self::CloseTab,
659 "quit" => Self::Quit,
660 "force_quit" => Self::ForceQuit,
661 "revert" => Self::Revert,
662 "toggle_auto_revert" => Self::ToggleAutoRevert,
663 "format_buffer" => Self::FormatBuffer,
664 "goto_line" => Self::GotoLine,
665 "goto_matching_bracket" => Self::GoToMatchingBracket,
666 "jump_to_next_error" => Self::JumpToNextError,
667 "jump_to_previous_error" => Self::JumpToPreviousError,
668
669 "smart_home" => Self::SmartHome,
670 "dedent_selection" => Self::DedentSelection,
671 "toggle_comment" => Self::ToggleComment,
672
673 "set_bookmark" => return Self::with_char(args, Self::SetBookmark),
674 "jump_to_bookmark" => return Self::with_char(args, Self::JumpToBookmark),
675 "clear_bookmark" => return Self::with_char(args, Self::ClearBookmark),
676
677 "list_bookmarks" => Self::ListBookmarks,
678
679 "toggle_search_case_sensitive" => Self::ToggleSearchCaseSensitive,
680 "toggle_search_whole_word" => Self::ToggleSearchWholeWord,
681 "toggle_search_regex" => Self::ToggleSearchRegex,
682 "toggle_search_confirm_each" => Self::ToggleSearchConfirmEach,
683
684 "start_macro_recording" => Self::StartMacroRecording,
685 "stop_macro_recording" => Self::StopMacroRecording,
686 "play_macro" => return Self::with_char(args, Self::PlayMacro),
687 "toggle_macro_recording" => return Self::with_char(args, Self::ToggleMacroRecording),
688
689 "show_macro" => return Self::with_char(args, Self::ShowMacro),
690
691 "list_macros" => Self::ListMacros,
692 "prompt_record_macro" => Self::PromptRecordMacro,
693 "prompt_play_macro" => Self::PromptPlayMacro,
694 "play_last_macro" => Self::PlayLastMacro,
695 "prompt_set_bookmark" => Self::PromptSetBookmark,
696 "prompt_jump_to_bookmark" => Self::PromptJumpToBookmark,
697
698 "undo" => Self::Undo,
699 "redo" => Self::Redo,
700
701 "scroll_up" => Self::ScrollUp,
702 "scroll_down" => Self::ScrollDown,
703 "show_help" => Self::ShowHelp,
704 "keyboard_shortcuts" => Self::ShowKeyboardShortcuts,
705 "show_warnings" => Self::ShowWarnings,
706 "show_status_log" => Self::ShowStatusLog,
707 "show_lsp_status" => Self::ShowLspStatus,
708 "clear_warnings" => Self::ClearWarnings,
709 "command_palette" => Self::CommandPalette,
710 "quick_open" => Self::QuickOpen,
711 "toggle_line_wrap" => Self::ToggleLineWrap,
712 "toggle_compose_mode" => Self::ToggleComposeMode,
713 "set_compose_width" => Self::SetComposeWidth,
714
715 "next_buffer" => Self::NextBuffer,
716 "prev_buffer" => Self::PrevBuffer,
717
718 "navigate_back" => Self::NavigateBack,
719 "navigate_forward" => Self::NavigateForward,
720
721 "split_horizontal" => Self::SplitHorizontal,
722 "split_vertical" => Self::SplitVertical,
723 "close_split" => Self::CloseSplit,
724 "next_split" => Self::NextSplit,
725 "prev_split" => Self::PrevSplit,
726 "increase_split_size" => Self::IncreaseSplitSize,
727 "decrease_split_size" => Self::DecreaseSplitSize,
728 "toggle_maximize_split" => Self::ToggleMaximizeSplit,
729
730 "prompt_confirm" => Self::PromptConfirm,
731 "prompt_cancel" => Self::PromptCancel,
732 "prompt_backspace" => Self::PromptBackspace,
733 "prompt_move_left" => Self::PromptMoveLeft,
734 "prompt_move_right" => Self::PromptMoveRight,
735 "prompt_move_start" => Self::PromptMoveStart,
736 "prompt_move_end" => Self::PromptMoveEnd,
737 "prompt_select_prev" => Self::PromptSelectPrev,
738 "prompt_select_next" => Self::PromptSelectNext,
739 "prompt_page_up" => Self::PromptPageUp,
740 "prompt_page_down" => Self::PromptPageDown,
741 "prompt_accept_suggestion" => Self::PromptAcceptSuggestion,
742 "prompt_delete_word_forward" => Self::PromptDeleteWordForward,
743 "prompt_delete_word_backward" => Self::PromptDeleteWordBackward,
744 "prompt_delete_to_line_end" => Self::PromptDeleteToLineEnd,
745 "prompt_copy" => Self::PromptCopy,
746 "prompt_cut" => Self::PromptCut,
747 "prompt_paste" => Self::PromptPaste,
748 "prompt_move_left_selecting" => Self::PromptMoveLeftSelecting,
749 "prompt_move_right_selecting" => Self::PromptMoveRightSelecting,
750 "prompt_move_home_selecting" => Self::PromptMoveHomeSelecting,
751 "prompt_move_end_selecting" => Self::PromptMoveEndSelecting,
752 "prompt_select_word_left" => Self::PromptSelectWordLeft,
753 "prompt_select_word_right" => Self::PromptSelectWordRight,
754 "prompt_select_all" => Self::PromptSelectAll,
755 "file_browser_toggle_hidden" => Self::FileBrowserToggleHidden,
756 "prompt_move_word_left" => Self::PromptMoveWordLeft,
757 "prompt_move_word_right" => Self::PromptMoveWordRight,
758 "prompt_delete" => Self::PromptDelete,
759
760 "popup_select_next" => Self::PopupSelectNext,
761 "popup_select_prev" => Self::PopupSelectPrev,
762 "popup_page_up" => Self::PopupPageUp,
763 "popup_page_down" => Self::PopupPageDown,
764 "popup_confirm" => Self::PopupConfirm,
765 "popup_cancel" => Self::PopupCancel,
766
767 "toggle_file_explorer" => Self::ToggleFileExplorer,
768 "toggle_menu_bar" => Self::ToggleMenuBar,
769 "toggle_tab_bar" => Self::ToggleTabBar,
770 "focus_file_explorer" => Self::FocusFileExplorer,
771 "focus_editor" => Self::FocusEditor,
772 "file_explorer_up" => Self::FileExplorerUp,
773 "file_explorer_down" => Self::FileExplorerDown,
774 "file_explorer_page_up" => Self::FileExplorerPageUp,
775 "file_explorer_page_down" => Self::FileExplorerPageDown,
776 "file_explorer_expand" => Self::FileExplorerExpand,
777 "file_explorer_collapse" => Self::FileExplorerCollapse,
778 "file_explorer_open" => Self::FileExplorerOpen,
779 "file_explorer_refresh" => Self::FileExplorerRefresh,
780 "file_explorer_new_file" => Self::FileExplorerNewFile,
781 "file_explorer_new_directory" => Self::FileExplorerNewDirectory,
782 "file_explorer_delete" => Self::FileExplorerDelete,
783 "file_explorer_rename" => Self::FileExplorerRename,
784 "file_explorer_toggle_hidden" => Self::FileExplorerToggleHidden,
785 "file_explorer_toggle_gitignored" => Self::FileExplorerToggleGitignored,
786
787 "lsp_completion" => Self::LspCompletion,
788 "lsp_goto_definition" => Self::LspGotoDefinition,
789 "lsp_references" => Self::LspReferences,
790 "lsp_rename" => Self::LspRename,
791 "lsp_hover" => Self::LspHover,
792 "lsp_signature_help" => Self::LspSignatureHelp,
793 "lsp_code_actions" => Self::LspCodeActions,
794 "lsp_restart" => Self::LspRestart,
795 "lsp_stop" => Self::LspStop,
796 "toggle_inlay_hints" => Self::ToggleInlayHints,
797 "toggle_mouse_hover" => Self::ToggleMouseHover,
798
799 "toggle_line_numbers" => Self::ToggleLineNumbers,
800 "toggle_mouse_capture" => Self::ToggleMouseCapture,
801 "toggle_debug_highlights" => Self::ToggleDebugHighlights,
802 "set_background" => Self::SetBackground,
803 "set_background_blend" => Self::SetBackgroundBlend,
804 "select_theme" => Self::SelectTheme,
805 "select_keybinding_map" => Self::SelectKeybindingMap,
806 "select_locale" => Self::SelectLocale,
807
808 "set_tab_size" => Self::SetTabSize,
810 "set_line_ending" => Self::SetLineEnding,
811 "toggle_indentation_style" => Self::ToggleIndentationStyle,
812 "toggle_tab_indicators" => Self::ToggleTabIndicators,
813 "reset_buffer_settings" => Self::ResetBufferSettings,
814
815 "dump_config" => Self::DumpConfig,
816
817 "search" => Self::Search,
818 "find_in_selection" => Self::FindInSelection,
819 "find_next" => Self::FindNext,
820 "find_previous" => Self::FindPrevious,
821 "find_selection_next" => Self::FindSelectionNext,
822 "find_selection_previous" => Self::FindSelectionPrevious,
823 "replace" => Self::Replace,
824 "query_replace" => Self::QueryReplace,
825
826 "menu_activate" => Self::MenuActivate,
827 "menu_close" => Self::MenuClose,
828 "menu_left" => Self::MenuLeft,
829 "menu_right" => Self::MenuRight,
830 "menu_up" => Self::MenuUp,
831 "menu_down" => Self::MenuDown,
832 "menu_execute" => Self::MenuExecute,
833 "menu_open" => {
834 let name = args.get("name")?.as_str()?;
835 Self::MenuOpen(name.to_string())
836 }
837
838 "switch_keybinding_map" => {
839 let map_name = args.get("map")?.as_str()?;
840 Self::SwitchKeybindingMap(map_name.to_string())
841 }
842
843 "open_terminal" => Self::OpenTerminal,
845 "close_terminal" => Self::CloseTerminal,
846 "focus_terminal" => Self::FocusTerminal,
847 "terminal_escape" => Self::TerminalEscape,
848 "toggle_keyboard_capture" => Self::ToggleKeyboardCapture,
849 "terminal_paste" => Self::TerminalPaste,
850
851 "shell_command" => Self::ShellCommand,
853 "shell_command_replace" => Self::ShellCommandReplace,
854
855 "to_upper_case" => Self::ToUpperCase,
857 "to_lower_case" => Self::ToLowerCase,
858
859 "calibrate_input" => Self::CalibrateInput,
861
862 "event_debug" => Self::EventDebug,
864
865 "open_settings" => Self::OpenSettings,
867 "close_settings" => Self::CloseSettings,
868 "settings_save" => Self::SettingsSave,
869 "settings_reset" => Self::SettingsReset,
870 "settings_toggle_focus" => Self::SettingsToggleFocus,
871 "settings_activate" => Self::SettingsActivate,
872 "settings_search" => Self::SettingsSearch,
873 "settings_help" => Self::SettingsHelp,
874 "settings_increment" => Self::SettingsIncrement,
875 "settings_decrement" => Self::SettingsDecrement,
876
877 _ => return None,
878 })
879 }
880
881 pub fn is_movement_or_editing(&self) -> bool {
884 matches!(
885 self,
886 Action::MoveLeft
888 | Action::MoveRight
889 | Action::MoveUp
890 | Action::MoveDown
891 | Action::MoveWordLeft
892 | Action::MoveWordRight
893 | Action::MoveWordEnd
894 | Action::MoveLineStart
895 | Action::MoveLineEnd
896 | Action::MovePageUp
897 | Action::MovePageDown
898 | Action::MoveDocumentStart
899 | Action::MoveDocumentEnd
900 | Action::SelectLeft
902 | Action::SelectRight
903 | Action::SelectUp
904 | Action::SelectDown
905 | Action::SelectWordLeft
906 | Action::SelectWordRight
907 | Action::SelectWordEnd
908 | Action::SelectLineStart
909 | Action::SelectLineEnd
910 | Action::SelectDocumentStart
911 | Action::SelectDocumentEnd
912 | Action::SelectPageUp
913 | Action::SelectPageDown
914 | Action::SelectAll
915 | Action::SelectWord
916 | Action::SelectLine
917 | Action::ExpandSelection
918 | Action::BlockSelectLeft
920 | Action::BlockSelectRight
921 | Action::BlockSelectUp
922 | Action::BlockSelectDown
923 | Action::InsertChar(_)
925 | Action::InsertNewline
926 | Action::InsertTab
927 | Action::DeleteBackward
928 | Action::DeleteForward
929 | Action::DeleteWordBackward
930 | Action::DeleteWordForward
931 | Action::DeleteLine
932 | Action::DeleteToLineEnd
933 | Action::DeleteToLineStart
934 | Action::TransposeChars
935 | Action::OpenLine
936 | Action::Cut
938 | Action::Paste
939 | Action::Undo
941 | Action::Redo
942 )
943 }
944
945 pub fn is_editing(&self) -> bool {
948 matches!(
949 self,
950 Action::InsertChar(_)
951 | Action::InsertNewline
952 | Action::InsertTab
953 | Action::DeleteBackward
954 | Action::DeleteForward
955 | Action::DeleteWordBackward
956 | Action::DeleteWordForward
957 | Action::DeleteLine
958 | Action::DeleteToLineEnd
959 | Action::DeleteToLineStart
960 | Action::TransposeChars
961 | Action::OpenLine
962 | Action::Cut
963 | Action::Paste
964 )
965 }
966}
967
968#[derive(Debug, Clone, PartialEq)]
970pub enum ChordResolution {
971 Complete(Action),
973 Partial,
975 NoMatch,
977}
978
979#[derive(Clone)]
981pub struct KeybindingResolver {
982 bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
985
986 default_bindings: HashMap<KeyContext, HashMap<(KeyCode, KeyModifiers), Action>>,
988
989 chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
992
993 default_chord_bindings: HashMap<KeyContext, HashMap<Vec<(KeyCode, KeyModifiers)>, Action>>,
995}
996
997impl KeybindingResolver {
998 pub fn new(config: &Config) -> Self {
1000 let mut resolver = Self {
1001 bindings: HashMap::new(),
1002 default_bindings: HashMap::new(),
1003 chord_bindings: HashMap::new(),
1004 default_chord_bindings: HashMap::new(),
1005 };
1006
1007 let map_bindings = config.resolve_keymap(&config.active_keybinding_map);
1009 resolver.load_default_bindings_from_vec(&map_bindings);
1010
1011 resolver.load_bindings_from_vec(&config.keybindings);
1013
1014 resolver
1015 }
1016
1017 fn load_default_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1019 for binding in bindings {
1020 let context = if let Some(ref when) = binding.when {
1022 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1023 } else {
1024 KeyContext::Normal
1025 };
1026
1027 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1028 if !binding.keys.is_empty() {
1030 let mut sequence = Vec::new();
1032 for key_press in &binding.keys {
1033 if let Some(key_code) = Self::parse_key(&key_press.key) {
1034 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1035 sequence.push((key_code, modifiers));
1036 } else {
1037 break;
1039 }
1040 }
1041
1042 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1044 self.default_chord_bindings
1045 .entry(context)
1046 .or_default()
1047 .insert(sequence, action);
1048 }
1049 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1050 let modifiers = Self::parse_modifiers(&binding.modifiers);
1052
1053 self.insert_binding_with_equivalents(
1055 context,
1056 key_code,
1057 modifiers,
1058 action,
1059 &binding.key,
1060 );
1061 }
1062 }
1063 }
1064 }
1065
1066 fn insert_binding_with_equivalents(
1069 &mut self,
1070 context: KeyContext,
1071 key_code: KeyCode,
1072 modifiers: KeyModifiers,
1073 action: Action,
1074 key_name: &str,
1075 ) {
1076 let context_bindings = self.default_bindings.entry(context).or_default();
1077
1078 context_bindings.insert((key_code, modifiers), action.clone());
1080
1081 let equivalents = terminal_key_equivalents(key_code, modifiers);
1083 for (equiv_key, equiv_mods) in equivalents {
1084 if let Some(existing_action) = context_bindings.get(&(equiv_key, equiv_mods)) {
1086 if existing_action != &action {
1088 let equiv_name = format!("{:?}", equiv_key);
1089 tracing::warn!(
1090 "Terminal key equivalent conflict in {:?} context: {} (equivalent of {}) \
1091 is bound to {:?}, but {} is bound to {:?}. \
1092 The explicit binding takes precedence.",
1093 context,
1094 equiv_name,
1095 key_name,
1096 existing_action,
1097 key_name,
1098 action
1099 );
1100 }
1101 } else {
1103 context_bindings.insert((equiv_key, equiv_mods), action.clone());
1105 }
1106 }
1107 }
1108
1109 fn load_bindings_from_vec(&mut self, bindings: &[crate::config::Keybinding]) {
1111 for binding in bindings {
1112 let context = if let Some(ref when) = binding.when {
1114 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1115 } else {
1116 KeyContext::Normal
1117 };
1118
1119 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1120 if !binding.keys.is_empty() {
1122 let mut sequence = Vec::new();
1124 for key_press in &binding.keys {
1125 if let Some(key_code) = Self::parse_key(&key_press.key) {
1126 let modifiers = Self::parse_modifiers(&key_press.modifiers);
1127 sequence.push((key_code, modifiers));
1128 } else {
1129 break;
1131 }
1132 }
1133
1134 if sequence.len() == binding.keys.len() && !sequence.is_empty() {
1136 self.chord_bindings
1137 .entry(context)
1138 .or_default()
1139 .insert(sequence, action);
1140 }
1141 } else if let Some(key_code) = Self::parse_key(&binding.key) {
1142 let modifiers = Self::parse_modifiers(&binding.modifiers);
1144 self.bindings
1145 .entry(context)
1146 .or_default()
1147 .insert((key_code, modifiers), action);
1148 }
1149 }
1150 }
1151 }
1152
1153 fn is_application_wide_action(action: &Action) -> bool {
1155 matches!(
1156 action,
1157 Action::Quit
1158 | Action::ForceQuit
1159 | Action::Save
1160 | Action::SaveAs
1161 | Action::ShowHelp
1162 | Action::ShowKeyboardShortcuts
1163 | Action::PromptCancel | Action::PopupCancel )
1166 }
1167
1168 pub fn is_terminal_ui_action(action: &Action) -> bool {
1172 matches!(
1173 action,
1174 Action::CommandPalette
1176 | Action::QuickOpen
1177 | Action::OpenSettings
1178 | Action::MenuActivate
1179 | Action::MenuOpen(_)
1180 | Action::ShowHelp
1181 | Action::ShowKeyboardShortcuts
1182 | Action::Quit
1183 | Action::ForceQuit
1184 | Action::NextSplit
1186 | Action::PrevSplit
1187 | Action::SplitHorizontal
1188 | Action::SplitVertical
1189 | Action::CloseSplit
1190 | Action::ToggleMaximizeSplit
1191 | Action::NextBuffer
1193 | Action::PrevBuffer
1194 | Action::Close
1195 | Action::ScrollTabsLeft
1196 | Action::ScrollTabsRight
1197 | Action::TerminalEscape
1199 | Action::ToggleKeyboardCapture
1200 | Action::OpenTerminal
1201 | Action::CloseTerminal
1202 | Action::TerminalPaste
1203 | Action::ToggleFileExplorer
1205 | Action::ToggleMenuBar
1207 )
1208 }
1209
1210 pub fn resolve_chord(
1216 &self,
1217 chord_state: &[(KeyCode, KeyModifiers)],
1218 event: &KeyEvent,
1219 context: KeyContext,
1220 ) -> ChordResolution {
1221 let mut full_sequence = chord_state.to_vec();
1223 full_sequence.push((event.code, event.modifiers));
1224
1225 tracing::trace!(
1226 "KeybindingResolver.resolve_chord: sequence={:?}, context={:?}",
1227 full_sequence,
1228 context
1229 );
1230
1231 let search_order = vec![
1233 (&self.chord_bindings, &KeyContext::Global, "custom global"),
1234 (
1235 &self.default_chord_bindings,
1236 &KeyContext::Global,
1237 "default global",
1238 ),
1239 (&self.chord_bindings, &context, "custom context"),
1240 (&self.default_chord_bindings, &context, "default context"),
1241 ];
1242
1243 let mut has_partial_match = false;
1244
1245 for (binding_map, bind_context, label) in search_order {
1246 if let Some(context_chords) = binding_map.get(bind_context) {
1247 if let Some(action) = context_chords.get(&full_sequence) {
1249 tracing::trace!(" -> Complete chord match in {}: {:?}", label, action);
1250 return ChordResolution::Complete(action.clone());
1251 }
1252
1253 for (chord_seq, _) in context_chords.iter() {
1255 if chord_seq.len() > full_sequence.len()
1256 && chord_seq[..full_sequence.len()] == full_sequence[..]
1257 {
1258 tracing::trace!(" -> Partial chord match in {}", label);
1259 has_partial_match = true;
1260 break;
1261 }
1262 }
1263 }
1264 }
1265
1266 if has_partial_match {
1267 ChordResolution::Partial
1268 } else {
1269 tracing::trace!(" -> No chord match");
1270 ChordResolution::NoMatch
1271 }
1272 }
1273
1274 pub fn resolve(&self, event: &KeyEvent, context: KeyContext) -> Action {
1276 tracing::trace!(
1277 "KeybindingResolver.resolve: code={:?}, modifiers={:?}, context={:?}",
1278 event.code,
1279 event.modifiers,
1280 context
1281 );
1282
1283 if let Some(global_bindings) = self.bindings.get(&KeyContext::Global) {
1285 if let Some(action) = global_bindings.get(&(event.code, event.modifiers)) {
1286 tracing::trace!(" -> Found in custom global bindings: {:?}", action);
1287 return action.clone();
1288 }
1289 }
1290
1291 if let Some(global_bindings) = self.default_bindings.get(&KeyContext::Global) {
1292 if let Some(action) = global_bindings.get(&(event.code, event.modifiers)) {
1293 tracing::trace!(" -> Found in default global bindings: {:?}", action);
1294 return action.clone();
1295 }
1296 }
1297
1298 if let Some(context_bindings) = self.bindings.get(&context) {
1300 if let Some(action) = context_bindings.get(&(event.code, event.modifiers)) {
1301 tracing::trace!(
1302 " -> Found in custom {} bindings: {:?}",
1303 context.to_when_clause(),
1304 action
1305 );
1306 return action.clone();
1307 }
1308 }
1309
1310 if let Some(context_bindings) = self.default_bindings.get(&context) {
1312 if let Some(action) = context_bindings.get(&(event.code, event.modifiers)) {
1313 tracing::trace!(
1314 " -> Found in default {} bindings: {:?}",
1315 context.to_when_clause(),
1316 action
1317 );
1318 return action.clone();
1319 }
1320 }
1321
1322 if context != KeyContext::Normal {
1325 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
1326 if let Some(action) = normal_bindings.get(&(event.code, event.modifiers)) {
1327 if Self::is_application_wide_action(action) {
1328 tracing::trace!(
1329 " -> Found application-wide action in custom normal bindings: {:?}",
1330 action
1331 );
1332 return action.clone();
1333 }
1334 }
1335 }
1336
1337 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
1338 if let Some(action) = normal_bindings.get(&(event.code, event.modifiers)) {
1339 if Self::is_application_wide_action(action) {
1340 tracing::trace!(
1341 " -> Found application-wide action in default normal bindings: {:?}",
1342 action
1343 );
1344 return action.clone();
1345 }
1346 }
1347 }
1348 }
1349
1350 if context.allows_text_input()
1352 && (event.modifiers.is_empty() || event.modifiers == KeyModifiers::SHIFT)
1353 {
1354 if let KeyCode::Char(c) = event.code {
1355 tracing::trace!(" -> Character input: '{}'", c);
1356 return Action::InsertChar(c);
1357 }
1358 }
1359
1360 tracing::trace!(" -> No binding found, returning Action::None");
1361 Action::None
1362 }
1363
1364 pub fn resolve_terminal_ui_action(&self, event: &KeyEvent) -> Action {
1368 tracing::trace!(
1369 "KeybindingResolver.resolve_terminal_ui_action: code={:?}, modifiers={:?}",
1370 event.code,
1371 event.modifiers
1372 );
1373
1374 for bindings in [&self.bindings, &self.default_bindings] {
1376 if let Some(terminal_bindings) = bindings.get(&KeyContext::Terminal) {
1377 if let Some(action) = terminal_bindings.get(&(event.code, event.modifiers)) {
1378 if Self::is_terminal_ui_action(action) {
1379 tracing::trace!(" -> Found UI action in terminal bindings: {:?}", action);
1380 return action.clone();
1381 }
1382 }
1383 }
1384 }
1385
1386 for bindings in [&self.bindings, &self.default_bindings] {
1388 if let Some(global_bindings) = bindings.get(&KeyContext::Global) {
1389 if let Some(action) = global_bindings.get(&(event.code, event.modifiers)) {
1390 if Self::is_terminal_ui_action(action) {
1391 tracing::trace!(" -> Found UI action in global bindings: {:?}", action);
1392 return action.clone();
1393 }
1394 }
1395 }
1396 }
1397
1398 for bindings in [&self.bindings, &self.default_bindings] {
1400 if let Some(normal_bindings) = bindings.get(&KeyContext::Normal) {
1401 if let Some(action) = normal_bindings.get(&(event.code, event.modifiers)) {
1402 if Self::is_terminal_ui_action(action) {
1403 tracing::trace!(" -> Found UI action in normal bindings: {:?}", action);
1404 return action.clone();
1405 }
1406 }
1407 }
1408 }
1409
1410 tracing::trace!(" -> No UI action found");
1411 Action::None
1412 }
1413
1414 pub fn find_keybinding_for_action(
1417 &self,
1418 action_name: &str,
1419 context: KeyContext,
1420 ) -> Option<String> {
1421 let target_action = Action::from_str(action_name, &HashMap::new())?;
1423
1424 let search_maps = vec![
1426 self.bindings.get(&context),
1427 self.bindings.get(&KeyContext::Global),
1428 self.default_bindings.get(&context),
1429 self.default_bindings.get(&KeyContext::Global),
1430 ];
1431
1432 for map in search_maps.into_iter().flatten() {
1433 let mut matches: Vec<(KeyCode, KeyModifiers)> = map
1435 .iter()
1436 .filter(|(_, action)| {
1437 std::mem::discriminant(*action) == std::mem::discriminant(&target_action)
1438 })
1439 .map(|((key_code, modifiers), _)| (*key_code, *modifiers))
1440 .collect();
1441
1442 if !matches.is_empty() {
1443 matches.sort_by(|(key_a, mod_a), (key_b, mod_b)| {
1445 let mod_count_a = mod_a.bits().count_ones();
1447 let mod_count_b = mod_b.bits().count_ones();
1448 match mod_count_a.cmp(&mod_count_b) {
1449 std::cmp::Ordering::Equal => {
1450 match mod_a.bits().cmp(&mod_b.bits()) {
1452 std::cmp::Ordering::Equal => {
1453 Self::key_code_sort_key(key_a)
1455 .cmp(&Self::key_code_sort_key(key_b))
1456 }
1457 other => other,
1458 }
1459 }
1460 other => other,
1461 }
1462 });
1463
1464 let (key_code, modifiers) = matches[0];
1465 return Some(format_keybinding(&key_code, &modifiers));
1466 }
1467 }
1468
1469 None
1470 }
1471
1472 fn key_code_sort_key(key_code: &KeyCode) -> (u8, u32) {
1474 match key_code {
1475 KeyCode::Char(c) => (0, *c as u32),
1476 KeyCode::F(n) => (1, *n as u32),
1477 KeyCode::Enter => (2, 0),
1478 KeyCode::Tab => (2, 1),
1479 KeyCode::Backspace => (2, 2),
1480 KeyCode::Delete => (2, 3),
1481 KeyCode::Esc => (2, 4),
1482 KeyCode::Left => (3, 0),
1483 KeyCode::Right => (3, 1),
1484 KeyCode::Up => (3, 2),
1485 KeyCode::Down => (3, 3),
1486 KeyCode::Home => (3, 4),
1487 KeyCode::End => (3, 5),
1488 KeyCode::PageUp => (3, 6),
1489 KeyCode::PageDown => (3, 7),
1490 _ => (255, 0),
1491 }
1492 }
1493
1494 pub fn find_menu_mnemonic(&self, menu_name: &str) -> Option<char> {
1497 let search_maps = vec![
1499 self.bindings.get(&KeyContext::Normal),
1500 self.bindings.get(&KeyContext::Global),
1501 self.default_bindings.get(&KeyContext::Normal),
1502 self.default_bindings.get(&KeyContext::Global),
1503 ];
1504
1505 for map in search_maps.into_iter().flatten() {
1506 for ((key_code, modifiers), action) in map {
1507 if let Action::MenuOpen(name) = action {
1509 if name.eq_ignore_ascii_case(menu_name) && *modifiers == KeyModifiers::ALT {
1510 if let KeyCode::Char(c) = key_code {
1512 return Some(c.to_ascii_lowercase());
1513 }
1514 }
1515 }
1516 }
1517 }
1518
1519 None
1520 }
1521
1522 fn parse_key(key: &str) -> Option<KeyCode> {
1524 let lower = key.to_lowercase();
1525 match lower.as_str() {
1526 "enter" => Some(KeyCode::Enter),
1527 "backspace" => Some(KeyCode::Backspace),
1528 "delete" | "del" => Some(KeyCode::Delete),
1529 "tab" => Some(KeyCode::Tab),
1530 "backtab" => Some(KeyCode::BackTab),
1531 "esc" | "escape" => Some(KeyCode::Esc),
1532 "space" => Some(KeyCode::Char(' ')),
1533
1534 "left" => Some(KeyCode::Left),
1535 "right" => Some(KeyCode::Right),
1536 "up" => Some(KeyCode::Up),
1537 "down" => Some(KeyCode::Down),
1538 "home" => Some(KeyCode::Home),
1539 "end" => Some(KeyCode::End),
1540 "pageup" => Some(KeyCode::PageUp),
1541 "pagedown" => Some(KeyCode::PageDown),
1542
1543 s if s.len() == 1 => s.chars().next().map(KeyCode::Char),
1544 s if s.starts_with('f') && s.len() >= 2 => s[1..].parse::<u8>().ok().map(KeyCode::F),
1546 _ => None,
1547 }
1548 }
1549
1550 fn parse_modifiers(modifiers: &[String]) -> KeyModifiers {
1552 let mut result = KeyModifiers::empty();
1553 for m in modifiers {
1554 match m.to_lowercase().as_str() {
1555 "ctrl" | "control" => result |= KeyModifiers::CONTROL,
1556 "shift" => result |= KeyModifiers::SHIFT,
1557 "alt" => result |= KeyModifiers::ALT,
1558 _ => {}
1559 }
1560 }
1561 result
1562 }
1563
1564 pub fn get_all_bindings(&self) -> Vec<(String, String)> {
1568 let mut bindings = Vec::new();
1569
1570 for context in &[
1572 KeyContext::Normal,
1573 KeyContext::Prompt,
1574 KeyContext::Popup,
1575 KeyContext::FileExplorer,
1576 KeyContext::Menu,
1577 ] {
1578 let mut all_keys: HashMap<(KeyCode, KeyModifiers), Action> = HashMap::new();
1579
1580 if let Some(context_defaults) = self.default_bindings.get(context) {
1582 for (key, action) in context_defaults {
1583 all_keys.insert(*key, action.clone());
1584 }
1585 }
1586
1587 if let Some(context_bindings) = self.bindings.get(context) {
1589 for (key, action) in context_bindings {
1590 all_keys.insert(*key, action.clone());
1591 }
1592 }
1593
1594 let context_str = if *context != KeyContext::Normal {
1596 format!("[{}] ", context.to_when_clause())
1597 } else {
1598 String::new()
1599 };
1600
1601 for ((key_code, modifiers), action) in all_keys {
1602 let key_str = Self::format_key(key_code, modifiers);
1603 let action_str = format!("{}{}", context_str, Self::format_action(&action));
1604 bindings.push((key_str, action_str));
1605 }
1606 }
1607
1608 bindings.sort_by(|a, b| a.1.cmp(&b.1));
1610
1611 bindings
1612 }
1613
1614 fn format_key(key_code: KeyCode, modifiers: KeyModifiers) -> String {
1616 format_keybinding(&key_code, &modifiers)
1617 }
1618
1619 fn format_action(action: &Action) -> String {
1621 match action {
1622 Action::InsertChar(c) => t!("action.insert_char", char = c),
1623 Action::InsertNewline => t!("action.insert_newline"),
1624 Action::InsertTab => t!("action.insert_tab"),
1625 Action::MoveLeft => t!("action.move_left"),
1626 Action::MoveRight => t!("action.move_right"),
1627 Action::MoveUp => t!("action.move_up"),
1628 Action::MoveDown => t!("action.move_down"),
1629 Action::MoveWordLeft => t!("action.move_word_left"),
1630 Action::MoveWordRight => t!("action.move_word_right"),
1631 Action::MoveWordEnd => t!("action.move_word_end"),
1632 Action::MoveLineStart => t!("action.move_line_start"),
1633 Action::MoveLineEnd => t!("action.move_line_end"),
1634 Action::MovePageUp => t!("action.move_page_up"),
1635 Action::MovePageDown => t!("action.move_page_down"),
1636 Action::MoveDocumentStart => t!("action.move_document_start"),
1637 Action::MoveDocumentEnd => t!("action.move_document_end"),
1638 Action::SelectLeft => t!("action.select_left"),
1639 Action::SelectRight => t!("action.select_right"),
1640 Action::SelectUp => t!("action.select_up"),
1641 Action::SelectDown => t!("action.select_down"),
1642 Action::SelectWordLeft => t!("action.select_word_left"),
1643 Action::SelectWordRight => t!("action.select_word_right"),
1644 Action::SelectWordEnd => t!("action.select_word_end"),
1645 Action::SelectLineStart => t!("action.select_line_start"),
1646 Action::SelectLineEnd => t!("action.select_line_end"),
1647 Action::SelectDocumentStart => t!("action.select_document_start"),
1648 Action::SelectDocumentEnd => t!("action.select_document_end"),
1649 Action::SelectPageUp => t!("action.select_page_up"),
1650 Action::SelectPageDown => t!("action.select_page_down"),
1651 Action::SelectAll => t!("action.select_all"),
1652 Action::SelectWord => t!("action.select_word"),
1653 Action::SelectLine => t!("action.select_line"),
1654 Action::ExpandSelection => t!("action.expand_selection"),
1655 Action::BlockSelectLeft => t!("action.block_select_left"),
1656 Action::BlockSelectRight => t!("action.block_select_right"),
1657 Action::BlockSelectUp => t!("action.block_select_up"),
1658 Action::BlockSelectDown => t!("action.block_select_down"),
1659 Action::DeleteBackward => t!("action.delete_backward"),
1660 Action::DeleteForward => t!("action.delete_forward"),
1661 Action::DeleteWordBackward => t!("action.delete_word_backward"),
1662 Action::DeleteWordForward => t!("action.delete_word_forward"),
1663 Action::DeleteLine => t!("action.delete_line"),
1664 Action::DeleteToLineEnd => t!("action.delete_to_line_end"),
1665 Action::DeleteToLineStart => t!("action.delete_to_line_start"),
1666 Action::TransposeChars => t!("action.transpose_chars"),
1667 Action::OpenLine => t!("action.open_line"),
1668 Action::Recenter => t!("action.recenter"),
1669 Action::SetMark => t!("action.set_mark"),
1670 Action::Copy => t!("action.copy"),
1671 Action::CopyWithTheme(theme) if theme.is_empty() => t!("action.copy_with_formatting"),
1672 Action::CopyWithTheme(theme) => t!("action.copy_with_theme", theme = theme),
1673 Action::Cut => t!("action.cut"),
1674 Action::Paste => t!("action.paste"),
1675 Action::YankWordForward => t!("action.yank_word_forward"),
1676 Action::YankWordBackward => t!("action.yank_word_backward"),
1677 Action::YankToLineEnd => t!("action.yank_to_line_end"),
1678 Action::YankToLineStart => t!("action.yank_to_line_start"),
1679 Action::AddCursorAbove => t!("action.add_cursor_above"),
1680 Action::AddCursorBelow => t!("action.add_cursor_below"),
1681 Action::AddCursorNextMatch => t!("action.add_cursor_next_match"),
1682 Action::RemoveSecondaryCursors => t!("action.remove_secondary_cursors"),
1683 Action::Save => t!("action.save"),
1684 Action::SaveAs => t!("action.save_as"),
1685 Action::Open => t!("action.open"),
1686 Action::SwitchProject => t!("action.switch_project"),
1687 Action::New => t!("action.new"),
1688 Action::Close => t!("action.close"),
1689 Action::CloseTab => t!("action.close_tab"),
1690 Action::Quit => t!("action.quit"),
1691 Action::ForceQuit => t!("action.force_quit"),
1692 Action::Revert => t!("action.revert"),
1693 Action::ToggleAutoRevert => t!("action.toggle_auto_revert"),
1694 Action::FormatBuffer => t!("action.format_buffer"),
1695 Action::TrimTrailingWhitespace => t!("action.trim_trailing_whitespace"),
1696 Action::EnsureFinalNewline => t!("action.ensure_final_newline"),
1697 Action::GotoLine => t!("action.goto_line"),
1698 Action::GoToMatchingBracket => t!("action.goto_matching_bracket"),
1699 Action::JumpToNextError => t!("action.jump_to_next_error"),
1700 Action::JumpToPreviousError => t!("action.jump_to_previous_error"),
1701 Action::SmartHome => t!("action.smart_home"),
1702 Action::DedentSelection => t!("action.dedent_selection"),
1703 Action::ToggleComment => t!("action.toggle_comment"),
1704 Action::SetBookmark(c) => t!("action.set_bookmark", key = c),
1705 Action::JumpToBookmark(c) => t!("action.jump_to_bookmark", key = c),
1706 Action::ClearBookmark(c) => t!("action.clear_bookmark", key = c),
1707 Action::ListBookmarks => t!("action.list_bookmarks"),
1708 Action::ToggleSearchCaseSensitive => t!("action.toggle_search_case_sensitive"),
1709 Action::ToggleSearchWholeWord => t!("action.toggle_search_whole_word"),
1710 Action::ToggleSearchRegex => t!("action.toggle_search_regex"),
1711 Action::ToggleSearchConfirmEach => t!("action.toggle_search_confirm_each"),
1712 Action::StartMacroRecording => t!("action.start_macro_recording"),
1713 Action::StopMacroRecording => t!("action.stop_macro_recording"),
1714 Action::PlayMacro(c) => t!("action.play_macro", key = c),
1715 Action::ToggleMacroRecording(c) => t!("action.toggle_macro_recording", key = c),
1716 Action::ShowMacro(c) => t!("action.show_macro", key = c),
1717 Action::ListMacros => t!("action.list_macros"),
1718 Action::PromptRecordMacro => t!("action.prompt_record_macro"),
1719 Action::PromptPlayMacro => t!("action.prompt_play_macro"),
1720 Action::PlayLastMacro => t!("action.play_last_macro"),
1721 Action::PromptSetBookmark => t!("action.prompt_set_bookmark"),
1722 Action::PromptJumpToBookmark => t!("action.prompt_jump_to_bookmark"),
1723 Action::Undo => t!("action.undo"),
1724 Action::Redo => t!("action.redo"),
1725 Action::ScrollUp => t!("action.scroll_up"),
1726 Action::ScrollDown => t!("action.scroll_down"),
1727 Action::ShowHelp => t!("action.show_help"),
1728 Action::ShowKeyboardShortcuts => t!("action.show_keyboard_shortcuts"),
1729 Action::ShowWarnings => t!("action.show_warnings"),
1730 Action::ShowStatusLog => t!("action.show_status_log"),
1731 Action::ShowLspStatus => t!("action.show_lsp_status"),
1732 Action::ClearWarnings => t!("action.clear_warnings"),
1733 Action::CommandPalette => t!("action.command_palette"),
1734 Action::QuickOpen => t!("action.quick_open"),
1735 Action::ToggleLineWrap => t!("action.toggle_line_wrap"),
1736 Action::ToggleComposeMode => t!("action.toggle_compose_mode"),
1737 Action::SetComposeWidth => t!("action.set_compose_width"),
1738 Action::NextBuffer => t!("action.next_buffer"),
1739 Action::PrevBuffer => t!("action.prev_buffer"),
1740 Action::NavigateBack => t!("action.navigate_back"),
1741 Action::NavigateForward => t!("action.navigate_forward"),
1742 Action::SplitHorizontal => t!("action.split_horizontal"),
1743 Action::SplitVertical => t!("action.split_vertical"),
1744 Action::CloseSplit => t!("action.close_split"),
1745 Action::NextSplit => t!("action.next_split"),
1746 Action::PrevSplit => t!("action.prev_split"),
1747 Action::IncreaseSplitSize => t!("action.increase_split_size"),
1748 Action::DecreaseSplitSize => t!("action.decrease_split_size"),
1749 Action::ToggleMaximizeSplit => t!("action.toggle_maximize_split"),
1750 Action::PromptConfirm => t!("action.prompt_confirm"),
1751 Action::PromptConfirmWithText(ref text) => {
1752 format!("{} ({})", t!("action.prompt_confirm"), text).into()
1753 }
1754 Action::PromptCancel => t!("action.prompt_cancel"),
1755 Action::PromptBackspace => t!("action.prompt_backspace"),
1756 Action::PromptDelete => t!("action.prompt_delete"),
1757 Action::PromptMoveLeft => t!("action.prompt_move_left"),
1758 Action::PromptMoveRight => t!("action.prompt_move_right"),
1759 Action::PromptMoveStart => t!("action.prompt_move_start"),
1760 Action::PromptMoveEnd => t!("action.prompt_move_end"),
1761 Action::PromptSelectPrev => t!("action.prompt_select_prev"),
1762 Action::PromptSelectNext => t!("action.prompt_select_next"),
1763 Action::PromptPageUp => t!("action.prompt_page_up"),
1764 Action::PromptPageDown => t!("action.prompt_page_down"),
1765 Action::PromptAcceptSuggestion => t!("action.prompt_accept_suggestion"),
1766 Action::PromptMoveWordLeft => t!("action.prompt_move_word_left"),
1767 Action::PromptMoveWordRight => t!("action.prompt_move_word_right"),
1768 Action::PromptDeleteWordForward => t!("action.prompt_delete_word_forward"),
1769 Action::PromptDeleteWordBackward => t!("action.prompt_delete_word_backward"),
1770 Action::PromptDeleteToLineEnd => t!("action.prompt_delete_to_line_end"),
1771 Action::PromptCopy => t!("action.prompt_copy"),
1772 Action::PromptCut => t!("action.prompt_cut"),
1773 Action::PromptPaste => t!("action.prompt_paste"),
1774 Action::PromptMoveLeftSelecting => t!("action.prompt_move_left_selecting"),
1775 Action::PromptMoveRightSelecting => t!("action.prompt_move_right_selecting"),
1776 Action::PromptMoveHomeSelecting => t!("action.prompt_move_home_selecting"),
1777 Action::PromptMoveEndSelecting => t!("action.prompt_move_end_selecting"),
1778 Action::PromptSelectWordLeft => t!("action.prompt_select_word_left"),
1779 Action::PromptSelectWordRight => t!("action.prompt_select_word_right"),
1780 Action::PromptSelectAll => t!("action.prompt_select_all"),
1781 Action::FileBrowserToggleHidden => t!("action.file_browser_toggle_hidden"),
1782 Action::PopupSelectNext => t!("action.popup_select_next"),
1783 Action::PopupSelectPrev => t!("action.popup_select_prev"),
1784 Action::PopupPageUp => t!("action.popup_page_up"),
1785 Action::PopupPageDown => t!("action.popup_page_down"),
1786 Action::PopupConfirm => t!("action.popup_confirm"),
1787 Action::PopupCancel => t!("action.popup_cancel"),
1788 Action::ToggleFileExplorer => t!("action.toggle_file_explorer"),
1789 Action::ToggleMenuBar => t!("action.toggle_menu_bar"),
1790 Action::ToggleTabBar => t!("action.toggle_tab_bar"),
1791 Action::FocusFileExplorer => t!("action.focus_file_explorer"),
1792 Action::FocusEditor => t!("action.focus_editor"),
1793 Action::FileExplorerUp => t!("action.file_explorer_up"),
1794 Action::FileExplorerDown => t!("action.file_explorer_down"),
1795 Action::FileExplorerPageUp => t!("action.file_explorer_page_up"),
1796 Action::FileExplorerPageDown => t!("action.file_explorer_page_down"),
1797 Action::FileExplorerExpand => t!("action.file_explorer_expand"),
1798 Action::FileExplorerCollapse => t!("action.file_explorer_collapse"),
1799 Action::FileExplorerOpen => t!("action.file_explorer_open"),
1800 Action::FileExplorerRefresh => t!("action.file_explorer_refresh"),
1801 Action::FileExplorerNewFile => t!("action.file_explorer_new_file"),
1802 Action::FileExplorerNewDirectory => t!("action.file_explorer_new_directory"),
1803 Action::FileExplorerDelete => t!("action.file_explorer_delete"),
1804 Action::FileExplorerRename => t!("action.file_explorer_rename"),
1805 Action::FileExplorerToggleHidden => t!("action.file_explorer_toggle_hidden"),
1806 Action::FileExplorerToggleGitignored => t!("action.file_explorer_toggle_gitignored"),
1807 Action::LspCompletion => t!("action.lsp_completion"),
1808 Action::LspGotoDefinition => t!("action.lsp_goto_definition"),
1809 Action::LspReferences => t!("action.lsp_references"),
1810 Action::LspRename => t!("action.lsp_rename"),
1811 Action::LspHover => t!("action.lsp_hover"),
1812 Action::LspSignatureHelp => t!("action.lsp_signature_help"),
1813 Action::LspCodeActions => t!("action.lsp_code_actions"),
1814 Action::LspRestart => t!("action.lsp_restart"),
1815 Action::LspStop => t!("action.lsp_stop"),
1816 Action::ToggleInlayHints => t!("action.toggle_inlay_hints"),
1817 Action::ToggleMouseHover => t!("action.toggle_mouse_hover"),
1818 Action::ToggleLineNumbers => t!("action.toggle_line_numbers"),
1819 Action::ToggleMouseCapture => t!("action.toggle_mouse_capture"),
1820 Action::ToggleDebugHighlights => t!("action.toggle_debug_highlights"),
1821 Action::SetBackground => t!("action.set_background"),
1822 Action::SetBackgroundBlend => t!("action.set_background_blend"),
1823 Action::SetTabSize => t!("action.set_tab_size"),
1824 Action::SetLineEnding => t!("action.set_line_ending"),
1825 Action::SetLanguage => t!("action.set_language"),
1826 Action::ToggleIndentationStyle => t!("action.toggle_indentation_style"),
1827 Action::ToggleTabIndicators => t!("action.toggle_tab_indicators"),
1828 Action::ResetBufferSettings => t!("action.reset_buffer_settings"),
1829 Action::DumpConfig => t!("action.dump_config"),
1830 Action::Search => t!("action.search"),
1831 Action::FindInSelection => t!("action.find_in_selection"),
1832 Action::FindNext => t!("action.find_next"),
1833 Action::FindPrevious => t!("action.find_previous"),
1834 Action::FindSelectionNext => t!("action.find_selection_next"),
1835 Action::FindSelectionPrevious => t!("action.find_selection_previous"),
1836 Action::Replace => t!("action.replace"),
1837 Action::QueryReplace => t!("action.query_replace"),
1838 Action::MenuActivate => t!("action.menu_activate"),
1839 Action::MenuClose => t!("action.menu_close"),
1840 Action::MenuLeft => t!("action.menu_left"),
1841 Action::MenuRight => t!("action.menu_right"),
1842 Action::MenuUp => t!("action.menu_up"),
1843 Action::MenuDown => t!("action.menu_down"),
1844 Action::MenuExecute => t!("action.menu_execute"),
1845 Action::MenuOpen(name) => t!("action.menu_open", name = name),
1846 Action::SwitchKeybindingMap(map) => t!("action.switch_keybinding_map", map = map),
1847 Action::PluginAction(name) => t!("action.plugin_action", name = name),
1848 Action::ScrollTabsLeft => t!("action.scroll_tabs_left"),
1849 Action::ScrollTabsRight => t!("action.scroll_tabs_right"),
1850 Action::SelectTheme => t!("action.select_theme"),
1851 Action::SelectKeybindingMap => t!("action.select_keybinding_map"),
1852 Action::SelectCursorStyle => t!("action.select_cursor_style"),
1853 Action::SelectLocale => t!("action.select_locale"),
1854 Action::SwitchToPreviousTab => t!("action.switch_to_previous_tab"),
1855 Action::SwitchToTabByName => t!("action.switch_to_tab_by_name"),
1856 Action::OpenTerminal => t!("action.open_terminal"),
1857 Action::CloseTerminal => t!("action.close_terminal"),
1858 Action::FocusTerminal => t!("action.focus_terminal"),
1859 Action::TerminalEscape => t!("action.terminal_escape"),
1860 Action::ToggleKeyboardCapture => t!("action.toggle_keyboard_capture"),
1861 Action::TerminalPaste => t!("action.terminal_paste"),
1862 Action::OpenSettings => t!("action.open_settings"),
1863 Action::CloseSettings => t!("action.close_settings"),
1864 Action::SettingsSave => t!("action.settings_save"),
1865 Action::SettingsReset => t!("action.settings_reset"),
1866 Action::SettingsToggleFocus => t!("action.settings_toggle_focus"),
1867 Action::SettingsActivate => t!("action.settings_activate"),
1868 Action::SettingsSearch => t!("action.settings_search"),
1869 Action::SettingsHelp => t!("action.settings_help"),
1870 Action::SettingsIncrement => t!("action.settings_increment"),
1871 Action::SettingsDecrement => t!("action.settings_decrement"),
1872 Action::ShellCommand => t!("action.shell_command"),
1873 Action::ShellCommandReplace => t!("action.shell_command_replace"),
1874 Action::ToUpperCase => t!("action.to_uppercase"),
1875 Action::ToLowerCase => t!("action.to_lowercase"),
1876 Action::CalibrateInput => t!("action.calibrate_input"),
1877 Action::EventDebug => t!("action.event_debug"),
1878 Action::None => t!("action.none"),
1879 }
1880 .to_string()
1881 }
1882
1883 pub fn get_keybinding_for_action(
1889 &self,
1890 action: &Action,
1891 context: KeyContext,
1892 ) -> Option<String> {
1893 fn find_best_keybinding(
1895 bindings: &HashMap<(KeyCode, KeyModifiers), Action>,
1896 action: &Action,
1897 ) -> Option<(KeyCode, KeyModifiers)> {
1898 let matches: Vec<_> = bindings
1899 .iter()
1900 .filter(|(_, a)| *a == action)
1901 .map(|((k, m), _)| (*k, *m))
1902 .collect();
1903
1904 if matches.is_empty() {
1905 return None;
1906 }
1907
1908 let mut sorted = matches;
1911 sorted.sort_by(|(k1, m1), (k2, m2)| {
1912 let score1 = keybinding_priority_score(k1);
1913 let score2 = keybinding_priority_score(k2);
1914 match score1.cmp(&score2) {
1916 std::cmp::Ordering::Equal => {
1917 let s1 = format_keybinding(k1, m1);
1919 let s2 = format_keybinding(k2, m2);
1920 s1.cmp(&s2)
1921 }
1922 other => other,
1923 }
1924 });
1925
1926 sorted.into_iter().next()
1927 }
1928
1929 if let Some(context_bindings) = self.bindings.get(&context) {
1931 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
1932 return Some(format_keybinding(&keycode, &modifiers));
1933 }
1934 }
1935
1936 if let Some(context_bindings) = self.default_bindings.get(&context) {
1938 if let Some((keycode, modifiers)) = find_best_keybinding(context_bindings, action) {
1939 return Some(format_keybinding(&keycode, &modifiers));
1940 }
1941 }
1942
1943 if context != KeyContext::Normal && Self::is_application_wide_action(action) {
1945 if let Some(normal_bindings) = self.bindings.get(&KeyContext::Normal) {
1947 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
1948 return Some(format_keybinding(&keycode, &modifiers));
1949 }
1950 }
1951
1952 if let Some(normal_bindings) = self.default_bindings.get(&KeyContext::Normal) {
1954 if let Some((keycode, modifiers)) = find_best_keybinding(normal_bindings, action) {
1955 return Some(format_keybinding(&keycode, &modifiers));
1956 }
1957 }
1958 }
1959
1960 None
1961 }
1962
1963 pub fn reload(&mut self, config: &Config) {
1965 self.bindings.clear();
1966 for binding in &config.keybindings {
1967 if let Some(key_code) = Self::parse_key(&binding.key) {
1968 let modifiers = Self::parse_modifiers(&binding.modifiers);
1969 if let Some(action) = Action::from_str(&binding.action, &binding.args) {
1970 let context = if let Some(ref when) = binding.when {
1972 KeyContext::from_when_clause(when).unwrap_or(KeyContext::Normal)
1973 } else {
1974 KeyContext::Normal
1975 };
1976
1977 self.bindings
1978 .entry(context)
1979 .or_default()
1980 .insert((key_code, modifiers), action);
1981 }
1982 }
1983 }
1984 }
1985}
1986
1987#[cfg(test)]
1988mod tests {
1989 use super::*;
1990
1991 #[test]
1992 fn test_parse_key() {
1993 assert_eq!(KeybindingResolver::parse_key("enter"), Some(KeyCode::Enter));
1994 assert_eq!(
1995 KeybindingResolver::parse_key("backspace"),
1996 Some(KeyCode::Backspace)
1997 );
1998 assert_eq!(KeybindingResolver::parse_key("tab"), Some(KeyCode::Tab));
1999 assert_eq!(
2000 KeybindingResolver::parse_key("backtab"),
2001 Some(KeyCode::BackTab)
2002 );
2003 assert_eq!(
2004 KeybindingResolver::parse_key("BackTab"),
2005 Some(KeyCode::BackTab)
2006 );
2007 assert_eq!(KeybindingResolver::parse_key("a"), Some(KeyCode::Char('a')));
2008 }
2009
2010 #[test]
2011 fn test_parse_modifiers() {
2012 let mods = vec!["ctrl".to_string()];
2013 assert_eq!(
2014 KeybindingResolver::parse_modifiers(&mods),
2015 KeyModifiers::CONTROL
2016 );
2017
2018 let mods = vec!["ctrl".to_string(), "shift".to_string()];
2019 assert_eq!(
2020 KeybindingResolver::parse_modifiers(&mods),
2021 KeyModifiers::CONTROL | KeyModifiers::SHIFT
2022 );
2023 }
2024
2025 #[test]
2026 fn test_resolve_basic() {
2027 let config = Config::default();
2028 let resolver = KeybindingResolver::new(&config);
2029
2030 let event = KeyEvent::new(KeyCode::Left, KeyModifiers::empty());
2031 assert_eq!(
2032 resolver.resolve(&event, KeyContext::Normal),
2033 Action::MoveLeft
2034 );
2035
2036 let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2037 assert_eq!(
2038 resolver.resolve(&event, KeyContext::Normal),
2039 Action::InsertChar('a')
2040 );
2041 }
2042
2043 #[test]
2044 fn test_action_from_str() {
2045 let args = HashMap::new();
2046 assert_eq!(Action::from_str("move_left", &args), Some(Action::MoveLeft));
2047 assert_eq!(Action::from_str("save", &args), Some(Action::Save));
2048 assert_eq!(Action::from_str("unknown", &args), None);
2049
2050 assert_eq!(
2052 Action::from_str("keyboard_shortcuts", &args),
2053 Some(Action::ShowKeyboardShortcuts)
2054 );
2055 assert_eq!(
2056 Action::from_str("prompt_confirm", &args),
2057 Some(Action::PromptConfirm)
2058 );
2059 assert_eq!(
2060 Action::from_str("popup_cancel", &args),
2061 Some(Action::PopupCancel)
2062 );
2063
2064 assert_eq!(
2066 Action::from_str("calibrate_input", &args),
2067 Some(Action::CalibrateInput)
2068 );
2069 }
2070
2071 #[test]
2072 fn test_key_context_from_when_clause() {
2073 assert_eq!(
2074 KeyContext::from_when_clause("normal"),
2075 Some(KeyContext::Normal)
2076 );
2077 assert_eq!(
2078 KeyContext::from_when_clause("prompt"),
2079 Some(KeyContext::Prompt)
2080 );
2081 assert_eq!(
2082 KeyContext::from_when_clause("popup"),
2083 Some(KeyContext::Popup)
2084 );
2085 assert_eq!(KeyContext::from_when_clause("help"), None);
2086 assert_eq!(KeyContext::from_when_clause(" help "), None); assert_eq!(KeyContext::from_when_clause("unknown"), None);
2088 assert_eq!(KeyContext::from_when_clause(""), None);
2089 }
2090
2091 #[test]
2092 fn test_key_context_to_when_clause() {
2093 assert_eq!(KeyContext::Normal.to_when_clause(), "normal");
2094 assert_eq!(KeyContext::Prompt.to_when_clause(), "prompt");
2095 assert_eq!(KeyContext::Popup.to_when_clause(), "popup");
2096 }
2097
2098 #[test]
2099 fn test_context_specific_bindings() {
2100 let config = Config::default();
2101 let resolver = KeybindingResolver::new(&config);
2102
2103 let enter_event = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
2105 assert_eq!(
2106 resolver.resolve(&enter_event, KeyContext::Prompt),
2107 Action::PromptConfirm
2108 );
2109 assert_eq!(
2110 resolver.resolve(&enter_event, KeyContext::Normal),
2111 Action::InsertNewline
2112 );
2113
2114 let up_event = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
2116 assert_eq!(
2117 resolver.resolve(&up_event, KeyContext::Popup),
2118 Action::PopupSelectPrev
2119 );
2120 assert_eq!(
2121 resolver.resolve(&up_event, KeyContext::Normal),
2122 Action::MoveUp
2123 );
2124 }
2125
2126 #[test]
2127 fn test_context_fallback_to_normal() {
2128 let config = Config::default();
2129 let resolver = KeybindingResolver::new(&config);
2130
2131 let save_event = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL);
2133 assert_eq!(
2134 resolver.resolve(&save_event, KeyContext::Normal),
2135 Action::Save
2136 );
2137 assert_eq!(
2138 resolver.resolve(&save_event, KeyContext::Popup),
2139 Action::Save
2140 );
2141 }
2143
2144 #[test]
2145 fn test_context_priority_resolution() {
2146 use crate::config::Keybinding;
2147
2148 let mut config = Config::default();
2150 config.keybindings.push(Keybinding {
2151 key: "esc".to_string(),
2152 modifiers: vec![],
2153 keys: vec![],
2154 action: "quit".to_string(), args: HashMap::new(),
2156 when: Some("popup".to_string()),
2157 });
2158
2159 let resolver = KeybindingResolver::new(&config);
2160 let esc_event = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
2161
2162 assert_eq!(
2164 resolver.resolve(&esc_event, KeyContext::Popup),
2165 Action::Quit
2166 );
2167
2168 assert_eq!(
2170 resolver.resolve(&esc_event, KeyContext::Normal),
2171 Action::RemoveSecondaryCursors
2172 );
2173 }
2174
2175 #[test]
2176 fn test_character_input_in_contexts() {
2177 let config = Config::default();
2178 let resolver = KeybindingResolver::new(&config);
2179
2180 let char_event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
2181
2182 assert_eq!(
2184 resolver.resolve(&char_event, KeyContext::Normal),
2185 Action::InsertChar('a')
2186 );
2187 assert_eq!(
2188 resolver.resolve(&char_event, KeyContext::Prompt),
2189 Action::InsertChar('a')
2190 );
2191
2192 assert_eq!(
2194 resolver.resolve(&char_event, KeyContext::Popup),
2195 Action::None
2196 );
2197 }
2198
2199 #[test]
2200 fn test_custom_keybinding_loading() {
2201 use crate::config::Keybinding;
2202
2203 let mut config = Config::default();
2204
2205 config.keybindings.push(Keybinding {
2207 key: "f".to_string(),
2208 modifiers: vec!["ctrl".to_string()],
2209 keys: vec![],
2210 action: "command_palette".to_string(),
2211 args: HashMap::new(),
2212 when: None, });
2214
2215 let resolver = KeybindingResolver::new(&config);
2216
2217 let ctrl_f = KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL);
2219 assert_eq!(
2220 resolver.resolve(&ctrl_f, KeyContext::Normal),
2221 Action::CommandPalette
2222 );
2223
2224 let ctrl_k = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
2226 assert_eq!(
2227 resolver.resolve(&ctrl_k, KeyContext::Prompt),
2228 Action::PromptDeleteToLineEnd
2229 );
2230 assert_eq!(
2231 resolver.resolve(&ctrl_k, KeyContext::Normal),
2232 Action::DeleteToLineEnd
2233 );
2234 }
2235
2236 #[test]
2237 fn test_all_context_default_bindings_exist() {
2238 let config = Config::default();
2239 let resolver = KeybindingResolver::new(&config);
2240
2241 assert!(resolver.default_bindings.contains_key(&KeyContext::Normal));
2243 assert!(resolver.default_bindings.contains_key(&KeyContext::Prompt));
2244 assert!(resolver.default_bindings.contains_key(&KeyContext::Popup));
2245 assert!(resolver
2246 .default_bindings
2247 .contains_key(&KeyContext::FileExplorer));
2248 assert!(resolver.default_bindings.contains_key(&KeyContext::Menu));
2249
2250 assert!(!resolver.default_bindings[&KeyContext::Normal].is_empty());
2252 assert!(!resolver.default_bindings[&KeyContext::Prompt].is_empty());
2253 assert!(!resolver.default_bindings[&KeyContext::Popup].is_empty());
2254 assert!(!resolver.default_bindings[&KeyContext::FileExplorer].is_empty());
2255 assert!(!resolver.default_bindings[&KeyContext::Menu].is_empty());
2256 }
2257
2258 #[test]
2259 fn test_resolve_determinism() {
2260 let config = Config::default();
2262 let resolver = KeybindingResolver::new(&config);
2263
2264 let test_cases = vec![
2265 (KeyCode::Left, KeyModifiers::empty(), KeyContext::Normal),
2266 (
2267 KeyCode::Esc,
2268 KeyModifiers::empty(),
2269 KeyContext::FileExplorer,
2270 ),
2271 (KeyCode::Enter, KeyModifiers::empty(), KeyContext::Prompt),
2272 (KeyCode::Down, KeyModifiers::empty(), KeyContext::Popup),
2273 ];
2274
2275 for (key_code, modifiers, context) in test_cases {
2276 let event = KeyEvent::new(key_code, modifiers);
2277 let action1 = resolver.resolve(&event, context);
2278 let action2 = resolver.resolve(&event, context);
2279 let action3 = resolver.resolve(&event, context);
2280
2281 assert_eq!(action1, action2, "Resolve should be deterministic");
2282 assert_eq!(action2, action3, "Resolve should be deterministic");
2283 }
2284 }
2285
2286 #[test]
2287 fn test_modifier_combinations() {
2288 let config = Config::default();
2289 let resolver = KeybindingResolver::new(&config);
2290
2291 let char_s = KeyCode::Char('s');
2293
2294 let no_mod = KeyEvent::new(char_s, KeyModifiers::empty());
2295 let ctrl = KeyEvent::new(char_s, KeyModifiers::CONTROL);
2296 let shift = KeyEvent::new(char_s, KeyModifiers::SHIFT);
2297 let ctrl_shift = KeyEvent::new(char_s, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
2298
2299 let action_no_mod = resolver.resolve(&no_mod, KeyContext::Normal);
2300 let action_ctrl = resolver.resolve(&ctrl, KeyContext::Normal);
2301 let action_shift = resolver.resolve(&shift, KeyContext::Normal);
2302 let action_ctrl_shift = resolver.resolve(&ctrl_shift, KeyContext::Normal);
2303
2304 assert_eq!(action_no_mod, Action::InsertChar('s'));
2306 assert_eq!(action_ctrl, Action::Save);
2307 assert_eq!(action_shift, Action::InsertChar('s')); assert_eq!(action_ctrl_shift, Action::None);
2310 }
2311
2312 #[test]
2313 fn test_scroll_keybindings() {
2314 let config = Config::default();
2315 let resolver = KeybindingResolver::new(&config);
2316
2317 let ctrl_up = KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL);
2319 assert_eq!(
2320 resolver.resolve(&ctrl_up, KeyContext::Normal),
2321 Action::ScrollUp,
2322 "Ctrl+Up should resolve to ScrollUp"
2323 );
2324
2325 let ctrl_down = KeyEvent::new(KeyCode::Down, KeyModifiers::CONTROL);
2327 assert_eq!(
2328 resolver.resolve(&ctrl_down, KeyContext::Normal),
2329 Action::ScrollDown,
2330 "Ctrl+Down should resolve to ScrollDown"
2331 );
2332 }
2333
2334 #[test]
2335 fn test_lsp_completion_keybinding() {
2336 let config = Config::default();
2337 let resolver = KeybindingResolver::new(&config);
2338
2339 let ctrl_space = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::CONTROL);
2341 assert_eq!(
2342 resolver.resolve(&ctrl_space, KeyContext::Normal),
2343 Action::LspCompletion,
2344 "Ctrl+Space should resolve to LspCompletion"
2345 );
2346 }
2347
2348 #[test]
2349 fn test_terminal_key_equivalents() {
2350 let ctrl = KeyModifiers::CONTROL;
2352
2353 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2355 assert_eq!(slash_equivs, vec![(KeyCode::Char('7'), ctrl)]);
2356
2357 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2358 assert_eq!(seven_equivs, vec![(KeyCode::Char('/'), ctrl)]);
2359
2360 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2362 assert_eq!(backspace_equivs, vec![(KeyCode::Char('h'), ctrl)]);
2363
2364 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2365 assert_eq!(h_equivs, vec![(KeyCode::Backspace, ctrl)]);
2366
2367 let a_equivs = terminal_key_equivalents(KeyCode::Char('a'), ctrl);
2369 assert!(a_equivs.is_empty());
2370
2371 let slash_no_ctrl = terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty());
2373 assert!(slash_no_ctrl.is_empty());
2374 }
2375
2376 #[test]
2377 fn test_terminal_key_equivalents_auto_binding() {
2378 let config = Config::default();
2379 let resolver = KeybindingResolver::new(&config);
2380
2381 let ctrl_slash = KeyEvent::new(KeyCode::Char('/'), KeyModifiers::CONTROL);
2383 let action_slash = resolver.resolve(&ctrl_slash, KeyContext::Normal);
2384 assert_eq!(
2385 action_slash,
2386 Action::ToggleComment,
2387 "Ctrl+/ should resolve to ToggleComment"
2388 );
2389
2390 let ctrl_7 = KeyEvent::new(KeyCode::Char('7'), KeyModifiers::CONTROL);
2392 let action_7 = resolver.resolve(&ctrl_7, KeyContext::Normal);
2393 assert_eq!(
2394 action_7,
2395 Action::ToggleComment,
2396 "Ctrl+7 should resolve to ToggleComment (terminal equivalent of Ctrl+/)"
2397 );
2398 }
2399
2400 #[test]
2401 fn test_terminal_key_equivalents_normalization() {
2402 let ctrl = KeyModifiers::CONTROL;
2407
2408 let slash_equivs = terminal_key_equivalents(KeyCode::Char('/'), ctrl);
2411 assert_eq!(
2412 slash_equivs,
2413 vec![(KeyCode::Char('7'), ctrl)],
2414 "Ctrl+/ should map to Ctrl+7"
2415 );
2416 let seven_equivs = terminal_key_equivalents(KeyCode::Char('7'), ctrl);
2417 assert_eq!(
2418 seven_equivs,
2419 vec![(KeyCode::Char('/'), ctrl)],
2420 "Ctrl+7 should map back to Ctrl+/"
2421 );
2422
2423 let backspace_equivs = terminal_key_equivalents(KeyCode::Backspace, ctrl);
2426 assert_eq!(
2427 backspace_equivs,
2428 vec![(KeyCode::Char('h'), ctrl)],
2429 "Ctrl+Backspace should map to Ctrl+H"
2430 );
2431 let h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl);
2432 assert_eq!(
2433 h_equivs,
2434 vec![(KeyCode::Backspace, ctrl)],
2435 "Ctrl+H should map back to Ctrl+Backspace"
2436 );
2437
2438 let space_equivs = terminal_key_equivalents(KeyCode::Char(' '), ctrl);
2441 assert_eq!(
2442 space_equivs,
2443 vec![(KeyCode::Char('@'), ctrl)],
2444 "Ctrl+Space should map to Ctrl+@"
2445 );
2446 let at_equivs = terminal_key_equivalents(KeyCode::Char('@'), ctrl);
2447 assert_eq!(
2448 at_equivs,
2449 vec![(KeyCode::Char(' '), ctrl)],
2450 "Ctrl+@ should map back to Ctrl+Space"
2451 );
2452
2453 let minus_equivs = terminal_key_equivalents(KeyCode::Char('-'), ctrl);
2456 assert_eq!(
2457 minus_equivs,
2458 vec![(KeyCode::Char('_'), ctrl)],
2459 "Ctrl+- should map to Ctrl+_"
2460 );
2461 let underscore_equivs = terminal_key_equivalents(KeyCode::Char('_'), ctrl);
2462 assert_eq!(
2463 underscore_equivs,
2464 vec![(KeyCode::Char('-'), ctrl)],
2465 "Ctrl+_ should map back to Ctrl+-"
2466 );
2467
2468 assert!(
2470 terminal_key_equivalents(KeyCode::Char('a'), ctrl).is_empty(),
2471 "Ctrl+A should have no terminal equivalents"
2472 );
2473 assert!(
2474 terminal_key_equivalents(KeyCode::Char('z'), ctrl).is_empty(),
2475 "Ctrl+Z should have no terminal equivalents"
2476 );
2477 assert!(
2478 terminal_key_equivalents(KeyCode::Enter, ctrl).is_empty(),
2479 "Ctrl+Enter should have no terminal equivalents"
2480 );
2481
2482 assert!(
2484 terminal_key_equivalents(KeyCode::Char('/'), KeyModifiers::empty()).is_empty(),
2485 "/ without Ctrl should have no equivalents"
2486 );
2487 assert!(
2488 terminal_key_equivalents(KeyCode::Char('7'), KeyModifiers::SHIFT).is_empty(),
2489 "Shift+7 should have no equivalents"
2490 );
2491 assert!(
2492 terminal_key_equivalents(KeyCode::Char('h'), KeyModifiers::ALT).is_empty(),
2493 "Alt+H should have no equivalents"
2494 );
2495
2496 let ctrl_shift = KeyModifiers::CONTROL | KeyModifiers::SHIFT;
2499 let ctrl_shift_h_equivs = terminal_key_equivalents(KeyCode::Char('h'), ctrl_shift);
2500 assert!(
2501 ctrl_shift_h_equivs.is_empty(),
2502 "Ctrl+Shift+H should NOT map to Ctrl+Shift+Backspace"
2503 );
2504 }
2505
2506 #[test]
2507 fn test_no_duplicate_keybindings_in_keymaps() {
2508 use std::collections::HashMap;
2511
2512 let keymaps: &[(&str, &str)] = &[
2513 ("default", include_str!("../../keymaps/default.json")),
2514 ("macos", include_str!("../../keymaps/macos.json")),
2515 ];
2516
2517 for (keymap_name, json_content) in keymaps {
2518 let keymap: crate::config::KeymapConfig = serde_json::from_str(json_content)
2519 .unwrap_or_else(|e| panic!("Failed to parse keymap '{}': {}", keymap_name, e));
2520
2521 let mut seen: HashMap<(String, Vec<String>, String), String> = HashMap::new();
2523 let mut duplicates: Vec<String> = Vec::new();
2524
2525 for binding in &keymap.bindings {
2526 let when = binding.when.clone().unwrap_or_default();
2527 let key_id = (binding.key.clone(), binding.modifiers.clone(), when.clone());
2528
2529 if let Some(existing_action) = seen.get(&key_id) {
2530 duplicates.push(format!(
2531 "Duplicate in '{}': key='{}', modifiers={:?}, when='{}' -> '{}' vs '{}'",
2532 keymap_name,
2533 binding.key,
2534 binding.modifiers,
2535 when,
2536 existing_action,
2537 binding.action
2538 ));
2539 } else {
2540 seen.insert(key_id, binding.action.clone());
2541 }
2542 }
2543
2544 assert!(
2545 duplicates.is_empty(),
2546 "Found duplicate keybindings:\n{}",
2547 duplicates.join("\n")
2548 );
2549 }
2550 }
2551}