diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index 12f082eb60799bdf9a0cdfaf7d546fa2bdf13e04..e26123339bd65fecd6ff9e5356098e29cee30890 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -168,9 +168,21 @@ impl Keymap { let mut bindings: SmallVec<[_; 1]> = SmallVec::new(); let mut first_binding_index = None; + for (_, ix, binding) in matched_bindings { if is_no_action(&*binding.action) { - break; + // Only break if this is a user-defined NoAction binding + // This allows user keymaps to override base keymap NoAction bindings + if let Some(meta) = binding.meta { + if meta.0 == 0 { + break; + } + } else { + // If no meta is set, assume it's a user binding for safety + break; + } + // For non-user NoAction bindings, continue searching for user overrides + continue; } bindings.push(binding.clone()); first_binding_index.get_or_insert(ix); @@ -617,6 +629,33 @@ mod tests { assert!(!matched.1); } + #[test] + fn test_context_precedence_with_same_source() { + // Test case: User has both Workspace and Editor bindings for the same key + // Editor binding should take precedence over Workspace binding + let bindings = [ + KeyBinding::new("cmd-r", ActionAlpha {}, Some("Workspace")), + KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")), + ]; + + let mut keymap = Keymap::default(); + keymap.add_bindings(bindings); + + // Test with context stack: [Workspace, Editor] (Editor is deeper) + let (result, _) = keymap.bindings_for_input( + &[Keystroke::parse("cmd-r").unwrap()], + &[ + KeyContext::parse("Workspace").unwrap(), + KeyContext::parse("Editor").unwrap(), + ], + ); + + // Both bindings should be returned, but Editor binding should be first (highest precedence) + assert_eq!(result.len(), 2); + assert!(result[0].action.partial_eq(&ActionBeta {})); // Editor binding first + assert!(result[1].action.partial_eq(&ActionAlpha {})); // Workspace binding second + } + #[test] fn test_bindings_for_action() { let bindings = [ @@ -643,4 +682,32 @@ mod tests { assert_eq!(actual, expected, "{:?}", action); } } + + #[test] + fn test_source_precedence_sorting() { + // KeybindSource precedence: User (0) > Vim (1) > Base (2) > Default (3) + // Test that user keymaps take precedence over default keymaps at the same context depth + let mut keymap = Keymap::default(); + + // Add a default keymap binding first + let mut default_binding = KeyBinding::new("cmd-r", ActionAlpha {}, Some("Editor")); + default_binding.set_meta(KeyBindingMetaIndex(3)); // Default source + keymap.add_bindings([default_binding]); + + // Add a user keymap binding + let mut user_binding = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")); + user_binding.set_meta(KeyBindingMetaIndex(0)); // User source + keymap.add_bindings([user_binding]); + + // Test with Editor context stack + let (result, _) = keymap.bindings_for_input( + &[Keystroke::parse("cmd-r").unwrap()], + &[KeyContext::parse("Editor").unwrap()], + ); + + // User binding should take precedence over default binding + assert_eq!(result.len(), 2); + assert!(result[0].action.partial_eq(&ActionBeta {})); + assert!(result[1].action.partial_eq(&ActionAlpha {})); + } } diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 4f1173a188b6d3113234c79f02a55d2c34cf12d9..3e6c96ac72f1e6dee568dafbfda6956c1dd5970c 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -70,11 +70,14 @@ impl VimTestContext { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |s| s.vim_mode = Some(enabled)); }); - let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure( + let mut default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure( "keymaps/default-macos.json", cx, ) .unwrap(); + for key_binding in &mut default_key_bindings { + key_binding.set_meta(settings::KeybindSource::Default.meta()); + } cx.bind_keys(default_key_bindings); if enabled { let vim_key_bindings = settings::KeymapFile::load_asset(