use color_eyre::eyre::{Result, WrapErr, eyre};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub fn parse_keysequence(seq: &str) -> Result<Vec<KeyEvent>> {
let mut result = Vec::new();
let mut current_combo = String::new();
let mut in_angle = false;
for char in seq.chars() {
if char == '<' {
in_angle = true;
current_combo.push(char);
} else if char == '>' && in_angle {
current_combo.push(char);
in_angle = false;
let key_event = parse_vim_key(¤t_combo)?;
result.push(key_event);
current_combo.clear();
} else if in_angle {
current_combo.push(char);
} else {
result.push(KeyEvent {
code: KeyCode::Char(char),
modifiers: KeyModifiers::empty(),
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
});
}
}
Ok(result)
}
fn parse_vim_key(key: &str) -> Result<KeyEvent> {
let key = key.trim_matches(|c| c == '<' || c == '>');
let parts: Vec<&str> = key.split('-').collect();
let mut ctrl = false;
let mut alt = false;
let mut shift = false;
let base_key;
if parts.len() > 1 {
for part in &parts[..parts.len() - 1] {
match part.to_uppercase().as_str() {
"C" => ctrl = true,
"M" => alt = true,
"S" => shift = true,
other => {
return Err(eyre!(
"Invalid leadr keymap: <{key}>.\n \
'{other}' is not a recognized modifier (valid modifiers are C, M, S)."
));
}
}
}
base_key = parts[parts.len() - 1].to_string();
} else {
base_key = parts[0].to_string();
}
let code = match base_key {
k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()),
k => match k.to_uppercase().as_str() {
"SPACE" => KeyCode::Char(' '),
"CR" | "ENTER" => KeyCode::Enter,
"TAB" => KeyCode::Tab,
"ESC" => KeyCode::Esc,
"UP" => KeyCode::Up,
"DOWN" => KeyCode::Down,
"LEFT" => KeyCode::Left,
"RIGHT" => KeyCode::Right,
k if k.starts_with('F') => {
let n = k[1..].parse::<u8>().wrap_err_with(|| {
format!("Invalid leadr keymap: <{key}>. '{k}' is not a valid function key.")
})?;
KeyCode::F(n)
}
_ => {
return Err(eyre!(
"Invalid leadr keymap: <{key}>. '{k}' is not a recognized keycode."
));
}
},
};
let mut modifiers = KeyModifiers::empty();
if ctrl {
modifiers |= KeyModifiers::CONTROL;
}
if alt {
modifiers |= KeyModifiers::ALT;
}
if shift {
modifiers |= KeyModifiers::SHIFT;
}
Ok(KeyEvent {
code,
modifiers,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyCode, KeyModifiers};
#[test]
fn test_parse_simple_char() {
let ev = parse_vim_key("x").unwrap();
assert_eq!(ev.code, KeyCode::Char('x'));
assert!(ev.modifiers.is_empty());
}
#[test]
fn test_parse_ctrl_char() {
let ev = parse_vim_key("<C-a>").unwrap();
assert_eq!(ev.code, KeyCode::Char('a'));
assert!(ev.modifiers.contains(KeyModifiers::CONTROL));
}
#[test]
fn test_parse_alt_shift_char() {
let ev = parse_vim_key("<M-S-x>").unwrap();
assert_eq!(ev.code, KeyCode::Char("x".chars().next().unwrap()));
assert!(ev.modifiers.contains(KeyModifiers::ALT));
assert!(ev.modifiers.contains(KeyModifiers::SHIFT));
}
#[test]
fn test_parse_named_keys() {
assert_eq!(parse_vim_key("<Enter>").unwrap().code, KeyCode::Enter);
assert_eq!(parse_vim_key("<Tab>").unwrap().code, KeyCode::Tab);
assert_eq!(parse_vim_key("<Esc>").unwrap().code, KeyCode::Esc);
assert_eq!(parse_vim_key("<Up>").unwrap().code, KeyCode::Up);
assert_eq!(parse_vim_key("<Down>").unwrap().code, KeyCode::Down);
assert_eq!(parse_vim_key("<Left>").unwrap().code, KeyCode::Left);
assert_eq!(parse_vim_key("<Right>").unwrap().code, KeyCode::Right);
}
#[test]
fn test_parse_function_keys() {
assert_eq!(parse_vim_key("<F1>").unwrap().code, KeyCode::F(1));
assert_eq!(parse_vim_key("<F12>").unwrap().code, KeyCode::F(12));
assert!(parse_vim_key("<F13>").is_ok()); }
#[test]
fn test_invalid_modifier() {
let err = parse_vim_key("<Q-x>").unwrap_err();
let msg = format!("{err}"); assert!(
msg.contains("not a recognized modifier"),
"Error message: {msg}"
);
}
#[test]
fn test_invalid_key() {
let err = parse_vim_key("<C-NotAKey>").unwrap_err();
let msg = format!("{err}"); assert!(
msg.contains("not a recognized keycode"),
"Error message: {msg}"
);
}
#[test]
fn test_parse_keysequence_simple() {
let events = parse_keysequence("abc").unwrap();
assert_eq!(events.len(), 3);
assert_eq!(events[0].code, KeyCode::Char('a'));
assert_eq!(events[1].code, KeyCode::Char('b'));
assert_eq!(events[2].code, KeyCode::Char('c'));
}
#[test]
fn test_parse_keysequence_vim_style() {
let events = parse_keysequence("<C-x><M-Enter>").unwrap();
assert_eq!(events.len(), 2);
assert_eq!(events[0].code, KeyCode::Char('x'));
assert!(events[0].modifiers.contains(KeyModifiers::CONTROL));
assert_eq!(events[1].code, KeyCode::Enter);
assert!(events[1].modifiers.contains(KeyModifiers::ALT));
}
}