Add a collection of contextless action keystrokes that can be built with a shared key context (#43)
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
mod binding;
|
||||
mod collection;
|
||||
mod context;
|
||||
|
||||
pub use binding::*;
|
||||
pub use collection::*;
|
||||
pub use context::*;
|
||||
|
||||
use crate::{Action, AsKeystroke, Keystroke, Unbind, is_no_action, is_unbind};
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
use crate::{Action, DummyKeyboardMapper, KeyBinding, KeyBindingContextPredicate, SharedString};
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
|
||||
/// A generic collection of `Action` -> keystrokes which can be bound under a shared key-context.
|
||||
/// Use `as_keybindings` to create an iterator of `KeyBinding` to provide to `App::bind_keys`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use gpui::{ActionBindingCollection, Action};
|
||||
/// gpui::actions!([MyAction]);
|
||||
/// # #[gpui::test]
|
||||
/// # fn test(app: &mut gpui::TestAppContext) {
|
||||
/// app.bind_keys(ActionBindingCollection::default().with::<MyAction>("enter").as_keybindings(None));
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct ActionBindingCollection {
|
||||
entries: HashMap<TypeId, ActionBindingEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ActionBindingEntry {
|
||||
// The action used to build Keybinding
|
||||
action: Box<dyn Action>,
|
||||
// The keystrokes that trigger the action
|
||||
// NOTE: could limit allocations by utilizing smallvec
|
||||
keystrokes: Vec<SharedString>,
|
||||
}
|
||||
|
||||
impl ActionBindingCollection {
|
||||
/// Adds a keystroke to an action in the collection.
|
||||
pub fn with<A: Action + Default>(mut self, keystrokes: impl Into<SharedString>) -> Self {
|
||||
let action_id = TypeId::of::<A>();
|
||||
let entry = self.entries.entry(action_id);
|
||||
let entry = entry.or_insert_with(|| ActionBindingEntry {
|
||||
action: Box::new(A::default()),
|
||||
keystrokes: Vec::with_capacity(5),
|
||||
});
|
||||
entry.keystrokes.push(keystrokes.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates an iterator of `KeyBinding` which represents all action+keystrokes in this collection bound to the same key-context.
|
||||
/// This sequence can be provided to `App::bind_keys`, and any elements which use the same `InteractiveElement::key_context` will receive the bound actions.
|
||||
pub fn as_keybindings(&self, context: Option<&str>) -> impl Iterator<Item = KeyBinding> {
|
||||
let context_predicate =
|
||||
context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
|
||||
let iter = self.entries.iter();
|
||||
let iter = iter.map(move |(_, entry)| {
|
||||
let action = &entry.action;
|
||||
let context_predicate = context_predicate.clone();
|
||||
entry.keystrokes.iter().map(move |keystrokes| {
|
||||
let keybinding = KeyBinding::load(
|
||||
keystrokes.as_str(),
|
||||
action.boxed_clone(),
|
||||
context_predicate.clone(),
|
||||
false,
|
||||
None,
|
||||
&DummyKeyboardMapper,
|
||||
);
|
||||
keybinding.expect("failed to load keybinding")
|
||||
})
|
||||
});
|
||||
iter.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::TestAppContext;
|
||||
|
||||
actions!([TestAction]);
|
||||
|
||||
#[gpui::test]
|
||||
fn test_default_empty(_cx: &mut TestAppContext) {
|
||||
assert!(ActionBindingCollection::default().entries.is_empty());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_single_binding(_cx: &mut TestAppContext) {
|
||||
let collection = ActionBindingCollection::default();
|
||||
let collection = collection.with::<TestAction>("enter");
|
||||
|
||||
let entry = collection.entries.get(&TypeId::of::<TestAction>());
|
||||
let entry = entry.map(|entry| &entry.keystrokes);
|
||||
assert_eq!(entry, Some(&vec![SharedString::new_static("enter")]));
|
||||
|
||||
let out_bindings = collection.as_keybindings(None).collect::<Vec<_>>();
|
||||
assert_eq!(out_bindings.len(), 1);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_single_action_many_keys(_cx: &mut TestAppContext) {
|
||||
let collection = ActionBindingCollection::default();
|
||||
let collection = collection.with::<TestAction>("enter");
|
||||
let collection = collection.with::<TestAction>("escape");
|
||||
|
||||
let entry = collection.entries.get(&TypeId::of::<TestAction>());
|
||||
let entry = entry.map(|entry| &entry.keystrokes);
|
||||
assert_eq!(
|
||||
entry,
|
||||
Some(&vec![
|
||||
SharedString::new_static("enter"),
|
||||
SharedString::new_static("escape")
|
||||
])
|
||||
);
|
||||
|
||||
let out_bindings = collection.as_keybindings(None).collect::<Vec<_>>();
|
||||
assert_eq!(out_bindings.len(), 2);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_single_action_many_keys_context(_cx: &mut TestAppContext) {
|
||||
let collection = ActionBindingCollection::default();
|
||||
let collection = collection.with::<TestAction>("enter");
|
||||
let collection = collection.with::<TestAction>("escape");
|
||||
let out_bindings = collection
|
||||
.as_keybindings(Some("test_content"))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(out_bindings.len(), 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user