@@ -1,4 +1,4 @@
-use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke, NoAction};
+use crate::{Action, KeyBinding, KeyBindingContextPredicate, KeyContext, Keystroke, NoAction};
use collections::HashSet;
use smallvec::SmallVec;
use std::{
@@ -29,54 +29,22 @@ impl Keymap {
self.version
}
- pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ KeyBinding> {
- self.binding_indices_by_action_id
- .get(&action_id)
- .map(SmallVec::as_slice)
- .unwrap_or(&[])
- .iter()
- .map(|ix| &self.bindings[*ix])
- .filter(|binding| !self.binding_disabled(binding))
- }
-
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
- let no_action_id = &(NoAction {}).type_id();
- let mut new_bindings = Vec::new();
- let mut has_new_disabled_keystrokes = false;
+ let no_action_id = (NoAction {}).type_id();
+
for binding in bindings {
- if binding.action.type_id() == *no_action_id {
- has_new_disabled_keystrokes |= self
- .disabled_keystrokes
+ let action_id = binding.action().as_any().type_id();
+ if action_id == no_action_id {
+ self.disabled_keystrokes
.entry(binding.keystrokes)
.or_default()
.insert(binding.context_predicate);
} else {
- new_bindings.push(binding);
- }
- }
-
- if has_new_disabled_keystrokes {
- self.binding_indices_by_action_id.retain(|_, indices| {
- indices.retain(|ix| {
- let binding = &self.bindings[*ix];
- match self.disabled_keystrokes.get(&binding.keystrokes) {
- Some(disabled_predicates) => {
- !disabled_predicates.contains(&binding.context_predicate)
- }
- None => true,
- }
- });
- !indices.is_empty()
- });
- }
-
- for new_binding in new_bindings {
- if !self.binding_disabled(&new_binding) {
self.binding_indices_by_action_id
- .entry(new_binding.action().as_any().type_id())
+ .entry(action_id)
.or_default()
.push(self.bindings.len());
- self.bindings.push(new_binding);
+ self.bindings.push(binding);
}
}
@@ -90,311 +58,113 @@ impl Keymap {
self.version.0 += 1;
}
- pub fn bindings(&self) -> Vec<&KeyBinding> {
- self.bindings
- .iter()
- .filter(|binding| !self.binding_disabled(binding))
- .collect()
+ /// Iterate over all bindings, in the order they were added.
+ pub fn bindings(&self) -> impl Iterator<Item = &KeyBinding> + DoubleEndedIterator {
+ self.bindings.iter()
}
- fn binding_disabled(&self, binding: &KeyBinding) -> bool {
- match self.disabled_keystrokes.get(&binding.keystrokes) {
- Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
- None => false,
- }
+ /// Iterate over all bindings for the given action, in the order they were added.
+ pub fn bindings_for_action<'a>(
+ &'a self,
+ action: &'a dyn Action,
+ ) -> impl 'a + Iterator<Item = &'a KeyBinding> + DoubleEndedIterator {
+ let action_id = action.type_id();
+ self.binding_indices_by_action_id
+ .get(&action_id)
+ .map_or(&[] as _, SmallVec::as_slice)
+ .iter()
+ .map(|ix| &self.bindings[*ix])
+ .filter(move |binding| binding.action().partial_eq(action))
}
-}
-
-// #[cfg(test)]
-// mod tests {
-// use crate::actions;
-
-// use super::*;
-// actions!(
-// keymap_test,
-// [Present1, Present2, Present3, Duplicate, Missing]
-// );
-
-// #[test]
-// fn regular_keymap() {
-// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
-// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
-// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
-// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-// let missing = Binding::new("ctrl-r", Missing {}, None);
-// let all_bindings = [
-// &present_1,
-// &present_2,
-// &present_3,
-// &keystroke_duplicate_to_1,
-// &full_duplicate_to_2,
-// &missing,
-// ];
-
-// let mut keymap = Keymap::default();
-// assert_absent(&keymap, &all_bindings);
-// assert!(keymap.bindings().is_empty());
-
-// keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
-// assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
-// assert_present(
-// &keymap,
-// &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
-// );
-
-// keymap.add_bindings([
-// keystroke_duplicate_to_1.clone(),
-// full_duplicate_to_2.clone(),
-// ]);
-// assert_absent(&keymap, &[&missing]);
-// assert!(
-// !keymap.binding_disabled(&keystroke_duplicate_to_1),
-// "Duplicate binding 1 was added and should not be disabled"
-// );
-// assert!(
-// !keymap.binding_disabled(&full_duplicate_to_2),
-// "Duplicate binding 2 was added and should not be disabled"
-// );
-
-// assert_eq!(
-// keymap
-// .bindings_for_action(keystroke_duplicate_to_1.action().id())
-// .map(|binding| &binding.keystrokes)
-// .flatten()
-// .collect::<Vec<_>>(),
-// vec![&Keystroke {
-// ctrl: true,
-// alt: false,
-// shift: false,
-// cmd: false,
-// function: false,
-// key: "q".to_string(),
-// ime_key: None,
-// }],
-// "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
-// );
-// assert_eq!(
-// keymap
-// .bindings_for_action(full_duplicate_to_2.action().id())
-// .map(|binding| &binding.keystrokes)
-// .flatten()
-// .collect::<Vec<_>>(),
-// vec![
-// &Keystroke {
-// ctrl: true,
-// alt: false,
-// shift: false,
-// cmd: false,
-// function: false,
-// key: "w".to_string(),
-// ime_key: None,
-// },
-// &Keystroke {
-// ctrl: true,
-// alt: false,
-// shift: false,
-// cmd: false,
-// function: false,
-// key: "w".to_string(),
-// ime_key: None,
-// }
-// ],
-// "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
-// );
-
-// let updated_bindings = keymap.bindings();
-// let expected_updated_bindings = vec![
-// &present_1,
-// &present_2,
-// &present_3,
-// &keystroke_duplicate_to_1,
-// &full_duplicate_to_2,
-// ];
-// assert_eq!(
-// updated_bindings.len(),
-// expected_updated_bindings.len(),
-// "Unexpected updated keymap bindings {updated_bindings:?}"
-// );
-// for (i, expected) in expected_updated_bindings.iter().enumerate() {
-// let keymap_binding = &updated_bindings[i];
-// assert_eq!(
-// keymap_binding.context_predicate, expected.context_predicate,
-// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
-// );
-// assert_eq!(
-// keymap_binding.keystrokes, expected.keystrokes,
-// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
-// );
-// }
-
-// keymap.clear();
-// assert_absent(&keymap, &all_bindings);
-// assert!(keymap.bindings().is_empty());
-// }
-
-// #[test]
-// fn keymap_with_ignored() {
-// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
-// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
-// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
-// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-// let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
-// let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
-// let ignored_3_with_other_context =
-// Binding::new("ctrl-e", NoAction {}, Some("other_context"));
-
-// let mut keymap = Keymap::default();
-
-// keymap.add_bindings([
-// ignored_1.clone(),
-// ignored_2.clone(),
-// ignored_3_with_other_context.clone(),
-// ]);
-// assert_absent(&keymap, &[&present_3]);
-// assert_disabled(
-// &keymap,
-// &[
-// &present_1,
-// &present_2,
-// &ignored_1,
-// &ignored_2,
-// &ignored_3_with_other_context,
-// ],
-// );
-// assert!(keymap.bindings().is_empty());
-// keymap.clear();
-
-// keymap.add_bindings([
-// present_1.clone(),
-// present_2.clone(),
-// present_3.clone(),
-// ignored_1.clone(),
-// ignored_2.clone(),
-// ignored_3_with_other_context.clone(),
-// ]);
-// assert_present(&keymap, &[(&present_3, "e")]);
-// assert_disabled(
-// &keymap,
-// &[
-// &present_1,
-// &present_2,
-// &ignored_1,
-// &ignored_2,
-// &ignored_3_with_other_context,
-// ],
-// );
-// keymap.clear();
-
-// keymap.add_bindings([
-// present_1.clone(),
-// present_2.clone(),
-// present_3.clone(),
-// ignored_1.clone(),
-// ]);
-// assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
-// assert_disabled(&keymap, &[&present_1, &ignored_1]);
-// assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
-// keymap.clear();
+ pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
+ // If binding has a context predicate, it must match the current context,
+ if let Some(predicate) = &binding.context_predicate {
+ if !predicate.eval(context) {
+ return false;
+ }
+ }
-// keymap.add_bindings([
-// present_1.clone(),
-// present_2.clone(),
-// present_3.clone(),
-// keystroke_duplicate_to_1.clone(),
-// full_duplicate_to_2.clone(),
-// ignored_1.clone(),
-// ignored_2.clone(),
-// ignored_3_with_other_context.clone(),
-// ]);
-// assert_present(&keymap, &[(&present_3, "e")]);
-// assert_disabled(
-// &keymap,
-// &[
-// &present_1,
-// &present_2,
-// &keystroke_duplicate_to_1,
-// &full_duplicate_to_2,
-// &ignored_1,
-// &ignored_2,
-// &ignored_3_with_other_context,
-// ],
-// );
-// keymap.clear();
-// }
+ if let Some(disabled_predicates) = self.disabled_keystrokes.get(&binding.keystrokes) {
+ for disabled_predicate in disabled_predicates {
+ match disabled_predicate {
+ // The binding must not be globally disabled.
+ None => return false,
-// #[track_caller]
-// fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
-// let keymap_bindings = keymap.bindings();
-// assert_eq!(
-// expected_bindings.len(),
-// keymap_bindings.len(),
-// "Unexpected keymap bindings {keymap_bindings:?}"
-// );
-// for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
-// assert!(
-// !keymap.binding_disabled(expected),
-// "{expected:?} should not be disabled as it was added into keymap for element {i}"
-// );
-// assert_eq!(
-// keymap
-// .bindings_for_action(expected.action().id())
-// .map(|binding| &binding.keystrokes)
-// .flatten()
-// .collect::<Vec<_>>(),
-// vec![&Keystroke {
-// ctrl: true,
-// alt: false,
-// shift: false,
-// cmd: false,
-// function: false,
-// key: expected_key.to_string(),
-// ime_key: None,
-// }],
-// "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
-// );
+ // The binding must not be disabled in the current context.
+ Some(predicate) => {
+ if predicate.eval(context) {
+ return false;
+ }
+ }
+ }
+ }
+ }
-// let keymap_binding = &keymap_bindings[i];
-// assert_eq!(
-// keymap_binding.context_predicate, expected.context_predicate,
-// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
-// );
-// assert_eq!(
-// keymap_binding.keystrokes, expected.keystrokes,
-// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
-// );
-// }
-// }
+ true
+ }
+}
-// #[track_caller]
-// fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
-// for binding in bindings.iter() {
-// assert!(
-// !keymap.binding_disabled(binding),
-// "{binding:?} should not be disabled in the keymap where was not added"
-// );
-// assert_eq!(
-// keymap.bindings_for_action(binding.action().id()).count(),
-// 0,
-// "{binding:?} should have no actions in the keymap where was not added"
-// );
-// }
-// }
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate as gpui;
+ use gpui::actions;
+
+ actions!(
+ keymap_test,
+ [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
+ );
+
+ #[test]
+ fn test_keymap() {
+ let bindings = [
+ KeyBinding::new("ctrl-a", ActionAlpha {}, None),
+ KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
+ KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ // global bindings are enabled in all contexts
+ assert!(keymap.binding_enabled(&bindings[0], &[]));
+ assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]));
+
+ // contextual bindings are enabled in contexts that match their predicate
+ assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]));
+ assert!(keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]));
+
+ assert!(!keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]));
+ assert!(keymap.binding_enabled(
+ &bindings[2],
+ &[KeyContext::parse("editor mode=full").unwrap()]
+ ));
+ }
-// #[track_caller]
-// fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
-// for binding in bindings.iter() {
-// assert!(
-// keymap.binding_disabled(binding),
-// "{binding:?} should be disabled in the keymap"
-// );
-// assert_eq!(
-// keymap.bindings_for_action(binding.action().id()).count(),
-// 0,
-// "{binding:?} should have no actions in the keymap where it was disabled"
-// );
-// }
-// }
-// }
+ #[test]
+ fn test_keymap_disabled() {
+ let bindings = [
+ KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
+ KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
+ KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
+ KeyBinding::new("ctrl-b", NoAction {}, None),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ // binding is only enabled in a specific context
+ assert!(!keymap.binding_enabled(&bindings[0], &[KeyContext::parse("barf").unwrap()]));
+ assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("editor").unwrap()]));
+
+ // binding is disabled in a more specific context
+ assert!(!keymap.binding_enabled(
+ &bindings[0],
+ &[KeyContext::parse("editor mode=full").unwrap()]
+ ));
+
+ // binding is globally disabled
+ assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf").unwrap()]));
+ }
+}