#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeyEvent(pub KeyCode, pub Modifiers);
impl KeyEvent {
pub(crate) const BACKSPACE: Self = Self(KeyCode::Backspace, Modifiers::NONE);
pub(crate) const ENTER: Self = Self(KeyCode::Enter, Modifiers::NONE);
pub(crate) const ESC: Self = Self(KeyCode::Esc, Modifiers::NONE);
#[must_use]
pub fn new(c: char, mut mods: Modifiers) -> Self {
use {KeyCode as K, KeyEvent as E, Modifiers as M};
if !c.is_control() {
if !mods.is_empty() {
mods.remove(M::SHIFT); }
return E(K::Char(c), mods);
}
#[allow(clippy::match_same_arms)]
match c {
'\x00' => E(K::Char('@'), mods | M::CTRL), '\x01' => E(K::Char('A'), mods | M::CTRL),
'\x02' => E(K::Char('B'), mods | M::CTRL),
'\x03' => E(K::Char('C'), mods | M::CTRL),
'\x04' => E(K::Char('D'), mods | M::CTRL),
'\x05' => E(K::Char('E'), mods | M::CTRL),
'\x06' => E(K::Char('F'), mods | M::CTRL),
'\x07' => E(K::Char('G'), mods | M::CTRL), #[cfg(unix)]
'\x08' => E(K::Backspace, mods), #[cfg(windows)]
'\x08' => E(K::Char('H'), mods | M::CTRL),
#[cfg(unix)]
'\x09' => {
if mods.contains(M::SHIFT) {
mods.remove(M::SHIFT);
E(K::BackTab, mods)
} else {
E(K::Tab, mods)
}
}
#[cfg(windows)]
'\x09' => E(K::Char('I'), mods | M::CTRL),
'\x0a' => E(K::Char('J'), mods | M::CTRL), '\x0b' => E(K::Char('K'), mods | M::CTRL),
'\x0c' => E(K::Char('L'), mods | M::CTRL),
#[cfg(unix)]
'\x0d' => E(K::Enter, mods), #[cfg(windows)]
'\x0d' => E(K::Char('M'), mods | M::CTRL),
'\x0e' => E(K::Char('N'), mods | M::CTRL),
'\x0f' => E(K::Char('O'), mods | M::CTRL),
'\x10' => E(K::Char('P'), mods | M::CTRL),
'\x11' => E(K::Char('Q'), mods | M::CTRL),
'\x12' => E(K::Char('R'), mods | M::CTRL),
'\x13' => E(K::Char('S'), mods | M::CTRL),
'\x14' => E(K::Char('T'), mods | M::CTRL),
'\x15' => E(K::Char('U'), mods | M::CTRL),
'\x16' => E(K::Char('V'), mods | M::CTRL),
'\x17' => E(K::Char('W'), mods | M::CTRL),
'\x18' => E(K::Char('X'), mods | M::CTRL),
'\x19' => E(K::Char('Y'), mods | M::CTRL),
'\x1a' => E(K::Char('Z'), mods | M::CTRL),
'\x1b' => E(K::Esc, mods), '\x1c' => E(K::Char('\\'), mods | M::CTRL),
'\x1d' => E(K::Char(']'), mods | M::CTRL),
'\x1e' => E(K::Char('^'), mods | M::CTRL),
'\x1f' => E(K::Char('_'), mods | M::CTRL),
'\x7f' => E(K::Backspace, mods), '\u{9b}' => E(K::Esc, mods | M::SHIFT),
_ => E(K::Null, mods),
}
}
#[must_use]
pub fn ctrl(c: char) -> Self {
Self::new(c, Modifiers::CTRL)
}
#[must_use]
pub fn alt(c: char) -> Self {
Self::new(c, Modifiers::ALT)
}
#[must_use]
pub fn normalize(e: Self) -> Self {
use {KeyCode as K, KeyEvent as E, Modifiers as M};
match e {
E(K::Char(c), m) if c.is_ascii_control() => Self::new(c, m),
E(K::Char(c), m) if c.is_ascii_lowercase() && m.contains(M::CTRL) => {
E(K::Char(c.to_ascii_uppercase()), m)
}
E(K::Char(c), m) if c.is_ascii_uppercase() && m.contains(M::SHIFT) => {
E(K::Char(c), m ^ M::SHIFT)
}
E(K::Tab, m) if m.contains(M::SHIFT) => E(K::BackTab, m ^ M::SHIFT),
_ => e,
}
}
}
impl From<char> for KeyEvent {
fn from(c: char) -> Self {
Self::new(c, Modifiers::NONE)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyCode {
UnknownEscSeq,
Backspace,
BackTab,
BracketedPasteStart,
BracketedPasteEnd,
Char(char),
Delete,
Down,
End,
Enter,
Esc,
F(u8),
Home,
Insert,
Left,
Null,
PageDown,
PageUp,
Right,
Tab,
Up,
}
bitflags::bitflags! {
pub struct Modifiers: u8 {
const CTRL = 1<<3;
const ALT = 1<<2;
const SHIFT = 1<<1;
const NONE = 0;
const CTRL_SHIFT = Self::CTRL.bits | Self::SHIFT.bits;
const ALT_SHIFT = Self::ALT.bits | Self::SHIFT.bits;
const CTRL_ALT = Self::CTRL.bits | Self::ALT.bits;
const CTRL_ALT_SHIFT = Self::CTRL.bits | Self::ALT.bits | Self::SHIFT.bits;
}
}
#[cfg(test)]
mod tests {
use super::{KeyCode as K, KeyEvent as E, Modifiers as M};
#[test]
fn new() {
assert_eq!(E::ESC, E::new('\x1b', M::NONE));
}
#[test]
#[cfg(unix)]
fn from() {
assert_eq!(E(K::Tab, M::NONE), E::from('\t'));
}
#[test]
#[cfg(windows)]
fn from() {
assert_eq!(E(K::Char('I'), M::CTRL), E::from('\t'));
}
#[test]
fn normalize() {
assert_eq!(E::ctrl('A'), E::normalize(E(K::Char('\x01'), M::NONE)));
assert_eq!(E::ctrl('A'), E::normalize(E::ctrl('a')));
assert_eq!(E::from('A'), E::normalize(E(K::Char('A'), M::SHIFT)));
assert_eq!(E(K::BackTab, M::NONE), E::normalize(E(K::Tab, M::SHIFT)));
}
}