use std::collections::BTreeMap;
use std::sync::Arc;
use crate::{
parse_key_sequence, Binding, BindingGroup, CategoryBuilder, DisplayBinding, GroupBuilder, Key,
KeyChild, KeyNode, LeafEntry, NodeResult, ScopeAndCategoryBuilder, ScopeBuilder,
};
type CatchAllHandler<K, A> = Arc<dyn Fn(&K) -> Option<A> + Send + Sync>;
#[derive(Clone)]
pub struct Keymap<K: Key, S, A, C> {
bindings: Vec<KeyChild<K, S, A, C>>,
leader_key: K,
catch_all_handlers: BTreeMap<S, CatchAllHandler<K, A>>,
}
impl<K: Key, S, A, C> std::fmt::Debug for Keymap<K, S, A, C>
where
K: std::fmt::Debug,
S: std::fmt::Debug,
A: std::fmt::Debug,
C: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Keymap")
.field("bindings", &self.bindings)
.field("leader_key", &self.leader_key)
.finish_non_exhaustive()
}
}
impl<K: Key, S, A, C: Clone> Keymap<K, S, A, C> {
#[must_use]
pub fn new() -> Self {
Self {
bindings: Vec::new(),
leader_key: K::space(),
catch_all_handlers: BTreeMap::new(),
}
}
#[must_use]
pub fn with_leader(leader_key: K) -> Self {
Self {
bindings: Vec::new(),
leader_key,
catch_all_handlers: BTreeMap::new(),
}
}
#[must_use]
pub fn leader_key(&self) -> &K {
&self.leader_key
}
#[must_use]
pub fn bindings(&self) -> &[KeyChild<K, S, A, C>] {
&self.bindings
}
pub fn bind(&mut self, sequence: &str, action: A, category: C, scope: S) -> &mut Self
where
K: Clone,
S: Clone + PartialEq,
A: Clone + std::fmt::Display,
C: Clone,
{
let keys = parse_key_sequence(sequence);
if keys.is_empty() {
return self;
}
self.insert_into_tree(&keys, action, category, scope);
self
}
pub(super) fn insert_into_tree(&mut self, keys: &[K], action: A, category: C, scope: S)
where
K: Clone,
S: Clone + PartialEq,
A: Clone + std::fmt::Display,
C: Clone,
{
if keys.is_empty() {
return;
}
let first_key = keys[0].clone();
if let Some(child) = self.bindings.iter_mut().find(|c| c.key == first_key) {
Self::insert_into_child(child, keys, action, category, scope);
} else {
let new_child = Self::build_tree(keys, action, category, scope);
self.bindings.push(new_child);
}
}
fn build_tree(keys: &[K], action: A, category: C, scope: S) -> KeyChild<K, S, A, C>
where
K: Clone,
S: Clone,
A: Clone + std::fmt::Display,
C: Clone,
{
if keys.len() == 1 {
let description = action.to_string();
KeyChild::leaf(keys[0].clone(), action, description, category, scope)
} else {
let first = keys[0].clone();
let rest = &keys[1..];
let child = Self::build_tree(rest, action, category, scope);
KeyChild::branch(first, "...", vec![child])
}
}
fn insert_into_child(
child: &mut KeyChild<K, S, A, C>,
keys: &[K],
action: A,
category: C,
scope: S,
) where
K: Clone,
S: Clone + PartialEq,
A: Clone + std::fmt::Display,
C: Clone,
{
if keys.len() == 1 {
let description = action.to_string();
match &mut child.node {
KeyNode::Leaf(entries) => {
if let Some(existing) = entries.iter_mut().find(|e| e.scope == scope) {
existing.action = action;
existing.description = description;
existing.category = category;
} else {
entries.push(LeafEntry {
action,
description,
category,
scope,
});
}
}
KeyNode::Branch { .. } => {
child.node = KeyNode::Leaf(vec![LeafEntry {
action,
description,
category,
scope,
}]);
}
}
return;
}
let next_key = keys[1].clone();
match &mut child.node {
KeyNode::Leaf(_) => {
let new_child = Self::build_tree(&keys[1..], action, category, scope);
child.node = KeyNode::Branch {
description: "...",
children: vec![new_child],
};
}
KeyNode::Branch { children, .. } => {
if let Some(next_child) = children.iter_mut().find(|c| c.key == next_key) {
Self::insert_into_child(next_child, &keys[1..], action, category, scope);
} else {
let new_child = Self::build_tree(&keys[1..], action, category, scope);
children.push(new_child);
}
}
}
}
#[must_use]
pub fn get_node_at_path(&self, keys: &[K]) -> Option<&KeyNode<K, S, A, C>>
where
K: PartialEq,
{
if keys.is_empty() {
return None;
}
let child = self.bindings.iter().find(|c| c.key == keys[0])?;
if keys.len() == 1 {
return Some(&child.node);
}
Self::get_node_in_children(&child.node, &keys[1..])
}
fn get_node_in_children<'a>(
node: &'a KeyNode<K, S, A, C>,
keys: &[K],
) -> Option<&'a KeyNode<K, S, A, C>>
where
K: PartialEq,
{
match node {
KeyNode::Leaf(_) => {
if keys.is_empty() {
Some(node)
} else {
None
}
}
KeyNode::Branch { children, .. } => {
if keys.is_empty() {
return Some(node);
}
let child = children.iter().find(|c| c.key == keys[0])?;
if keys.len() == 1 {
Some(&child.node)
} else {
Self::get_node_in_children(&child.node, &keys[1..])
}
}
}
}
#[must_use]
pub fn get_children_at_path(&self, keys: &[K]) -> Option<Vec<(K, String)>>
where
K: PartialEq + Clone,
{
let node = if keys.is_empty() {
return Some(
self.bindings
.iter()
.map(|c| (c.key.clone(), c.node.description()))
.collect(),
);
} else {
self.get_node_at_path(keys)?
};
match node {
KeyNode::Branch { children, .. } => Some(
children
.iter()
.map(|c| (c.key.clone(), c.node.description()))
.collect(),
),
KeyNode::Leaf(_) => None,
}
}
pub fn is_prefix_key(&self, key: K) -> bool
where
K: PartialEq,
{
self.bindings
.iter()
.any(|c| c.key == key && c.node.is_branch())
}
pub fn get_bindings_for_scope(&self, scope: S) -> Vec<DisplayBinding<K, C>>
where
K: Clone,
S: Clone + PartialEq,
A: Clone,
C: Clone,
{
self.bindings
.iter()
.filter_map(|child| match &child.node {
KeyNode::Leaf(entries) => {
entries
.iter()
.find(|entry| entry.scope == scope)
.map(|entry| DisplayBinding {
key: child.key.clone(),
description: entry.description.clone(),
category: entry.category.clone(),
})
}
KeyNode::Branch { description, .. } => {
Self::find_category_in_children(&child.node, &scope).map(|category| {
DisplayBinding {
key: child.key.clone(),
description: description.to_string(),
category,
}
})
}
})
.collect()
}
fn find_category_in_children(node: &KeyNode<K, S, A, C>, scope: &S) -> Option<C>
where
K: Clone,
S: Clone + PartialEq,
A: Clone,
C: Clone,
{
match node {
KeyNode::Leaf(entries) => entries
.iter()
.find(|e| &e.scope == scope)
.map(|e| e.category.clone()),
KeyNode::Branch { children, .. } => children
.iter()
.find_map(|c| Self::find_category_in_children_recursive(&c.node, scope)),
}
}
fn find_category_in_children_recursive(node: &KeyNode<K, S, A, C>, scope: &S) -> Option<C>
where
K: Clone,
S: Clone + PartialEq,
A: Clone,
C: Clone,
{
match node {
KeyNode::Leaf(entries) => entries
.iter()
.find(|e| &e.scope == scope)
.map(|e| e.category.clone()),
KeyNode::Branch { children, .. } => children
.iter()
.find_map(|c| Self::find_category_in_children_recursive(&c.node, scope)),
}
}
#[must_use]
pub fn bindings_for_scope(&self, scope: S) -> Vec<BindingGroup<K>>
where
K: Clone,
S: Clone + PartialEq,
A: Clone,
C: Clone + std::fmt::Debug,
{
let bindings = self.get_bindings_for_scope(scope);
let groups: std::collections::BTreeMap<String, Vec<Binding<K>>> = bindings
.iter()
.map(|b| {
(
format!("{:?}", b.category),
Binding {
key: b.key.clone(),
description: b.description.clone(),
},
)
})
.fold(std::collections::BTreeMap::new(), |mut acc, (cat, bind)| {
acc.entry(cat).or_default().push(bind);
acc
});
groups
.into_iter()
.map(|(category, bindings)| BindingGroup { category, bindings })
.collect()
}
#[must_use]
pub fn children_at_path(&self, keys: &[K]) -> Option<Vec<Binding<K>>>
where
K: Clone + PartialEq,
{
self.get_children_at_path(keys).map(|children| {
children
.into_iter()
.map(|(key, description)| Binding { key, description })
.collect()
})
}
fn has_bindings_for_scope(node: &KeyNode<K, S, A, C>, scope: &S) -> bool
where
S: PartialEq,
C: Clone,
{
match node {
KeyNode::Leaf(entries) => entries.iter().any(|e| &e.scope == scope),
KeyNode::Branch { children, .. } => children
.iter()
.any(|c| Self::has_bindings_for_scope(&c.node, scope)),
}
}
#[must_use]
pub fn navigate(&self, keys: &[K], scope: &S) -> Option<NodeResult<K, A>>
where
K: Clone + PartialEq,
A: Clone,
S: PartialEq,
C: Clone,
{
let node = self.get_node_at_path(keys)?;
match node {
KeyNode::Branch { children, .. } => {
if Self::has_bindings_for_scope(node, scope) {
Some(NodeResult::Branch {
children: children
.iter()
.map(|c| Binding {
key: c.key.clone(),
description: c.node.description(),
})
.collect(),
})
} else {
None
}
}
KeyNode::Leaf(entries) => {
entries
.iter()
.find(|e| &e.scope == scope)
.map(|e| NodeResult::Leaf {
action: e.action.clone(),
})
}
}
}
pub fn describe_group(&mut self, prefix: &str, description: &'static str) -> &mut Self
where
K: Clone,
S: Clone,
A: Clone,
C: Clone,
{
let keys = parse_key_sequence(prefix);
if keys.is_empty() {
return self;
}
self.ensure_branch_with_description(&keys, description);
self
}
pub(super) fn ensure_branch_with_description(&mut self, keys: &[K], description: &'static str)
where
K: Clone,
S: Clone,
A: Clone,
C: Clone,
{
if keys.is_empty() {
return;
}
let first_key = keys[0].clone();
if let Some(child) = self.bindings.iter_mut().find(|c| c.key == first_key) {
Self::ensure_branch_in_child(child, keys, description);
} else {
let new_child = Self::build_branch_tree(keys, description);
self.bindings.push(new_child);
}
}
fn build_branch_tree(keys: &[K], description: &'static str) -> KeyChild<K, S, A, C>
where
K: Clone,
S: Clone,
A: Clone,
C: Clone,
{
if keys.len() == 1 {
KeyChild::branch(keys[0].clone(), description, Vec::new())
} else {
let first = keys[0].clone();
let rest = &keys[1..];
let child = Self::build_branch_tree(rest, description);
KeyChild::branch(first, description, vec![child])
}
}
fn ensure_final_key_is_branch(child: &mut KeyChild<K, S, A, C>, description: &'static str) {
match &mut child.node {
KeyNode::Branch {
description: desc, ..
} => {
if *desc == "..." {
*desc = description;
}
}
KeyNode::Leaf(_) => {
child.node = KeyNode::Branch {
description,
children: Vec::new(),
};
}
}
}
fn ensure_branch_in_child(
child: &mut KeyChild<K, S, A, C>,
keys: &[K],
description: &'static str,
) where
K: Clone,
S: Clone,
A: Clone,
{
if keys.len() == 1 {
Self::ensure_final_key_is_branch(child, description);
return;
}
let next_key = keys[1].clone();
let remaining = &keys[1..];
match &mut child.node {
KeyNode::Leaf(_) => {
let new_child = Self::build_branch_tree(remaining, description);
child.node = KeyNode::Branch {
description,
children: vec![new_child],
};
}
KeyNode::Branch {
description: desc,
children,
} => {
if *desc == "..." {
*desc = description;
}
if let Some(next_child) = children.iter_mut().find(|c| c.key == next_key) {
Self::ensure_branch_in_child(next_child, remaining, description);
} else {
let new_child = Self::build_branch_tree(remaining, description);
children.push(new_child);
}
}
}
}
pub fn group<F>(&mut self, prefix: &str, description: &'static str, bindings: F) -> &mut Self
where
K: Clone,
S: Clone,
A: Clone,
C: Clone,
F: FnOnce(&mut GroupBuilder<K, S, A, C>),
{
let prefix_keys = parse_key_sequence(prefix);
if prefix_keys.is_empty() {
return self;
}
self.ensure_branch_with_description(&prefix_keys, description);
let mut builder = GroupBuilder::new(self, prefix_keys);
bindings(&mut builder);
self
}
pub fn scope<F>(&mut self, scope: S, bindings: F) -> &mut Self
where
F: FnOnce(&mut ScopeBuilder<K, S, A, C>),
{
let mut builder = ScopeBuilder::new(self, scope);
bindings(&mut builder);
self
}
pub fn category<F>(&mut self, category: C, bindings: F) -> &mut Self
where
F: FnOnce(&mut CategoryBuilder<'_, K, S, A, C>),
{
let mut builder = CategoryBuilder::new(self, category);
bindings(&mut builder);
self
}
pub fn scope_and_category<F>(&mut self, scope: S, category: C, bindings: F) -> &mut Self
where
F: FnOnce(&mut ScopeAndCategoryBuilder<'_, K, S, A, C>),
{
let mut builder = ScopeAndCategoryBuilder::new(self, scope, category);
bindings(&mut builder);
self
}
pub fn register_catch_all<F>(&mut self, scope: S, handler: F)
where
S: Ord,
F: Fn(&K) -> Option<A> + Send + Sync + 'static,
{
self.catch_all_handlers.insert(scope, Arc::new(handler));
}
pub fn catch_all_handlers(&self) -> &BTreeMap<S, CatchAllHandler<K, A>> {
&self.catch_all_handlers
}
}
impl<K: Key, S, A, C: Clone> Default for Keymap<K, S, A, C> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::keymap_with_binding;
use crate::CrosstermKey;
#[derive(Debug, Clone, PartialEq)]
enum TestAction {
Quit,
Save,
Open,
}
impl std::fmt::Display for TestAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TestAction::Quit => write!(f, "quit"),
TestAction::Save => write!(f, "save"),
TestAction::Open => write!(f, "open"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum TestScope {
Global,
Insert,
Normal,
}
#[derive(Debug, Clone, PartialEq)]
enum TestCategory {
General,
Navigation,
}
fn get_leaf_entries<S: Clone + PartialEq, A: Clone, C: Clone + PartialEq>(
keymap: &Keymap<CrosstermKey, S, A, C>,
) -> Vec<(S, A, C, String)> {
let child = &keymap.bindings()[0];
if let KeyNode::Leaf(entries) = &child.node {
entries
.iter()
.map(|e| {
(
e.scope.clone(),
e.action.clone(),
e.category.clone(),
e.description.clone(),
)
})
.collect()
} else {
panic!("expected leaf node");
}
}
fn get_leaf_entry_count<S: Clone + PartialEq, A: Clone, C: Clone + PartialEq>(
keymap: &Keymap<CrosstermKey, S, A, C>,
) -> usize {
let child = &keymap.bindings()[0];
if let KeyNode::Leaf(entries) = &child.node {
entries.len()
} else {
panic!("expected leaf node");
}
}
#[test]
fn new_creates_empty_keymap_with_space_leader() {
let keymap: Keymap<CrosstermKey, (), TestAction, TestCategory> = Keymap::new();
assert_eq!(keymap.leader_key(), &CrosstermKey::Char(' '));
assert!(keymap.bindings().is_empty());
}
#[test]
fn with_leader_creates_keymap_with_custom_leader() {
let keymap: Keymap<CrosstermKey, (), TestAction, TestCategory> =
Keymap::with_leader(CrosstermKey::Esc);
assert_eq!(keymap.leader_key(), &CrosstermKey::Esc);
assert!(keymap.bindings().is_empty());
}
#[test]
fn bind_single_key_creates_leaf_node() {
let keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
assert_eq!(keymap.bindings().len(), 1);
let child = &keymap.bindings()[0];
assert_eq!(child.key, CrosstermKey::Char('q'));
assert!(matches!(child.node, KeyNode::Leaf(_)));
}
#[test]
fn multi_key_binding_count_is_one() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
assert_eq!(keymap.bindings().len(), 1);
}
#[test]
fn multi_key_root_key_is_g() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
assert_eq!(keymap.bindings()[0].key, CrosstermKey::Char('g'));
}
#[test]
fn multi_key_root_is_branch() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
assert!(keymap.bindings()[0].node.is_branch());
}
#[test]
fn multi_key_second_level_count_is_one() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
let child = &keymap.bindings()[0];
if let KeyNode::Branch { children, .. } = &child.node {
assert_eq!(children.len(), 1);
} else {
panic!("expected branch node");
}
}
#[test]
fn multi_key_second_level_key_is_g() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
let child = &keymap.bindings()[0];
if let KeyNode::Branch { children, .. } = &child.node {
assert_eq!(children[0].key, CrosstermKey::Char('g'));
} else {
panic!("expected branch node");
}
}
#[test]
fn multi_key_second_level_is_leaf() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
let child = &keymap.bindings()[0];
if let KeyNode::Branch { children, .. } = &child.node {
assert!(matches!(children[0].node, KeyNode::Leaf(_)));
} else {
panic!("expected branch node");
}
}
#[test]
fn same_scope_multi_entry_count_is_two() {
let mut keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"<esc>",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"<esc>",
TestAction::Save,
TestCategory::General,
TestScope::Insert,
);
let count = get_leaf_entry_count(&keymap);
assert_eq!(count, 2);
}
#[test]
fn same_scope_multi_entry_first_scope_is_global() {
let mut keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"<esc>",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"<esc>",
TestAction::Save,
TestCategory::General,
TestScope::Insert,
);
let entries = get_leaf_entries(&keymap);
assert_eq!(entries[0].0, TestScope::Global);
}
#[test]
fn same_scope_multi_entry_second_scope_is_insert() {
let mut keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"<esc>",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"<esc>",
TestAction::Save,
TestCategory::General,
TestScope::Insert,
);
let entries = get_leaf_entries(&keymap);
assert_eq!(entries[1].0, TestScope::Insert);
}
#[test]
fn same_scope_update_entry_count_is_one() {
let mut keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"q",
TestAction::Save,
TestCategory::Navigation,
TestScope::Global,
);
let count = get_leaf_entry_count(&keymap);
assert_eq!(count, 1);
}
#[test]
fn same_scope_update_action_is_save() {
let mut keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"q",
TestAction::Save,
TestCategory::Navigation,
TestScope::Global,
);
let entries = get_leaf_entries(&keymap);
assert_eq!(entries[0].1, TestAction::Save);
}
#[test]
fn same_scope_update_description_is_save() {
let mut keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"q",
TestAction::Save,
TestCategory::Navigation,
TestScope::Global,
);
let entries = get_leaf_entries(&keymap);
assert_eq!(entries[0].3, "save");
}
#[test]
fn same_scope_update_category_is_navigation() {
let mut keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"q",
TestAction::Save,
TestCategory::Navigation,
TestScope::Global,
);
let entries = get_leaf_entries(&keymap);
assert_eq!(entries[0].2, TestCategory::Navigation);
}
#[test]
fn bind_empty_sequence_does_nothing() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
assert!(keymap.bindings().is_empty());
}
#[test]
fn branch_extension_binding_count_is_one() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.bind(
"gd",
TestAction::Open,
TestCategory::Navigation,
TestScope::Global,
);
assert_eq!(keymap.bindings().len(), 1);
}
#[test]
fn branch_extension_root_key_is_g() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.bind(
"gd",
TestAction::Open,
TestCategory::Navigation,
TestScope::Global,
);
assert_eq!(keymap.bindings()[0].key, CrosstermKey::Char('g'));
}
#[test]
fn branch_extension_children_count_is_two() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.bind(
"gd",
TestAction::Open,
TestCategory::Navigation,
TestScope::Global,
);
let child = &keymap.bindings()[0];
if let KeyNode::Branch { children, .. } = &child.node {
assert_eq!(children.len(), 2);
} else {
panic!("expected branch node");
}
}
#[test]
fn branch_extension_includes_d_key() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.bind(
"gd",
TestAction::Open,
TestCategory::Navigation,
TestScope::Global,
);
let child = &keymap.bindings()[0];
if let KeyNode::Branch { children, .. } = &child.node {
let keys: Vec<_> = children.iter().map(|c| c.key.clone()).collect();
assert!(keys.contains(&CrosstermKey::Char('d')));
} else {
panic!("expected branch node");
}
}
#[test]
fn branch_extension_preserves_g_key() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.bind(
"gd",
TestAction::Open,
TestCategory::Navigation,
TestScope::Global,
);
let child = &keymap.bindings()[0];
if let KeyNode::Branch { children, .. } = &child.node {
let keys: Vec<_> = children.iter().map(|c| c.key.clone()).collect();
assert!(keys.contains(&CrosstermKey::Char('g')));
} else {
panic!("expected branch node");
}
}
#[test]
fn bind_converts_leaf_to_branch_when_extending() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"g",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.bind(
"gg",
TestAction::Open,
TestCategory::Navigation,
TestScope::Global,
);
assert_eq!(keymap.bindings().len(), 1);
let child = &keymap.bindings()[0];
assert!(child.node.is_branch());
if let KeyNode::Branch { children, .. } = &child.node {
assert_eq!(children.len(), 1);
assert_eq!(children[0].key, CrosstermKey::Char('g'));
} else {
panic!("expected branch node");
}
}
#[test]
fn bind_returns_self_for_chaining() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap
.bind(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
)
.bind(
"w",
TestAction::Save,
TestCategory::General,
TestScope::Global,
);
assert_eq!(keymap.bindings().len(), 2);
}
#[test]
fn get_node_at_path_returns_none_for_empty_keys() {
let keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
let result = keymap.get_node_at_path(&[]);
assert!(result.is_none());
}
#[test]
fn get_node_at_path_returns_none_for_nonexistent_key() {
let keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
let result = keymap.get_node_at_path(&[CrosstermKey::Char('x')]);
assert!(result.is_none());
}
#[test]
fn get_children_at_path_returns_root_bindings_for_empty_keys() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"w",
TestAction::Save,
TestCategory::General,
TestScope::Global,
);
let result = keymap.get_children_at_path(&[]);
assert!(result.is_some());
let children = result.unwrap();
assert_eq!(children.len(), 2);
}
#[test]
fn get_children_at_path_returns_none_for_leaf() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
let result = keymap.get_children_at_path(&[CrosstermKey::Char('q')]);
assert!(result.is_none());
}
#[test]
fn is_prefix_key_returns_false_for_leaf() {
let keymap = keymap_with_binding::<CrosstermKey, TestScope, TestAction, TestCategory>(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
let result = keymap.is_prefix_key(CrosstermKey::Char('q'));
assert!(!result);
}
#[test]
fn get_bindings_for_scope_filters_by_exact_scope() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
keymap.bind(
"w",
TestAction::Save,
TestCategory::General,
TestScope::Insert,
);
keymap.bind(
"e",
TestAction::Open,
TestCategory::General,
TestScope::Normal,
);
let result = keymap.get_bindings_for_scope(TestScope::Insert);
assert_eq!(result.len(), 1);
assert_eq!(result[0].key, CrosstermKey::Char('w'));
assert_eq!(result[0].description, "save");
}
#[test]
fn get_bindings_for_scope_returns_empty_for_no_matches() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
let result = keymap.get_bindings_for_scope(TestScope::Insert);
assert!(result.is_empty());
}
#[test]
fn get_bindings_for_scope_includes_branches() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.bind(
"q",
TestAction::Quit,
TestCategory::General,
TestScope::Global,
);
let result = keymap.get_bindings_for_scope(TestScope::Global);
assert_eq!(result.len(), 2);
let keys: Vec<_> = result.iter().map(|b| b.key.clone()).collect();
assert!(keys.contains(&CrosstermKey::Char('g')));
assert!(keys.contains(&CrosstermKey::Char('q')));
}
#[test]
fn describe_prefix_creates_branch_at_path() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("g", "go commands");
assert_eq!(keymap.bindings().len(), 1);
let child = &keymap.bindings()[0];
assert_eq!(child.key, CrosstermKey::Char('g'));
assert!(child.node.is_branch());
if let KeyNode::Branch {
description,
children,
} = &child.node
{
assert_eq!(*description, "go commands");
assert!(children.is_empty());
} else {
panic!("expected branch node");
}
}
#[test]
fn describe_prefix_updates_existing_placeholder_description() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
keymap.describe_group("g", "go commands");
let child = &keymap.bindings()[0];
if let KeyNode::Branch { description, .. } = &child.node {
assert_eq!(*description, "go commands");
} else {
panic!("expected branch node");
}
}
#[test]
fn single_level_branch_count_is_one() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("a", "single command");
assert_eq!(keymap.bindings().len(), 1);
}
#[test]
fn single_level_branch_key_is_a() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("a", "single command");
assert_eq!(keymap.bindings()[0].key, CrosstermKey::Char('a'));
}
#[test]
fn single_level_branch_description_is_set() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("a", "single command");
if let KeyNode::Branch { description, .. } = &keymap.bindings()[0].node {
assert_eq!(*description, "single command");
} else {
panic!("expected branch node");
}
}
#[test]
fn single_level_branch_has_no_children() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("a", "single command");
if let KeyNode::Branch { children, .. } = &keymap.bindings()[0].node {
assert!(children.is_empty());
} else {
panic!("expected branch node");
}
}
#[test]
fn nested_branches_first_key_is_a() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("abc", "nested command");
assert_eq!(keymap.bindings()[0].key, CrosstermKey::Char('a'));
}
#[test]
fn nested_branches_first_description_is_nested_command() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("abc", "nested command");
if let KeyNode::Branch { description, .. } = &keymap.bindings()[0].node {
assert_eq!(*description, "nested command");
} else {
panic!("expected branch node at 'a'");
}
}
#[test]
fn nested_branches_second_key_is_b() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("abc", "nested command");
if let KeyNode::Branch { children, .. } = &keymap.bindings()[0].node {
assert_eq!(children[0].key, CrosstermKey::Char('b'));
} else {
panic!("expected branch node at 'a'");
}
}
#[test]
fn nested_branches_third_key_is_c() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("abc", "nested command");
if let KeyNode::Branch { children, .. } = &keymap.bindings()[0].node {
if let KeyNode::Branch { children, .. } = &children[0].node {
assert_eq!(children[0].key, CrosstermKey::Char('c'));
} else {
panic!("expected branch node at 'b'");
}
} else {
panic!("expected branch node at 'a'");
}
}
#[test]
fn nested_branches_third_level_is_leaf() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("abc", "nested command");
if let KeyNode::Branch { children, .. } = &keymap.bindings()[0].node {
if let KeyNode::Branch { children, .. } = &children[0].node {
if let KeyNode::Branch { children, .. } = &children[0].node {
assert!(children.is_empty());
} else {
panic!("expected branch node at 'c'");
}
} else {
panic!("expected branch node at 'b'");
}
} else {
panic!("expected branch node at 'a'");
}
}
#[test]
fn describe_prefix_and_bind_work_together() {
let mut keymap: Keymap<CrosstermKey, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("g", "go commands").bind(
"gg",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
let child = &keymap.bindings()[0];
assert_eq!(child.key, CrosstermKey::Char('g'));
if let KeyNode::Branch {
description,
children,
} = &child.node
{
assert_eq!(*description, "go commands");
assert_eq!(children.len(), 1);
assert_eq!(children[0].key, CrosstermKey::Char('g'));
} else {
panic!("expected branch node");
}
}
#[test]
fn describe_prefix_empty_string_does_nothing() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.describe_group("", "empty");
assert!(keymap.bindings().is_empty());
}
#[test]
fn scope_groups_binds_first_key_count_is_one() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("q", TestAction::Quit, TestCategory::General);
});
assert_eq!(keymap.bindings().len(), 1);
}
#[test]
fn scope_groups_binds_first_key_node_exists() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("q", TestAction::Quit, TestCategory::General);
});
let node = keymap.get_node_at_path(&[CrosstermKey::Char('q')]);
assert!(node.is_some());
}
#[test]
fn scope_groups_binds_first_key_entry_count_is_one() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("q", TestAction::Quit, TestCategory::General);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('q')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries.len(), 1);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_first_key_scope_is_global() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("q", TestAction::Quit, TestCategory::General);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('q')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries[0].scope, TestScope::Global);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_first_key_action_is_quit() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("q", TestAction::Quit, TestCategory::General);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('q')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries[0].action, TestAction::Quit);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_second_key_count_is_one() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("w", TestAction::Save, TestCategory::General);
});
assert_eq!(keymap.bindings().len(), 1);
}
#[test]
fn scope_groups_binds_second_key_node_exists() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("w", TestAction::Save, TestCategory::General);
});
let node = keymap.get_node_at_path(&[CrosstermKey::Char('w')]);
assert!(node.is_some());
}
#[test]
fn scope_groups_binds_second_key_entry_count_is_one() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("w", TestAction::Save, TestCategory::General);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('w')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries.len(), 1);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_second_key_scope_is_global() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("w", TestAction::Save, TestCategory::General);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('w')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries[0].scope, TestScope::Global);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_second_key_action_is_save() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("w", TestAction::Save, TestCategory::General);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('w')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries[0].action, TestAction::Save);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_third_key_count_is_one() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("h", TestAction::Open, TestCategory::Navigation);
});
assert_eq!(keymap.bindings().len(), 1);
}
#[test]
fn scope_groups_binds_third_key_node_exists() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("h", TestAction::Open, TestCategory::Navigation);
});
let node = keymap.get_node_at_path(&[CrosstermKey::Char('h')]);
assert!(node.is_some());
}
#[test]
fn scope_groups_binds_third_key_entry_count_is_one() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("h", TestAction::Open, TestCategory::Navigation);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('h')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries.len(), 1);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_third_key_scope_is_global() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("h", TestAction::Open, TestCategory::Navigation);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('h')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries[0].scope, TestScope::Global);
} else {
panic!("expected leaf node");
}
}
#[test]
fn scope_groups_binds_third_key_action_is_open() {
let mut keymap = Keymap::<CrosstermKey, TestScope, TestAction, TestCategory>::new();
keymap.scope(TestScope::Global, |b| {
b.bind("h", TestAction::Open, TestCategory::Navigation);
});
let node = keymap
.get_node_at_path(&[CrosstermKey::Char('h')])
.expect("node exists");
if let KeyNode::Leaf(entries) = node {
assert_eq!(entries[0].action, TestAction::Open);
} else {
panic!("expected leaf node");
}
}
}