use crate::{parse_key_sequence, Key, Keymap};
#[derive(Debug)]
pub struct GroupBuilder<'a, K: Key, S, A, C> {
keymap: &'a mut Keymap<K, S, A, C>,
prefix: Vec<K>,
}
impl<'a, K: Key, S, A, C> GroupBuilder<'a, K, S, A, C> {
pub(super) fn new(keymap: &'a mut Keymap<K, S, A, C>, prefix: Vec<K>) -> Self {
Self { keymap, prefix }
}
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, self.keymap.leader_key());
if keys.is_empty() {
return self;
}
let full_sequence: Vec<K> = self.prefix.iter().cloned().chain(keys).collect();
self.keymap
.insert_into_tree(&full_sequence, action, category, scope);
self
}
pub fn describe<F>(&mut self, prefix: &str, description: &'static str, bindings: F)
where
F: for<'b> FnOnce(&mut GroupBuilder<'b, K, S, A, C>),
K: Clone,
S: Clone,
A: Clone,
C: Clone,
{
let keys = parse_key_sequence(prefix, self.keymap.leader_key());
if keys.is_empty() {
return;
}
let full_prefix: Vec<K> = self.prefix.iter().cloned().chain(keys).collect();
self.keymap
.ensure_branch_with_description(&full_prefix, description);
let mut builder = GroupBuilder::new(self.keymap, full_prefix);
bindings(&mut builder);
}
pub fn describe_prefix(&mut self, prefix: &str, description: &'static str) -> &mut Self
where
K: Clone,
S: Clone,
A: Clone,
C: Clone,
{
let keys = parse_key_sequence(prefix, self.keymap.leader_key());
if keys.is_empty() {
return self;
}
let full_prefix: Vec<K> = self.prefix.iter().cloned().chain(keys).collect();
self.keymap
.ensure_branch_with_description(&full_prefix, description);
self
}
}
#[cfg(test)]
mod tests {
#![allow(dead_code)]
use super::*;
use crate::test_utils::{TestAction, TestCategory, TestScope};
use crate::KeyNode;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[test]
fn bind_combines_prefix_with_sequence() {
let mut keymap: Keymap<KeyEvent, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("g", "goto");
let mut builder = GroupBuilder::new(
&mut keymap,
vec![KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty())],
);
builder.bind(
"h",
TestAction::Quit,
TestCategory::Navigation,
TestScope::Global,
);
let node = keymap.get_node_at_path(&[
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty()),
]);
assert!(node.is_some());
if let Some(KeyNode::Leaf(entries)) = node {
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].action, TestAction::Quit);
assert_eq!(entries[0].description, TestAction::Quit.to_string());
} else {
panic!("Expected leaf node with Quit action");
}
}
#[test]
fn describe_prefix_sets_nested_description() {
let mut keymap: Keymap<KeyEvent, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("g", "goto");
let mut builder = GroupBuilder::new(
&mut keymap,
vec![KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty())],
);
builder.describe_prefix("c", "git commits");
let node = keymap.get_node_at_path(&[
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::empty()),
]);
assert!(node.is_some());
if let Some(KeyNode::Branch { description, .. }) = node {
assert_eq!(*description, "git commits");
} else {
panic!("Expected branch node with description");
}
}
#[test]
fn describe_creates_nested_group() {
let mut keymap: Keymap<KeyEvent, TestScope, TestAction, TestCategory> = Keymap::new();
keymap.describe_group("g", "goto");
let mut builder = GroupBuilder::new(
&mut keymap,
vec![KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty())],
);
builder.describe("c", "git commands", |nested| {
nested.bind(
"l",
TestAction::Open,
TestCategory::General,
TestScope::Global,
);
nested.bind(
"s",
TestAction::Save,
TestCategory::General,
TestScope::Global,
);
});
let branch_node = keymap.get_node_at_path(&[
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::empty()),
]);
assert!(branch_node.is_some());
if let Some(KeyNode::Branch { description, .. }) = branch_node {
assert_eq!(*description, "git commands");
} else {
panic!("Expected branch node with description");
}
let leaf_l = keymap.get_node_at_path(&[
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::empty()),
KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty()),
]);
assert!(leaf_l.is_some());
let leaf_s = keymap.get_node_at_path(&[
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::empty()),
KeyEvent::new(KeyCode::Char('s'), KeyModifiers::empty()),
]);
assert!(leaf_s.is_some());
}
}