use tuinix::{KeyCode, KeyInput, MouseEvent};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum InputMatcher {
Key(KeyInput),
Printable,
Mouse(MouseEvent),
}
impl InputMatcher {
pub fn matches(self, input: tuinix::TerminalInput) -> bool {
match input {
tuinix::TerminalInput::Key(key) => match self {
InputMatcher::Key(k) => k == key,
InputMatcher::Printable => {
if let KeyInput {
ctrl: false,
alt: false,
code: KeyCode::Char(ch),
} = key
{
!ch.is_control()
} else {
false
}
}
_ => false,
},
tuinix::TerminalInput::Mouse(m) => {
matches!(self, InputMatcher::Mouse(e) if e == m.event)
}
}
}
}
impl std::str::FromStr for InputMatcher {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mouse = |event| InputMatcher::Mouse(event);
match s {
"<PRINTABLE>" => return Ok(InputMatcher::Printable),
"<LEFTCLICK>" => return Ok(mouse(MouseEvent::LeftPress)),
"<LEFTRELEASE>" => return Ok(mouse(MouseEvent::LeftRelease)),
"<RIGHTCLICK>" => return Ok(mouse(MouseEvent::RightPress)),
"<RIGHTRELEASE>" => return Ok(mouse(MouseEvent::RightRelease)),
"<MIDDLECLICK>" => return Ok(mouse(MouseEvent::MiddlePress)),
"<MIDDLERELEASE>" => return Ok(mouse(MouseEvent::MiddleRelease)),
"<DRAG>" => return Ok(mouse(MouseEvent::Drag)),
"<SCROLLUP>" => return Ok(mouse(MouseEvent::ScrollUp)),
"<SCROLLDOWN>" => return Ok(mouse(MouseEvent::ScrollDown)),
_ => {}
}
let mut alt = false;
let mut ctrl = false;
let mut remaining = s;
loop {
if let Some(rest) = remaining.strip_prefix("M-")
&& !alt
{
remaining = rest;
alt = true;
} else if let Some(rest) = remaining.strip_prefix("C-")
&& !ctrl
{
remaining = rest;
ctrl = true;
} else {
break;
}
}
let key = |code| InputMatcher::Key(KeyInput { ctrl, alt, code });
match remaining {
"<UP>" => return Ok(key(KeyCode::Up)),
"<DOWN>" => return Ok(key(KeyCode::Down)),
"<LEFT>" => return Ok(key(KeyCode::Left)),
"<RIGHT>" => return Ok(key(KeyCode::Right)),
"<ENTER>" => return Ok(key(KeyCode::Enter)),
"<ESCAPE>" => return Ok(key(KeyCode::Escape)),
"<BACKSPACE>" => return Ok(key(KeyCode::Backspace)),
"<TAB>" => return Ok(key(KeyCode::Tab)),
"<BACKTAB>" => return Ok(key(KeyCode::BackTab)),
"<DELETE>" => return Ok(key(KeyCode::Delete)),
"<INSERT>" => return Ok(key(KeyCode::Insert)),
"<HOME>" => return Ok(key(KeyCode::Home)),
"<END>" => return Ok(key(KeyCode::End)),
"<PAGEUP>" => return Ok(key(KeyCode::PageUp)),
"<PAGEDOWN>" => return Ok(key(KeyCode::PageDown)),
_ => {}
}
let mut chars = remaining.chars();
if let Some(ch) = chars.next()
&& chars.next().is_none()
{
let code = KeyCode::Char(ch);
Ok(key(code))
} else if let Some(hex_str) = remaining.strip_prefix("0x") {
match u32::from_str_radix(hex_str, 16) {
Ok(code_point) => {
if let Some(ch) = char::from_u32(code_point) {
let code = KeyCode::Char(ch);
Ok(key(code))
} else {
Err(format!("invalid Unicode code point: 0x{:x}", code_point))
}
}
Err(_) => Err(format!("invalid hex notation: {}", remaining)),
}
} else {
Err(format!("invalid key input format: {s:?}"))
}
}
}
impl<'text, 'raw> TryFrom<nojson::RawJsonValue<'text, 'raw>> for InputMatcher {
type Error = nojson::JsonParseError;
fn try_from(value: nojson::RawJsonValue<'text, 'raw>) -> Result<Self, Self::Error> {
value
.to_unquoted_string_str()?
.parse()
.map_err(|e| value.invalid(e))
}
}
impl std::fmt::Display for InputMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Printable => write!(f, "<PRINTABLE>"),
Self::Key(key) => {
if key.alt {
write!(f, "M-")?;
}
if key.ctrl {
write!(f, "C-")?;
}
match key.code {
KeyCode::Up => write!(f, "<UP>"),
KeyCode::Down => write!(f, "<DOWN>"),
KeyCode::Left => write!(f, "<LEFT>"),
KeyCode::Right => write!(f, "<RIGHT>"),
KeyCode::Enter => write!(f, "<ENTER>"),
KeyCode::Escape => write!(f, "<ESCAPE>"),
KeyCode::Backspace => write!(f, "<BACKSPACE>"),
KeyCode::Tab => write!(f, "<TAB>"),
KeyCode::BackTab => write!(f, "<BACKTAB>"),
KeyCode::Delete => write!(f, "<DELETE>"),
KeyCode::Insert => write!(f, "<INSERT>"),
KeyCode::Home => write!(f, "<HOME>"),
KeyCode::End => write!(f, "<END>"),
KeyCode::PageUp => write!(f, "<PAGEUP>"),
KeyCode::PageDown => write!(f, "<PAGEDOWN>"),
KeyCode::Char(ch) if ch.is_control() => write!(f, "0x{:x}", ch as u32),
KeyCode::Char(ch) => write!(f, "{ch}"),
}
}
Self::Mouse(mouse) => match mouse {
MouseEvent::LeftPress => write!(f, "<LEFTCLICK>"),
MouseEvent::LeftRelease => write!(f, "<LEFTRELEASE>"),
MouseEvent::RightPress => write!(f, "<RIGHTCLICK>"),
MouseEvent::RightRelease => write!(f, "<RIGHTRELEASE>"),
MouseEvent::MiddlePress => write!(f, "<MIDDLECLICK>"),
MouseEvent::MiddleRelease => write!(f, "<MIDDLERELEASE>"),
MouseEvent::Drag => write!(f, "<DRAG>"),
MouseEvent::ScrollUp => write!(f, "<SCROLLUP>"),
MouseEvent::ScrollDown => write!(f, "<SCROLLDOWN>"),
},
}
}
}
impl nojson::DisplayJson for InputMatcher {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.string(self)
}
}