@@ -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 {}));
+ }
}
@@ -70,11 +70,14 @@ impl VimTestContext {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<VimModeSetting>(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(