#[cfg(feature = "crossterm")]
pub(crate) mod crossterm;
mod grammar;
pub use grammar::ParseKeyInputError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Key {
Char(char),
F(u8),
Enter,
Esc,
Tab,
Backspace,
Delete,
Insert,
Home,
End,
PageUp,
PageDown,
Up,
Down,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Modifiers(u8);
impl Modifiers {
pub const NONE: Modifiers = Modifiers(0);
pub const CTRL: Modifiers = Modifiers(0b0001);
pub const ALT: Modifiers = Modifiers(0b0010);
pub const SHIFT: Modifiers = Modifiers(0b0100);
pub const SUPER: Modifiers = Modifiers(0b1000);
#[must_use]
pub const fn empty() -> Self {
Modifiers::NONE
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn contains(self, other: Modifiers) -> bool {
(self.0 & other.0) == other.0
}
#[must_use]
pub const fn union(self, other: Modifiers) -> Self {
Modifiers(self.0 | other.0)
}
#[must_use]
pub const fn difference(self, other: Modifiers) -> Self {
Modifiers(self.0 & !other.0)
}
}
impl core::ops::BitOr for Modifiers {
type Output = Modifiers;
fn bitor(self, rhs: Modifiers) -> Modifiers {
self.union(rhs)
}
}
impl core::ops::BitOrAssign for Modifiers {
fn bitor_assign(&mut self, rhs: Modifiers) {
self.0 |= rhs.0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct KeyInput {
key: Key,
mods: Modifiers,
}
impl KeyInput {
#[must_use]
pub const fn new(key: Key, mods: Modifiers) -> Self {
KeyInput { key, mods }
}
#[must_use]
pub fn normalized(key: Key, mods: Modifiers) -> Self {
let (key, mods) = normalize(key, mods);
KeyInput { key, mods }
}
#[must_use]
pub const fn key(&self) -> Key {
self.key
}
#[must_use]
pub const fn modifiers(&self) -> Modifiers {
self.mods
}
}
#[must_use]
pub(crate) fn normalize(key: Key, mods: Modifiers) -> (Key, Modifiers) {
match key {
Key::Char(_) if mods.difference(Modifiers::SHIFT).is_empty() => (key, Modifiers::NONE),
_ => (key, mods),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn modifiers_union_and_contains() {
let cs = Modifiers::CTRL | Modifiers::SHIFT;
assert!(cs.contains(Modifiers::CTRL));
assert!(cs.contains(Modifiers::SHIFT));
assert!(!cs.contains(Modifiers::ALT));
assert!(cs.contains(Modifiers::CTRL | Modifiers::SHIFT));
}
#[test]
fn modifiers_empty_contains_only_empty() {
assert!(Modifiers::NONE.is_empty());
assert!(Modifiers::NONE.contains(Modifiers::NONE));
assert!(!Modifiers::NONE.contains(Modifiers::CTRL));
assert!(Modifiers::CTRL.contains(Modifiers::NONE));
}
#[test]
fn modifiers_difference_clears_bits() {
let cs = Modifiers::CTRL | Modifiers::SHIFT;
assert_eq!(cs.difference(Modifiers::SHIFT), Modifiers::CTRL);
assert_eq!(cs.difference(Modifiers::ALT), cs);
}
#[test]
fn key_input_is_hash_eq_by_value() {
let a = KeyInput::new(Key::Char('q'), Modifiers::CTRL);
let b = KeyInput::new(Key::Char('q'), Modifiers::CTRL);
let c = KeyInput::new(Key::Char('q'), Modifiers::NONE);
assert_eq!(a, b);
assert_ne!(a, c);
}
}