#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyBinding {
pub key: KeyType,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyType {
Char(char),
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
Backspace,
Delete,
Enter,
Tab,
BackTab,
Escape,
Space,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
}
impl Modifiers {
pub const NONE: Self = Self {
ctrl: false,
alt: false,
shift: false,
};
pub const CTRL: Self = Self {
ctrl: true,
alt: false,
shift: false,
};
pub const ALT: Self = Self {
ctrl: false,
alt: true,
shift: false,
};
pub const SHIFT: Self = Self {
ctrl: false,
alt: false,
shift: true,
};
pub const CTRL_SHIFT: Self = Self {
ctrl: true,
alt: false,
shift: true,
};
}
impl KeyBinding {
pub fn new(key: KeyType, modifiers: Modifiers) -> Self {
Self { key, modifiers }
}
pub fn char(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::NONE)
}
pub fn ctrl(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::CTRL)
}
pub fn alt(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::ALT)
}
pub fn special(key: KeyType) -> Self {
Self::new(key, Modifiers::NONE)
}
pub fn ctrl_special(key: KeyType) -> Self {
Self::new(key, Modifiers::CTRL)
}
pub fn matches(&self, input: &str, key: &crate::hooks::Key) -> bool {
if self.modifiers.ctrl != key.ctrl || self.modifiers.alt != key.alt {
return false;
}
if !matches!(self.key, KeyType::BackTab) && self.modifiers.shift != key.shift {
return false;
}
match &self.key {
KeyType::Char(c) => input.len() == 1 && input.starts_with(*c),
KeyType::Up => key.up_arrow,
KeyType::Down => key.down_arrow,
KeyType::Left => key.left_arrow,
KeyType::Right => key.right_arrow,
KeyType::Home => key.home,
KeyType::End => key.end,
KeyType::PageUp => key.page_up,
KeyType::PageDown => key.page_down,
KeyType::Backspace => key.backspace,
KeyType::Delete => key.delete,
KeyType::Enter => key.return_key,
KeyType::Tab => key.tab && !key.back_tab,
KeyType::BackTab => key.back_tab || (key.tab && key.shift),
KeyType::Escape => key.escape,
KeyType::Space => key.space,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hooks::Key;
#[test]
fn test_tab_and_back_tab_bindings_are_distinct() {
let tab = Key {
tab: true,
..Key::default()
};
let back_tab = Key {
back_tab: true,
shift: true,
..Key::default()
};
let legacy_back_tab = Key {
tab: true,
shift: true,
..Key::default()
};
assert!(KeyBinding::special(KeyType::Tab).matches("", &tab));
assert!(!KeyBinding::special(KeyType::Tab).matches("", &back_tab));
assert!(KeyBinding::special(KeyType::BackTab).matches("", &back_tab));
assert!(KeyBinding::special(KeyType::BackTab).matches("", &legacy_back_tab));
}
}