218 lines
6.4 KiB
Rust
218 lines
6.4 KiB
Rust
use {
|
|
super::events::Action,
|
|
crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
|
|
eyre::Result,
|
|
std::{collections::HashMap, str::FromStr},
|
|
};
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct Config {
|
|
pub keybindings: KeyBindings,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct KeyBindings(pub HashMap<Vec<KeyEvent>, Action>);
|
|
|
|
impl KeyBindings {
|
|
pub fn new(raw: HashMap<&str, String>) -> Self {
|
|
let keybindings = raw
|
|
.into_iter()
|
|
.map(|(key_str, cmd)| {
|
|
let action = Action::from_str(&cmd);
|
|
|
|
match action {
|
|
Ok(action) => (parse_key_sequence(&key_str).unwrap(), action),
|
|
Err(_) => (parse_key_sequence(&key_str).unwrap(), Action::AppAction(cmd)),
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
KeyBindings(keybindings)
|
|
}
|
|
|
|
pub fn get(&self, key_events: &[KeyEvent]) -> Option<&Action> {
|
|
self.0.get(key_events)
|
|
}
|
|
}
|
|
|
|
fn parse_key_event(raw: &str) -> Result<KeyEvent, String> {
|
|
let raw_lower = raw.to_ascii_lowercase();
|
|
let (remaining, modifiers) = extract_modifiers(&raw_lower);
|
|
parse_key_code_with_modifiers(remaining, modifiers)
|
|
}
|
|
|
|
fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
|
|
let mut modifiers = KeyModifiers::empty();
|
|
let mut current = raw;
|
|
|
|
loop {
|
|
match current {
|
|
rest if rest.starts_with("ctrl-") => {
|
|
modifiers.insert(KeyModifiers::CONTROL);
|
|
current = &rest[5..];
|
|
}
|
|
rest if rest.starts_with("alt-") => {
|
|
modifiers.insert(KeyModifiers::ALT);
|
|
current = &rest[4..];
|
|
}
|
|
rest if rest.starts_with("shift-") => {
|
|
modifiers.insert(KeyModifiers::SHIFT);
|
|
current = &rest[6..];
|
|
}
|
|
_ => break, // break out of the loop if no known prefix is detected
|
|
};
|
|
}
|
|
|
|
(current, modifiers)
|
|
}
|
|
|
|
fn parse_key_code_with_modifiers(
|
|
raw: &str,
|
|
mut modifiers: KeyModifiers,
|
|
) -> Result<KeyEvent, String> {
|
|
let c = match raw {
|
|
"esc" => KeyCode::Esc,
|
|
"enter" => KeyCode::Enter,
|
|
"left" => KeyCode::Left,
|
|
"right" => KeyCode::Right,
|
|
"up" => KeyCode::Up,
|
|
"down" => KeyCode::Down,
|
|
"home" => KeyCode::Home,
|
|
"end" => KeyCode::End,
|
|
"pageup" => KeyCode::PageUp,
|
|
"pagedown" => KeyCode::PageDown,
|
|
"backtab" => {
|
|
modifiers.insert(KeyModifiers::SHIFT);
|
|
KeyCode::BackTab
|
|
}
|
|
"backspace" => KeyCode::Backspace,
|
|
"delete" => KeyCode::Delete,
|
|
"insert" => KeyCode::Insert,
|
|
"f1" => KeyCode::F(1),
|
|
"f2" => KeyCode::F(2),
|
|
"f3" => KeyCode::F(3),
|
|
"f4" => KeyCode::F(4),
|
|
"f5" => KeyCode::F(5),
|
|
"f6" => KeyCode::F(6),
|
|
"f7" => KeyCode::F(7),
|
|
"f8" => KeyCode::F(8),
|
|
"f9" => KeyCode::F(9),
|
|
"f10" => KeyCode::F(10),
|
|
"f11" => KeyCode::F(11),
|
|
"f12" => KeyCode::F(12),
|
|
"space" => KeyCode::Char(' '),
|
|
"hyphen" => KeyCode::Char('-'),
|
|
"minus" => KeyCode::Char('-'),
|
|
"tab" => KeyCode::Tab,
|
|
c if c.len() == 1 => {
|
|
let mut c = c.chars().next().unwrap();
|
|
if modifiers.contains(KeyModifiers::SHIFT) {
|
|
c = c.to_ascii_uppercase();
|
|
}
|
|
KeyCode::Char(c)
|
|
}
|
|
_ => return Err(format!("Unable to parse {raw}")),
|
|
};
|
|
Ok(KeyEvent::new(c, modifiers))
|
|
}
|
|
|
|
pub fn key_event_to_string(key_event: &KeyEvent) -> String {
|
|
let char;
|
|
let key_code = match key_event.code {
|
|
KeyCode::Backspace => "backspace",
|
|
KeyCode::Enter => "enter",
|
|
KeyCode::Left => "left",
|
|
KeyCode::Right => "right",
|
|
KeyCode::Up => "up",
|
|
KeyCode::Down => "down",
|
|
KeyCode::Home => "home",
|
|
KeyCode::End => "end",
|
|
KeyCode::PageUp => "pageup",
|
|
KeyCode::PageDown => "pagedown",
|
|
KeyCode::Tab => "tab",
|
|
KeyCode::BackTab => "backtab",
|
|
KeyCode::Delete => "delete",
|
|
KeyCode::Insert => "insert",
|
|
KeyCode::F(c) => {
|
|
char = format!("f({c})");
|
|
&char
|
|
}
|
|
KeyCode::Char(c) if c == ' ' => "space",
|
|
KeyCode::Char(c) => {
|
|
char = c.to_string();
|
|
&char
|
|
}
|
|
KeyCode::Esc => "esc",
|
|
KeyCode::Null => "",
|
|
KeyCode::CapsLock => "",
|
|
KeyCode::Menu => "",
|
|
KeyCode::ScrollLock => "",
|
|
KeyCode::Media(_) => "",
|
|
KeyCode::NumLock => "",
|
|
KeyCode::PrintScreen => "",
|
|
KeyCode::Pause => "",
|
|
KeyCode::KeypadBegin => "",
|
|
KeyCode::Modifier(_) => "",
|
|
};
|
|
|
|
let mut modifiers = Vec::with_capacity(3);
|
|
|
|
if key_event.modifiers.intersects(KeyModifiers::CONTROL) {
|
|
modifiers.push("ctrl");
|
|
}
|
|
|
|
if key_event.modifiers.intersects(KeyModifiers::SHIFT) {
|
|
modifiers.push("shift");
|
|
}
|
|
|
|
if key_event.modifiers.intersects(KeyModifiers::ALT) {
|
|
modifiers.push("alt");
|
|
}
|
|
|
|
// if the modifiers is "shift" and the key code is a letter, we just return the letter
|
|
// otherwise we return the modifiers joined by a dash and the key code
|
|
if modifiers.len() == 1
|
|
&& key_code.chars().count() == 1
|
|
&& key_code.chars().all(char::is_alphabetic)
|
|
{
|
|
return key_code.to_string();
|
|
}
|
|
|
|
let mut key = modifiers.join("-");
|
|
|
|
if !key.is_empty() {
|
|
key.push('-');
|
|
}
|
|
|
|
key.push_str(key_code);
|
|
|
|
key
|
|
}
|
|
|
|
pub fn parse_key_sequence(raw: &str) -> Result<Vec<KeyEvent>, String> {
|
|
if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() {
|
|
return Err(format!("Unable to parse `{}`", raw));
|
|
}
|
|
let raw = if !raw.contains("><") {
|
|
let raw = raw.strip_prefix('<').unwrap_or(raw);
|
|
let raw = raw.strip_prefix('>').unwrap_or(raw);
|
|
raw
|
|
} else {
|
|
raw
|
|
};
|
|
let sequences = raw
|
|
.split("><")
|
|
.map(|seq| {
|
|
if let Some(s) = seq.strip_prefix('<') {
|
|
s
|
|
} else if let Some(s) = seq.strip_suffix('>') {
|
|
s
|
|
} else {
|
|
seq
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
sequences.into_iter().map(parse_key_event).collect()
|
|
}
|