use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)]
pub enum Key {
Char(char),
Backspace,
Enter,
Left,
Right,
Up,
Down,
Tab,
Delete,
Home,
End,
Esc,
Copy,
Cut,
Paste,
#[default]
Null,
}
#[derive(Debug, Clone, Default, PartialEq, Hash, Eq)]
pub struct Input {
pub key: Key,
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
}
impl Input {
pub fn maybe_char(&self) -> Option<char> {
match self {
Input {
key: Key::Char(c), ..
} => Some(*c),
_ => None,
}
}
#[inline]
pub fn is_newline_except_enter(&self) -> bool {
matches!(
self,
Input {
key: Key::Char('\n' | '\r'),
ctrl: false,
alt: false,
..
} | Input {
key: Key::Enter,
ctrl: true,
..
} | Input {
key: Key::Enter,
alt: true,
..
} | Input {
key: Key::Enter,
shift: true,
..
}
)
}
#[inline]
pub fn is_newline(&self) -> bool {
self.is_newline_except_enter() || self.key == Key::Enter
}
#[inline]
pub fn is_char(&self) -> bool {
matches!(
self,
Input {
key: Key::Char(_),
ctrl: false,
alt: false,
..
}
)
}
#[inline]
pub fn is_char_raw(&self) -> bool {
matches!(
self,
Input {
key: Key::Char(_),
..
}
)
}
#[inline]
pub fn is_tab(&self) -> bool {
self.key == Key::Tab && !self.ctrl && !self.alt
}
#[inline]
pub fn is_backspace(&self) -> bool {
self.key == Key::Backspace && !self.ctrl && !self.alt
}
#[inline]
pub fn is_delete(&self) -> bool {
self.key == Key::Delete && !self.ctrl && !self.alt
}
#[inline]
pub fn is_down(&self) -> bool {
self.key == Key::Down && !self.ctrl && !self.alt
}
#[inline]
pub fn is_up(&self) -> bool {
self.key == Key::Up && !self.ctrl && !self.alt
}
#[inline]
pub fn is_left(&self) -> bool {
self.key == Key::Left && !self.ctrl && !self.alt
}
#[inline]
pub fn is_right(&self) -> bool {
self.key == Key::Right && !self.ctrl && !self.alt
}
#[inline]
pub fn is_home(&self) -> bool {
self.key == Key::Home
}
#[inline]
pub fn is_end(&self) -> bool {
self.key == Key::End
}
#[inline]
pub fn is_ctrl_left(&self) -> bool {
self.key == Key::Left && self.ctrl && !self.alt
}
#[inline]
pub fn is_ctrl_right(&self) -> bool {
self.key == Key::Right && self.ctrl && !self.alt
}
pub fn kind(&self) -> &str {
match self {
i if i.is_delete() => ":delete",
i if i.is_backspace() => ":backspace",
i if i.is_tab() => ":tab",
i if i.is_newline_except_enter() => ":non-enter-newline",
i if i.is_newline() => ":newline",
i if i.is_char() => ":char",
i if i.is_down() => ":down",
i if i.is_up() => ":up",
i if i.is_left() => ":left",
i if i.is_right() => ":right",
i if i.is_home() => ":home",
i if i.is_end() => ":end",
i if i.is_ctrl_left() => ":word-left",
i if i.is_ctrl_right() => ":word-right",
i if i.is_char_raw() => ":char",
_ => "",
}
}
}
impl From<Event> for Input {
fn from(event: Event) -> Self {
match event {
Event::Key(key) => Self::from(key),
_ => Self::default(),
}
}
}
impl From<KeyCode> for Key {
fn from(code: KeyCode) -> Self {
match code {
KeyCode::Char(c) => Key::Char(c),
KeyCode::Backspace => Key::Backspace,
KeyCode::Enter => Key::Enter,
KeyCode::Left => Key::Left,
KeyCode::Right => Key::Right,
KeyCode::Up => Key::Up,
KeyCode::Down => Key::Down,
KeyCode::Tab => Key::Tab,
KeyCode::Delete => Key::Delete,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::Esc => Key::Esc,
_ => Key::Null,
}
}
}
impl From<KeyEvent> for Input {
fn from(key: KeyEvent) -> Self {
if key.kind == KeyEventKind::Release {
return Self::default();
}
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
let alt = key.modifiers.contains(KeyModifiers::ALT);
let shift = key.modifiers.contains(KeyModifiers::SHIFT);
let key = Key::from(key.code);
Self {
key,
ctrl,
alt,
shift,
}
}
}
#[cfg(test)]
mod tests {
use {super::*, crossterm::event::KeyEventState};
fn key_event(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}
#[test]
fn key_to_input() {
for (from, to) in [
(
key_event(KeyCode::Char('a'), KeyModifiers::empty()),
input(Key::Char('a'), false, false, false),
),
(
key_event(KeyCode::Enter, KeyModifiers::empty()),
input(Key::Enter, false, false, false),
),
(
key_event(KeyCode::Left, KeyModifiers::CONTROL),
input(Key::Left, true, false, false),
),
(
key_event(KeyCode::Right, KeyModifiers::SHIFT),
input(Key::Right, false, false, true),
),
(
key_event(KeyCode::Home, KeyModifiers::ALT),
input(Key::Home, false, true, false),
),
(
key_event(KeyCode::NumLock, KeyModifiers::CONTROL),
input(Key::Null, true, false, false),
),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
}
#[test]
fn event_to_input() {
for (from, to) in [
(
Event::Key(key_event(KeyCode::Char('a'), KeyModifiers::empty())),
input(Key::Char('a'), false, false, false),
),
(Event::FocusGained, input(Key::Null, false, false, false)),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
}
}
#[test]
fn ignore_key_release_event() {
let mut from = key_event(KeyCode::Char('a'), KeyModifiers::empty());
from.kind = KeyEventKind::Release;
let to = input(Key::Null, false, false, false);
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
#[allow(dead_code)]
pub(crate) fn input(key: Key, ctrl: bool, alt: bool, shift: bool) -> Input {
Input {
key,
ctrl,
alt,
shift,
}
}
}