use std::collections::HashMap;
use super::key::{Key, KeyEvent};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeymapAction {
Submit,
Newline,
Quit,
HistorySearch,
ToggleTheme,
ScrollUp,
ScrollDown,
ExternalEditor,
}
impl KeymapAction {
pub const ALL: &'static [KeymapAction] = &[
KeymapAction::Submit,
KeymapAction::Newline,
KeymapAction::Quit,
KeymapAction::HistorySearch,
KeymapAction::ToggleTheme,
KeymapAction::ScrollUp,
KeymapAction::ScrollDown,
KeymapAction::ExternalEditor,
];
pub fn name(self) -> &'static str {
match self {
KeymapAction::Submit => "submit",
KeymapAction::Newline => "newline",
KeymapAction::Quit => "quit",
KeymapAction::HistorySearch => "history_search",
KeymapAction::ToggleTheme => "toggle_theme",
KeymapAction::ScrollUp => "scroll_up",
KeymapAction::ScrollDown => "scroll_down",
KeymapAction::ExternalEditor => "external_editor",
}
}
pub fn default_key(self) -> KeyEvent {
match self {
KeymapAction::Submit => KeyEvent::plain(Key::Enter),
KeymapAction::Newline => KeyEvent {
key: Key::Enter,
ctrl: false,
alt: true,
shift: false,
},
KeymapAction::Quit => KeyEvent::ctrl(Key::Char('c')),
KeymapAction::HistorySearch => KeyEvent::ctrl(Key::Char('r')),
KeymapAction::ToggleTheme => KeyEvent::ctrl(Key::Char('t')),
KeymapAction::ScrollUp => KeyEvent::plain(Key::PageUp),
KeymapAction::ScrollDown => KeyEvent::plain(Key::PageDown),
KeymapAction::ExternalEditor => KeyEvent::ctrl(Key::Char('e')),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Keymap {
bindings: HashMap<KeymapAction, KeyEvent>,
}
impl Default for Keymap {
fn default() -> Self {
let bindings = KeymapAction::ALL
.iter()
.map(|&a| (a, a.default_key()))
.collect();
Keymap { bindings }
}
}
impl Keymap {
pub fn with_overrides(overrides: &HashMap<String, String>) -> Self {
let mut km = Keymap::default();
for action in KeymapAction::ALL {
if let Some(spec) = overrides.get(action.name()) {
if let Some(key) = KeyEvent::parse(spec) {
km.bindings.insert(*action, key);
}
}
}
km
}
pub fn key_for(&self, action: KeymapAction) -> KeyEvent {
self.bindings
.get(&action)
.copied()
.unwrap_or_else(|| action.default_key())
}
pub fn action_for(&self, key: KeyEvent) -> Option<KeymapAction> {
self.bindings
.iter()
.find(|(_, &bound)| bound == key)
.map(|(&a, _)| a)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_keymap_round_trips_every_action() {
let km = Keymap::default();
for &action in KeymapAction::ALL {
assert_eq!(km.key_for(action), action.default_key());
assert_eq!(km.action_for(action.default_key()), Some(action));
}
}
#[test]
fn override_replaces_one_binding_leaves_others_default() {
let mut overrides = HashMap::new();
overrides.insert("history_search".to_string(), "ctrl+h".to_string());
let km = Keymap::with_overrides(&overrides);
assert_eq!(
km.key_for(KeymapAction::HistorySearch),
KeyEvent::ctrl(Key::Char('h'))
);
assert_eq!(
km.key_for(KeymapAction::Submit),
KeymapAction::Submit.default_key()
);
}
#[test]
fn unknown_action_name_is_ignored() {
let mut overrides = HashMap::new();
overrides.insert("not_a_real_action".to_string(), "ctrl+z".to_string());
let km = Keymap::with_overrides(&overrides);
assert_eq!(km, Keymap::default());
}
#[test]
fn unparseable_key_spec_keeps_default() {
let mut overrides = HashMap::new();
overrides.insert("submit".to_string(), "not a key".to_string());
let km = Keymap::with_overrides(&overrides);
assert_eq!(
km.key_for(KeymapAction::Submit),
KeymapAction::Submit.default_key()
);
}
#[test]
fn action_for_unbound_key_is_none() {
let km = Keymap::default();
assert_eq!(km.action_for(KeyEvent::ch('q')), None);
}
}