use crate::input::KeyChord;
use crossterm::event::{KeyCode, KeyModifiers};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseKeyError {
Empty,
UnknownKey(String),
}
impl fmt::Display for ParseKeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseKeyError::Empty => write!(f, "empty key binding"),
ParseKeyError::UnknownKey(token) => write!(f, "unrecognised key token: {token:?}"),
}
}
}
impl std::error::Error for ParseKeyError {}
pub fn parse_binding(binding: &str) -> Vec<KeyChord> {
binding.split_whitespace().filter_map(parse_key).collect()
}
pub fn parse_key(token: &str) -> Option<KeyChord> {
try_parse_key(token).ok()
}
pub fn try_parse_binding(binding: &str) -> Result<Vec<KeyChord>, ParseKeyError> {
let chords = binding
.split_whitespace()
.map(try_parse_key)
.collect::<Result<Vec<_>, _>>()?;
if chords.is_empty() {
return Err(ParseKeyError::Empty);
}
Ok(chords)
}
pub fn try_parse_key(token: &str) -> Result<KeyChord, ParseKeyError> {
let mut modifiers = KeyModifiers::empty();
let mut key = token.trim();
if key.is_empty() {
return Err(ParseKeyError::Empty);
}
loop {
let Some((prefix, rest)) = key.split_once('+') else {
break;
};
match prefix.to_ascii_lowercase().as_str() {
"ctrl" | "control" | "c" => modifiers |= KeyModifiers::CONTROL,
"alt" | "meta" | "m" => modifiers |= KeyModifiers::ALT,
"shift" | "s" => modifiers |= KeyModifiers::SHIFT,
_ => break,
}
key = rest;
}
let unknown = || ParseKeyError::UnknownKey(token.to_string());
let code = match key.to_ascii_lowercase().as_str() {
"enter" | "return" => KeyCode::Enter,
"tab" => KeyCode::Tab,
"backtab" => KeyCode::BackTab,
"esc" | "escape" => KeyCode::Esc,
"backspace" | "bs" => KeyCode::Backspace,
"space" => KeyCode::Char(' '),
"up" => KeyCode::Up,
"down" => KeyCode::Down,
"left" => KeyCode::Left,
"right" => KeyCode::Right,
"home" => KeyCode::Home,
"end" => KeyCode::End,
"pageup" | "page_up" => KeyCode::PageUp,
"pagedown" | "page_down" => KeyCode::PageDown,
"delete" | "del" => KeyCode::Delete,
"insert" | "ins" => KeyCode::Insert,
text if text.starts_with('f') && text.len() > 1 => {
let number = text[1..].parse().map_err(|_| unknown())?;
KeyCode::F(number)
}
text => {
let mut chars = text.chars();
let first = chars.next().ok_or_else(unknown)?;
if chars.next().is_some() {
return Err(unknown());
}
KeyCode::Char(first)
}
};
let (code, modifiers) = if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
(KeyCode::BackTab, modifiers - KeyModifiers::SHIFT)
} else {
(code, modifiers)
};
Ok(KeyChord::new(code, modifiers))
}