use std::collections::HashMap;
use super::actions::Action;
use super::conditions::{ConditionExpr, EvalContext};
use super::key::{KeyInput, SingleKey};
#[derive(Debug, Clone)]
pub struct KeyRule {
pub condition: Option<ConditionExpr>,
pub action: Action,
}
#[derive(Debug, Clone)]
pub struct KeyBinding {
pub rules: Vec<KeyRule>,
}
#[derive(Debug, Clone)]
pub struct Keymap {
pub bindings: HashMap<KeyInput, KeyBinding>,
}
impl KeyRule {
pub fn always(action: Action) -> Self {
KeyRule {
condition: None,
action,
}
}
pub fn when(condition: impl Into<ConditionExpr>, action: Action) -> Self {
KeyRule {
condition: Some(condition.into()),
action,
}
}
}
impl KeyBinding {
pub fn simple(action: Action) -> Self {
KeyBinding {
rules: vec![KeyRule::always(action)],
}
}
pub fn conditional(rules: Vec<KeyRule>) -> Self {
KeyBinding { rules }
}
}
impl Keymap {
pub fn new() -> Self {
Keymap {
bindings: HashMap::new(),
}
}
pub fn bind(&mut self, key: KeyInput, action: Action) {
self.bindings.insert(key, KeyBinding::simple(action));
}
pub fn bind_conditional(&mut self, key: KeyInput, rules: Vec<KeyRule>) {
self.bindings.insert(key, KeyBinding::conditional(rules));
}
pub fn resolve(&self, key: &KeyInput, ctx: &EvalContext) -> Option<Action> {
let binding = self.bindings.get(key)?;
for rule in &binding.rules {
match &rule.condition {
None => return Some(rule.action.clone()),
Some(cond) if cond.evaluate(ctx) => return Some(rule.action.clone()),
Some(_) => {}
}
}
None
}
pub fn has_sequence_starting_with(&self, prefix: &SingleKey) -> bool {
self.bindings.keys().any(|ki| match ki {
KeyInput::Sequence(keys) => keys.first() == Some(prefix),
KeyInput::Single(_) => false,
})
}
#[allow(dead_code)]
pub fn merge(&mut self, other: &Keymap) {
for (key, binding) in &other.bindings {
self.bindings.insert(key.clone(), binding.clone());
}
}
}
impl Default for Keymap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::super::conditions::ConditionAtom;
use super::*;
fn make_ctx(cursor: usize, width: usize, selected: usize, len: usize) -> EvalContext {
EvalContext {
cursor_position: cursor,
input_width: width,
input_byte_len: width,
selected_index: selected,
results_len: len,
original_input_empty: false,
has_context: false,
}
}
#[test]
fn simple_binding_resolves() {
let mut keymap = Keymap::new();
let key = KeyInput::parse("ctrl-c").unwrap();
keymap.bind(key.clone(), Action::ReturnOriginal);
let ctx = make_ctx(0, 0, 0, 10);
assert_eq!(keymap.resolve(&key, &ctx), Some(Action::ReturnOriginal));
}
#[test]
fn conditional_first_match_wins() {
let mut keymap = Keymap::new();
let key = KeyInput::parse("left").unwrap();
keymap.bind_conditional(
key.clone(),
vec![
KeyRule::when(ConditionAtom::CursorAtStart, Action::Exit),
KeyRule::always(Action::CursorLeft),
],
);
let ctx = make_ctx(0, 5, 0, 10);
assert_eq!(keymap.resolve(&key, &ctx), Some(Action::Exit));
let ctx = make_ctx(3, 5, 0, 10);
assert_eq!(keymap.resolve(&key, &ctx), Some(Action::CursorLeft));
}
#[test]
fn no_match_returns_none() {
let keymap = Keymap::new();
let key = KeyInput::parse("ctrl-c").unwrap();
let ctx = make_ctx(0, 0, 0, 0);
assert_eq!(keymap.resolve(&key, &ctx), None);
}
#[test]
fn conditional_no_condition_matches_returns_none() {
let mut keymap = Keymap::new();
let key = KeyInput::parse("left").unwrap();
keymap.bind_conditional(
key.clone(),
vec![KeyRule::when(ConditionAtom::CursorAtStart, Action::Exit)],
);
let ctx = make_ctx(3, 5, 0, 10);
assert_eq!(keymap.resolve(&key, &ctx), None);
}
#[test]
fn has_sequence_starting_with() {
let mut keymap = Keymap::new();
let seq = KeyInput::parse("g g").unwrap();
keymap.bind(seq, Action::ScrollToTop);
let g = SingleKey::parse("g").unwrap();
assert!(keymap.has_sequence_starting_with(&g));
let h = SingleKey::parse("h").unwrap();
assert!(!keymap.has_sequence_starting_with(&h));
}
#[test]
fn merge_overrides() {
let mut base = Keymap::new();
let key = KeyInput::parse("ctrl-c").unwrap();
base.bind(key.clone(), Action::ReturnOriginal);
let mut overlay = Keymap::new();
overlay.bind(key.clone(), Action::Exit);
base.merge(&overlay);
let ctx = make_ctx(0, 0, 0, 0);
assert_eq!(base.resolve(&key, &ctx), Some(Action::Exit));
}
#[test]
fn merge_preserves_unoverridden() {
let mut base = Keymap::new();
let key1 = KeyInput::parse("ctrl-c").unwrap();
let key2 = KeyInput::parse("ctrl-d").unwrap();
base.bind(key1.clone(), Action::ReturnOriginal);
base.bind(key2.clone(), Action::DeleteCharAfter);
let mut overlay = Keymap::new();
overlay.bind(key1.clone(), Action::Exit);
base.merge(&overlay);
let ctx = make_ctx(0, 0, 0, 0);
assert_eq!(base.resolve(&key1, &ctx), Some(Action::Exit));
assert_eq!(base.resolve(&key2, &ctx), Some(Action::DeleteCharAfter));
}
}