use std::ops::{Deref, DerefMut};
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use rustc_hash::FxHashMap;
use serde::{
de::{Deserializer, Error as DeError},
Deserialize,
};
use crate::event::UserEvent;
const DEFAULT_KEY_BIND: &str = include_str!("../assets/default-keybind.toml");
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct KeyBind(FxHashMap<KeyEvent, UserEvent>);
impl Deref for KeyBind {
type Target = FxHashMap<KeyEvent, UserEvent>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for KeyBind {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl KeyBind {
pub fn new() -> Self {
toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct")
}
pub fn keys_for_event(&self, user_event: UserEvent) -> Vec<String> {
let mut key_events: Vec<KeyEvent> = self
.iter()
.filter(|(_, ue)| **ue == user_event)
.map(|(ke, _)| *ke)
.collect();
key_events.sort_by(|a, b| a.partial_cmp(b).unwrap());
key_events.into_iter().map(key_event_to_string).collect()
}
}
impl<'de> Deserialize<'de> for KeyBind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let parsed_map = FxHashMap::<UserEvent, Vec<String>>::deserialize(deserializer)?;
let mut key_map = FxHashMap::<KeyEvent, UserEvent>::default();
for (user_event, key_events) in parsed_map {
for key_event_str in key_events {
let key_event = match parse_key_event(&key_event_str) {
Ok(e) => e,
Err(s) => {
let msg = format!("{key_event_str:?} is not a valid key event: {s:}");
return Err(DeError::custom(msg));
}
};
if let Some(conflict_user_event) = key_map.insert(key_event, user_event) {
let msg = format!(
"{key_event:?} map to multiple events: {user_event:?}, {conflict_user_event:?}"
);
return Err(DeError::custom(msg));
}
}
}
Ok(KeyBind(key_map))
}
}
fn parse_key_event(raw: &str) -> Result<KeyEvent, String> {
let raw_lower = raw.to_ascii_lowercase().replace(' ', "");
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,
};
}
(current, modifiers)
}
fn parse_key_code_with_modifiers(
raw: &str,
mut modifiers: KeyModifiers,
) -> Result<KeyEvent, String> {
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))
}
fn key_event_to_string(key_event: KeyEvent) -> String {
if let KeyCode::Char(c) = key_event.code {
if key_event.modifiers == KeyModifiers::SHIFT {
return c.to_ascii_uppercase().into();
}
}
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(n) => {
char = format!("F{n}");
&char
}
KeyCode::Char(' ') => "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 = Vec::with_capacity(3);
if key_event.modifiers.intersects(KeyModifiers::CONTROL) {
modifiers_vec.push("Ctrl");
}
if key_event.modifiers.intersects(KeyModifiers::SHIFT) {
modifiers_vec.push("Shift");
}
if key_event.modifiers.intersects(KeyModifiers::ALT) {
modifiers_vec.push("Alt");
}
let mut key = modifiers_vec.join("-");
if !key.is_empty() {
key.push('-');
}
key.push_str(key_code);
key
}