use super::action::Action;
use super::key_input::KeyInput;
use hashbrown::HashMap;
#[derive(Debug, Clone)]
pub struct KeyMap {
bindings: HashMap<KeyInput, Action>,
}
impl KeyMap {
pub fn empty() -> Self {
Self {
bindings: HashMap::new(),
}
}
pub fn bind(&mut self, key: KeyInput, action: Action) {
self.bindings.insert(key, action);
}
pub fn unbind(&mut self, key: &KeyInput) {
self.bindings.remove(key);
}
pub fn get(&self, key: &KeyInput) -> Option<Action> {
self.bindings.get(key).copied()
}
pub fn is_bound(&self, key: &KeyInput) -> bool {
self.bindings.contains_key(key)
}
pub fn bindings(&self) -> impl Iterator<Item = (&KeyInput, &Action)> {
self.bindings.iter()
}
pub fn len(&self) -> usize {
self.bindings.len()
}
pub fn is_empty(&self) -> bool {
self.bindings.is_empty()
}
pub fn keys_for(&self, action: Action) -> Vec<&KeyInput> {
self.bindings
.iter()
.filter(|(_, &a)| a == action)
.map(|(k, _)| k)
.collect()
}
}
impl Default for KeyMap {
fn default() -> Self {
let mut map = Self::empty();
map.bind(KeyInput::Up, Action::MoveUp);
map.bind(KeyInput::Down, Action::MoveDown);
map.bind(KeyInput::Char('k'), Action::MoveUp);
map.bind(KeyInput::Char('j'), Action::MoveDown);
map.bind(KeyInput::Char('g'), Action::MoveToTop);
map.bind(KeyInput::Shift('G'), Action::MoveToBottom);
map.bind(KeyInput::PageUp, Action::PageUp);
map.bind(KeyInput::PageDown, Action::PageDown);
map.bind(KeyInput::Enter, Action::Enter);
map.bind(KeyInput::Right, Action::Enter);
map.bind(KeyInput::Backspace, Action::GoParent);
map.bind(KeyInput::Left, Action::GoParent);
map.bind(KeyInput::Char('-'), Action::GoBack);
map.bind(KeyInput::Shift('_'), Action::GoForward);
map.bind(KeyInput::Char(' '), Action::ToggleSelect);
map.bind(KeyInput::Ctrl('a'), Action::SelectAll);
map.bind(KeyInput::Escape, Action::ClearSelection);
map.bind(KeyInput::ShiftUp, Action::MoveUpExtend);
map.bind(KeyInput::ShiftDown, Action::MoveDownExtend);
map.bind(KeyInput::Ctrl('x'), Action::Cut);
map.bind(KeyInput::Ctrl('c'), Action::Copy);
map.bind(KeyInput::Char('d'), Action::Delete);
map.bind(KeyInput::Char('r'), Action::Rename);
map.bind(KeyInput::Char('n'), Action::CreateDir);
map.bind(KeyInput::Shift('N'), Action::CreateFile);
map.bind(KeyInput::Char('.'), Action::ToggleHidden);
map.bind(KeyInput::Char('s'), Action::CycleSort);
map.bind(KeyInput::Shift('R'), Action::Refresh);
map.bind(KeyInput::Char('/'), Action::StartFilter);
map.bind(KeyInput::Char(':'), Action::StartPathInput);
map
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_keymap() {
let map = KeyMap::empty();
assert!(map.is_empty());
assert_eq!(map.get(&KeyInput::Up), None);
}
#[test]
fn test_bind_and_get() {
let mut map = KeyMap::empty();
map.bind(KeyInput::Up, Action::MoveUp);
assert_eq!(map.get(&KeyInput::Up), Some(Action::MoveUp));
}
#[test]
fn test_unbind() {
let mut map = KeyMap::default();
assert!(map.is_bound(&KeyInput::Up));
map.unbind(&KeyInput::Up);
assert!(!map.is_bound(&KeyInput::Up));
}
#[test]
fn test_default_has_vim_bindings() {
let map = KeyMap::default();
assert_eq!(map.get(&KeyInput::Char('j')), Some(Action::MoveDown));
assert_eq!(map.get(&KeyInput::Char('k')), Some(Action::MoveUp));
assert_eq!(map.get(&KeyInput::Char('g')), Some(Action::MoveToTop));
}
#[test]
fn test_keys_for_action() {
let map = KeyMap::default();
let up_keys = map.keys_for(Action::MoveUp);
assert!(up_keys.contains(&&KeyInput::Up));
assert!(up_keys.contains(&&KeyInput::Char('k')));
}
#[test]
fn test_replace_binding() {
let mut map = KeyMap::empty();
map.bind(KeyInput::Char('x'), Action::Delete);
map.bind(KeyInput::Char('x'), Action::Refresh);
assert_eq!(map.get(&KeyInput::Char('x')), Some(Action::Refresh));
}
}