use std::collections::HashMap;
use super::action::Action;
use super::bindings::Verb;
use super::pending::Key;
use crate::config::keys::parse_seq;
use crate::config::schema::KeysConfig;
pub type KeySeq = Vec<Key>;
#[derive(Default)]
pub struct Keymap {
normal: HashMap<KeySeq, Verb>,
insert: HashMap<KeySeq, Verb>,
visual: HashMap<KeySeq, Verb>,
}
pub enum Lookup<'a> {
Found(&'a Verb),
Prefix,
None,
}
impl Keymap {
pub fn from_config(cfg: &KeysConfig, leader: &str) -> (Keymap, Vec<String>) {
let mut warnings = Vec::new();
let mut table = |src: &HashMap<String, String>, mode: &str| -> HashMap<KeySeq, Verb> {
let mut map = HashMap::new();
let mut written: Vec<(KeySeq, &str)> = Vec::new();
for (keys, action) in src {
let seq = match parse_seq(keys, leader) {
Ok(s) => s,
Err(e) => {
warnings.push(format!("key {keys:?}: {e}"));
continue;
}
};
match Verb::from_config_name(action) {
Some(verb) => {
if verb != Verb::Act(Action::Nop) {
written.push((seq.clone(), keys));
}
map.insert(seq, verb);
}
None => warnings.push(format!("key {keys:?}: unknown action {action:?}")),
}
}
for (seq, raw) in &written {
if let Some((_, longer)) = written
.iter()
.find(|(k, _)| k.len() > seq.len() && k[..seq.len()] == seq[..])
{
warnings.push(format!(
"keys.{mode}: {raw:?} fires on its own, so {longer:?} can never run"
));
}
}
map
};
let keymap = Keymap {
normal: table(&cfg.normal, "normal"),
insert: table(&cfg.insert, "insert"),
visual: table(&cfg.visual, "visual"),
};
(keymap, warnings)
}
pub fn insert_key(&self, key: Key) -> Option<&Verb> {
self.insert.get(std::slice::from_ref(&key))
}
fn table(&self, name: &str) -> &HashMap<KeySeq, Verb> {
match name {
"insert" => &self.insert,
"visual" => &self.visual,
_ => &self.normal,
}
}
pub fn lookup(&self, table: &str, seq: &[Key]) -> Lookup<'_> {
let map = self.table(table);
if let Some(action) = map.get(seq) {
return Lookup::Found(action);
}
if map.keys().any(|k| k.len() > seq.len() && &k[..seq.len()] == seq) {
return Lookup::Prefix;
}
Lookup::None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn keys(pairs: &[(&str, &str)]) -> KeysConfig {
KeysConfig {
normal: pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
..KeysConfig::default()
}
}
#[test]
fn a_shadowing_binding_is_reported() {
let (_, warnings) = Keymap::from_config(
&keys(&[("<leader>f", "save"), ("<leader>ff", "find_file")]),
" ",
);
assert!(
warnings.iter().any(|w| w.contains("can never run")),
"expected a shadowing warning, got {warnings:?}"
);
}
#[test]
fn an_unbound_binding_is_not_shadowed() {
let (_, warnings) = Keymap::from_config(
&keys(&[("<leader>f", "file_tree"), ("<leader>fe", "none")]),
" ",
);
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
}
#[test]
fn siblings_under_a_prefix_are_fine() {
let (_, warnings) = Keymap::from_config(
&keys(&[("<leader>ff", "find_file"), ("<leader>fb", "find_buffer")]),
" ",
);
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
}
#[test]
fn the_default_bindings_are_clean() {
let cfg = crate::config::Config::default();
let (_, warnings) = Keymap::from_config(&cfg.keys, &cfg.input.leader);
assert!(warnings.is_empty(), "default keys warn: {warnings:?}");
}
}