You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
helix-plus/helix-view/src/input.rs

227 lines
7.4 KiB
Rust

//! Input event handling, currently backed by crossterm.
use anyhow::{anyhow, Error};
use crossterm::event;
use serde::de::{self, Deserialize, Deserializer};
use std::fmt;
pub use crossterm::event::{KeyCode, KeyModifiers};
/// Represents a key event.
// We use a newtype here because we want to customize Deserialize and Display.
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: KeyModifiers,
}
impl fmt::Display for KeyEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{}{}{}",
if self.modifiers.contains(KeyModifiers::SHIFT) {
"S-"
} else {
""
},
if self.modifiers.contains(KeyModifiers::ALT) {
"A-"
} else {
""
},
if self.modifiers.contains(KeyModifiers::CONTROL) {
"C-"
} else {
""
},
))?;
match self.code {
KeyCode::Backspace => f.write_str("backspace")?,
KeyCode::Enter => f.write_str("ret")?,
KeyCode::Left => f.write_str("left")?,
KeyCode::Right => f.write_str("right")?,
KeyCode::Up => f.write_str("up")?,
KeyCode::Down => f.write_str("down")?,
KeyCode::Home => f.write_str("home")?,
KeyCode::End => f.write_str("end")?,
KeyCode::PageUp => f.write_str("pageup")?,
KeyCode::PageDown => f.write_str("pagedown")?,
KeyCode::Tab => f.write_str("tab")?,
KeyCode::BackTab => f.write_str("backtab")?,
KeyCode::Delete => f.write_str("del")?,
KeyCode::Insert => f.write_str("ins")?,
KeyCode::Null => f.write_str("null")?,
KeyCode::Esc => f.write_str("esc")?,
KeyCode::Char('<') => f.write_str("lt")?,
KeyCode::Char('>') => f.write_str("gt")?,
KeyCode::Char('+') => f.write_str("plus")?,
KeyCode::Char('-') => f.write_str("minus")?,
KeyCode::Char(';') => f.write_str("semicolon")?,
KeyCode::Char('%') => f.write_str("percent")?,
KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
};
Ok(())
}
}
impl std::str::FromStr for KeyEvent {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut tokens: Vec<_> = s.split('-').collect();
let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
"backspace" => KeyCode::Backspace,
"space" => KeyCode::Char(' '),
"ret" => KeyCode::Enter,
"lt" => KeyCode::Char('<'),
"gt" => KeyCode::Char('>'),
"plus" => KeyCode::Char('+'),
"minus" => KeyCode::Char('-'),
"semicolon" => KeyCode::Char(';'),
"percent" => KeyCode::Char('%'),
"left" => KeyCode::Left,
"right" => KeyCode::Right,
"up" => KeyCode::Down,
"home" => KeyCode::Home,
"end" => KeyCode::End,
"pageup" => KeyCode::PageUp,
"pagedown" => KeyCode::PageDown,
"tab" => KeyCode::Tab,
"backtab" => KeyCode::BackTab,
"del" => KeyCode::Delete,
"ins" => KeyCode::Insert,
"null" => KeyCode::Null,
"esc" => KeyCode::Esc,
single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
function if function.len() > 1 && function.starts_with('F') => {
let function: String = function.chars().skip(1).collect();
let function = str::parse::<u8>(&function)?;
(function > 0 && function < 13)
.then(|| KeyCode::F(function))
.ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
}
invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
};
let mut modifiers = KeyModifiers::empty();
for token in tokens {
let flag = match token {
"S" => KeyModifiers::SHIFT,
"A" => KeyModifiers::ALT,
"C" => KeyModifiers::CONTROL,
_ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
};
if modifiers.contains(flag) {
return Err(anyhow!("Repeated key modifier '{}-'", token));
}
modifiers.insert(flag);
}
Ok(KeyEvent { code, modifiers })
}
}
impl<'de> Deserialize<'de> for KeyEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(de::Error::custom)
}
}
impl From<event::KeyEvent> for KeyEvent {
fn from(event::KeyEvent { code, modifiers }: event::KeyEvent) -> KeyEvent {
KeyEvent { code, modifiers }
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parsing_unmodified_keys() {
assert_eq!(
str::parse::<KeyEvent>("backspace").unwrap(),
KeyEvent {
code: KeyCode::Backspace,
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>("left").unwrap(),
KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>(",").unwrap(),
KeyEvent {
code: KeyCode::Char(','),
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>("w").unwrap(),
KeyEvent {
code: KeyCode::Char('w'),
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>("F12").unwrap(),
KeyEvent {
code: KeyCode::F(12),
modifiers: KeyModifiers::NONE
}
);
}
#[test]
fn parsing_modified_keys() {
assert_eq!(
str::parse::<KeyEvent>("S-minus").unwrap(),
KeyEvent {
code: KeyCode::Char('-'),
modifiers: KeyModifiers::SHIFT
}
);
assert_eq!(
str::parse::<KeyEvent>("C-A-S-F12").unwrap(),
KeyEvent {
code: KeyCode::F(12),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
}
);
assert_eq!(
str::parse::<KeyEvent>("S-C-2").unwrap(),
KeyEvent {
code: KeyCode::Char('2'),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
}
);
}
#[test]
fn parsing_nonsensical_keys_fails() {
assert!(str::parse::<KeyEvent>("F13").is_err());
assert!(str::parse::<KeyEvent>("F0").is_err());
assert!(str::parse::<KeyEvent>("aaa").is_err());
assert!(str::parse::<KeyEvent>("S-S-a").is_err());
assert!(str::parse::<KeyEvent>("C-A-S-C-1").is_err());
assert!(str::parse::<KeyEvent>("FU").is_err());
assert!(str::parse::<KeyEvent>("123").is_err());
assert!(str::parse::<KeyEvent>("S--").is_err());
}
}