use { super::events::Action, crossterm::event::{KeyCode, KeyEvent, KeyModifiers}, eyre::Result, std::{collections::HashMap, str::FromStr}, }; #[derive(Clone, Debug, Default)] pub struct Config { pub keybindings: KeyBindings, } #[derive(Clone, Debug, Default)] pub struct KeyBindings(pub HashMap, Action>); impl KeyBindings { pub fn new(raw: HashMap<&str, String>) -> Self { let keybindings = raw .into_iter() .map(|(key_str, cmd)| { let action = Action::from_str(&cmd); match action { Ok(action) => (parse_key_sequence(&key_str).unwrap(), action), Err(_) => (parse_key_sequence(&key_str).unwrap(), Action::AppAction(cmd)), } }) .collect(); KeyBindings(keybindings) } pub fn get(&self, key_events: &[KeyEvent]) -> Option<&Action> { self.0.get(key_events) } } fn parse_key_event(raw: &str) -> Result { let raw_lower = raw.to_ascii_lowercase(); let (remaining, modifiers) = extract_modifiers(&raw_lower); parse_key_code_with_modifiers(remaining, modifiers) } fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) { let mut modifiers = KeyModifiers::empty(); let mut current = raw; loop { match current { rest if rest.starts_with("ctrl-") => { modifiers.insert(KeyModifiers::CONTROL); current = &rest[5..]; } rest if rest.starts_with("alt-") => { modifiers.insert(KeyModifiers::ALT); current = &rest[4..]; } rest if rest.starts_with("shift-") => { modifiers.insert(KeyModifiers::SHIFT); current = &rest[6..]; } _ => break, // break out of the loop if no known prefix is detected }; } (current, modifiers) } fn parse_key_code_with_modifiers( raw: &str, mut modifiers: KeyModifiers, ) -> Result { let c = match raw { "esc" => KeyCode::Esc, "enter" => KeyCode::Enter, "left" => KeyCode::Left, "right" => KeyCode::Right, "up" => KeyCode::Up, "down" => KeyCode::Down, "home" => KeyCode::Home, "end" => KeyCode::End, "pageup" => KeyCode::PageUp, "pagedown" => KeyCode::PageDown, "backtab" => { modifiers.insert(KeyModifiers::SHIFT); KeyCode::BackTab } "backspace" => KeyCode::Backspace, "delete" => KeyCode::Delete, "insert" => KeyCode::Insert, "f1" => KeyCode::F(1), "f2" => KeyCode::F(2), "f3" => KeyCode::F(3), "f4" => KeyCode::F(4), "f5" => KeyCode::F(5), "f6" => KeyCode::F(6), "f7" => KeyCode::F(7), "f8" => KeyCode::F(8), "f9" => KeyCode::F(9), "f10" => KeyCode::F(10), "f11" => KeyCode::F(11), "f12" => KeyCode::F(12), "space" => KeyCode::Char(' '), "hyphen" => KeyCode::Char('-'), "minus" => KeyCode::Char('-'), "tab" => KeyCode::Tab, c if c.len() == 1 => { let mut c = c.chars().next().unwrap(); if modifiers.contains(KeyModifiers::SHIFT) { c = c.to_ascii_uppercase(); } KeyCode::Char(c) } _ => return Err(format!("Unable to parse {raw}")), }; Ok(KeyEvent::new(c, modifiers)) } pub fn key_event_to_string(key_event: &KeyEvent) -> String { let char; let key_code = match key_event.code { KeyCode::Backspace => "backspace", KeyCode::Enter => "enter", KeyCode::Left => "left", KeyCode::Right => "right", KeyCode::Up => "up", KeyCode::Down => "down", KeyCode::Home => "home", KeyCode::End => "end", KeyCode::PageUp => "pageup", KeyCode::PageDown => "pagedown", KeyCode::Tab => "tab", KeyCode::BackTab => "backtab", KeyCode::Delete => "delete", KeyCode::Insert => "insert", KeyCode::F(c) => { char = format!("f({c})"); &char } KeyCode::Char(c) if c == ' ' => "space", KeyCode::Char(c) => { char = c.to_string(); &char } KeyCode::Esc => "esc", KeyCode::Null => "", KeyCode::CapsLock => "", KeyCode::Menu => "", KeyCode::ScrollLock => "", KeyCode::Media(_) => "", KeyCode::NumLock => "", KeyCode::PrintScreen => "", KeyCode::Pause => "", KeyCode::KeypadBegin => "", KeyCode::Modifier(_) => "", }; let mut modifiers = Vec::with_capacity(3); if key_event.modifiers.intersects(KeyModifiers::CONTROL) { modifiers.push("ctrl"); } if key_event.modifiers.intersects(KeyModifiers::SHIFT) { modifiers.push("shift"); } if key_event.modifiers.intersects(KeyModifiers::ALT) { modifiers.push("alt"); } // if the modifiers is "shift" and the key code is a letter, we just return the letter // otherwise we return the modifiers joined by a dash and the key code if modifiers.len() == 1 && key_code.chars().count() == 1 && key_code.chars().all(char::is_alphabetic) { return key_code.to_string(); } let mut key = modifiers.join("-"); if !key.is_empty() { key.push('-'); } key.push_str(key_code); key } pub fn parse_key_sequence(raw: &str) -> Result, String> { if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() { return Err(format!("Unable to parse `{}`", raw)); } let raw = if !raw.contains("><") { let raw = raw.strip_prefix('<').unwrap_or(raw); let raw = raw.strip_prefix('>').unwrap_or(raw); raw } else { raw }; let sequences = raw .split("><") .map(|seq| { if let Some(s) = seq.strip_prefix('<') { s } else if let Some(s) = seq.strip_suffix('>') { s } else { seq } }) .collect::>(); sequences.into_iter().map(parse_key_event).collect() }