From 2b39608f85b9f0e5b3d6b845221c399496f005a0 Mon Sep 17 00:00:00 2001 From: Soispha Date: Tue, 7 Nov 2023 19:37:06 +0100 Subject: [PATCH] feat(keymaps): Separate the keymapping system into its own crate --- keymaps/Cargo.toml | 7 + keymaps/src/error/mod.rs | 40 +++ keymaps/src/key/chars/crossterm.rs | 68 ++++ keymaps/src/key/{chars.rs => chars/mod.rs} | 3 + keymaps/src/key/key.rs | 357 --------------------- keymaps/src/key/key/crossterm.rs | 134 ++++++++ keymaps/src/key/key/mod.rs | 80 +++++ keymaps/src/key/key/parsing/mod.rs | 213 ++++++++++++ keymaps/src/key/key_value.rs | 133 ++++---- keymaps/src/key/keys.pest | 60 ++++ keymaps/src/key/keys.rs | 55 ++-- keymaps/src/key/mod.rs | 37 ++- keymaps/src/lib.rs | 1 + keymaps/src/trie.rs | 42 +-- 14 files changed, 742 insertions(+), 488 deletions(-) create mode 100644 keymaps/src/error/mod.rs create mode 100644 keymaps/src/key/chars/crossterm.rs rename keymaps/src/key/{chars.rs => chars/mod.rs} (95%) delete mode 100644 keymaps/src/key/key.rs create mode 100644 keymaps/src/key/key/crossterm.rs create mode 100644 keymaps/src/key/key/mod.rs create mode 100644 keymaps/src/key/key/parsing/mod.rs create mode 100644 keymaps/src/key/keys.pest diff --git a/keymaps/Cargo.toml b/keymaps/Cargo.toml index 3415502..2823ff6 100644 --- a/keymaps/Cargo.toml +++ b/keymaps/Cargo.toml @@ -6,3 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +log = "0.4.20" +thiserror = "1.0.50" +pest = "2.7.5" +pest_derive = {version = "2.7.5", features = ["grammar-extras"]} + +[dev-dependencies] +pretty_assertions = "1.4.0" diff --git a/keymaps/src/error/mod.rs b/keymaps/src/error/mod.rs new file mode 100644 index 0000000..5b261c2 --- /dev/null +++ b/keymaps/src/error/mod.rs @@ -0,0 +1,40 @@ +use std::{fmt::Display, num::ParseIntError}; + +use thiserror::Error; + +use crate::key::{key, keys, Keys}; + +#[derive(Error, Debug)] +pub enum TrieInsertError { + #[error("The key ('{0}') contains nodes, which already have a value set!")] + KeyPathBlocked(Keys), + + #[error("The key ('{key}') already has a value associatet with it, which is: '{value}'")] + KeyAlreadySet { key: Keys, value: V }, + + #[error("The node accessed by this key ('{0}') already has children! You can not set a value for it")] + NodeHasChildren(Keys), +} + +#[derive(Error, Debug)] +pub enum KeyParseError { + #[error("This key expression could not be parsed, see the error message for more: {0}")] + RawParseError(#[from] pest::error::Error), +} + +#[derive(Error, Debug)] +pub enum KeysParseError { + #[error( + "This chain of key expressions could not be parsed, see the error message for more: {0}" + )] + RawParseError(#[from] pest::error::Error), +} + +#[derive(Error, Debug)] +pub enum KeyValueParseError { + #[error("The String ('{0}') is not a correct special key name!")] + NoMatch(String), + + #[error("The number associate with the F key ('{0}') can't be parsed as u8!")] + CantParseFNumber(#[from] ParseIntError), +} diff --git a/keymaps/src/key/chars/crossterm.rs b/keymaps/src/key/chars/crossterm.rs new file mode 100644 index 0000000..126946d --- /dev/null +++ b/keymaps/src/key/chars/crossterm.rs @@ -0,0 +1,68 @@ +use crossterm::event::KeyCode; + +impl From for KeyValue { + fn from(value: KeyCode) -> Self { + match value { + KeyCode::Backspace => Self::Backspace, + KeyCode::Enter => Self::Enter, + KeyCode::Left => Self::Left, + KeyCode::Right => Self::Right, + KeyCode::Up => Self::Up, + KeyCode::Down => Self::Down, + KeyCode::Home => Self::Home, + KeyCode::End => Self::End, + KeyCode::PageUp => Self::PageUp, + KeyCode::PageDown => Self::PageDown, + KeyCode::Tab => Self::Tab, + KeyCode::BackTab => Self::BackTab, + KeyCode::Delete => Self::Delete, + KeyCode::Insert => Self::Insert, + KeyCode::F(n) => Self::F(n), + KeyCode::Char(c) => Self::Char(c), + KeyCode::Null => Self::Null, + KeyCode::Esc => Self::Esc, + KeyCode::CapsLock => Self::CapsLock, + KeyCode::ScrollLock => Self::ScrollLock, + KeyCode::NumLock => Self::NumLock, + KeyCode::PrintScreen => Self::PrintScreen, + KeyCode::Pause => Self::Pause, + KeyCode::Menu => Self::Menu, + KeyCode::KeypadBegin => Self::KeypadBegin, + // FIXME(@soispha): This reduces our information, casting a KeyCode to a KeyValue + // and back again would not equal the original KeyCode <2023-10-15> + KeyCode::Media(_) => Self::Null, + KeyCode::Modifier(_) => Self::Null, + } + } +} +impl Into for KeyValue { + fn into(self) -> KeyCode { + match self { + Self::Backspace => KeyCode::Backspace, + Self::Enter => KeyCode::Enter, + Self::Left => KeyCode::Left, + Self::Right => KeyCode::Right, + Self::Up => KeyCode::Up, + Self::Down => KeyCode::Down, + Self::Home => KeyCode::Home, + Self::End => KeyCode::End, + Self::PageUp => KeyCode::PageUp, + Self::PageDown => KeyCode::PageDown, + Self::Tab => KeyCode::Tab, + Self::BackTab => KeyCode::BackTab, + Self::Delete => KeyCode::Delete, + Self::Insert => KeyCode::Insert, + Self::F(n) => KeyCode::F(n), + Self::Char(c) => KeyCode::Char(c), + Self::Null => KeyCode::Null, + Self::Esc => KeyCode::Esc, + Self::CapsLock => KeyCode::CapsLock, + Self::ScrollLock => KeyCode::ScrollLock, + Self::NumLock => KeyCode::NumLock, + Self::PrintScreen => KeyCode::PrintScreen, + Self::Pause => KeyCode::Pause, + Self::Menu => KeyCode::Menu, + Self::KeypadBegin => KeyCode::KeypadBegin, + } + } +} diff --git a/keymaps/src/key/chars.rs b/keymaps/src/key/chars/mod.rs similarity index 95% rename from keymaps/src/key/chars.rs rename to keymaps/src/key/chars/mod.rs index 942dbce..63b21ed 100644 --- a/keymaps/src/key/chars.rs +++ b/keymaps/src/key/chars/mod.rs @@ -1,3 +1,6 @@ +#[cfg(crossterm)] +mod crossterm; + use std::{collections::VecDeque, fmt::Display}; #[derive(Debug)] diff --git a/keymaps/src/key/key.rs b/keymaps/src/key/key.rs deleted file mode 100644 index f56609a..0000000 --- a/keymaps/src/key/key.rs +++ /dev/null @@ -1,357 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use anyhow::{bail, Context}; -use cli_log::{debug, info}; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}; - -use super::{Chars, KeyValue, Keys}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct Key { - // Modifiers - pub(super) alt: bool, - pub(super) ctrl: bool, - pub(super) meta: bool, - pub(super) shift: bool, - - pub(super) value: Option, -} -impl Key { - pub fn new() -> Self { - Key { - alt: false, - ctrl: false, - meta: false, - shift: false, - value: None, - } - } - pub fn value(&self) -> Option<&KeyValue> { - self.value.as_ref() - } - pub fn to_string_repr(self) -> String { - let mut output = String::new(); - if self.alt || self.ctrl || self.meta || self.shift { - output.push('<') - } - if self.alt { - output.push('A'); - } - if self.ctrl { - output.push('C'); - } - if self.meta { - output.push('M'); - } - if self.shift { - output.push('S'); - } - if self.alt || self.ctrl || self.meta || self.shift { - output.push('-') - } - output.push_str( - &self - .value - .expect("There can be no Nones here, if the Key comes from the public api") - .to_string(), - ); - if self.alt || self.ctrl || self.meta || self.shift { - output.push('>') - } - output - } - fn merge_with(mut self, other: Key) -> Self { - // Modifiers - self.alt = self.alt || other.alt; - self.ctrl = self.ctrl || other.ctrl; - self.meta = self.meta || other.meta; - self.shift = self.shift || other.shift; - - self.value = Some(self.value.unwrap_or(other.value.unwrap_or(KeyValue::Null))); - self - } - pub(super) fn parse(chars: &mut Chars) -> anyhow::Result { - assert_eq!(chars.pop().expect("This is a developer error"), '<'); - let mut parse_buffer: Vec = Vec::new(); - - let mut reached_non_modifier = false; - let mut output_key_filled = false; - let mut output_key = Key::new(); - while let Some(char) = chars.pop() { - if char == '>' { - break; - } else { - if char.is_ascii_uppercase() - || char.is_numeric() && !reached_non_modifier && !output_key_filled - { - parse_buffer.push(char); - } else if char == '-' && !reached_non_modifier && !output_key_filled { - // We moved to the modified char - reached_non_modifier = true; - - // Our parse_buffer should only contain modifiers: - let mut alt = false; - let mut ctrl = false; - let mut meta = false; - let mut shift = false; - - for char in &parse_buffer { - match char { - 'A' => alt = true, - 'C' => ctrl = true, - 'M' => meta = true, - 'S' => shift = true, - char => bail!( - "The char ('{}') is not a valid descriptor of a modifier", - char - ), - } - } - output_key = Key { - alt, - ctrl, - meta, - shift, - value: None, - }; - } else if reached_non_modifier && !output_key_filled { - if char == '<' { - chars.prepend('<'); - let key = Key::parse(chars)?; - output_key = output_key.merge_with(key); - } else { - output_key.value = Some(KeyValue::Char(char)); - } - output_key_filled = true; - } else { - bail!( - "Your can not put a this char here! - parse_buffer: '{}'; - char: '{}'; - chars: '{:#?}'; - output_key: '{:#?}' ", - &parse_buffer.iter().collect::(), - &char, - &chars, - &output_key - ) - } - } - } - if output_key_filled { - Ok(output_key) - } else { - let mut parse_buffer = Chars(parse_buffer.into()); - let get_output = |value: KeyValue| -> Key { - let key = Key { - alt: false, - ctrl: false, - meta: false, - shift: false, - value: Some(value), - }; - - return key.merge_with(output_key); - }; - if let Some(char) = parse_buffer.peek() { - if char == &'F' { - let _f = parse_buffer.pop(); - let number: u8 = parse_buffer.to_string().parse().with_context(|| { - format!("Failed to parse buffer ('{}') as u8", &parse_buffer) - })?; - Ok(get_output(KeyValue::F(number))) - } else { - match &parse_buffer.to_string()[..] { - "BACKSPACE" => Ok(get_output(KeyValue::Backspace)), - "ENTER" => Ok(get_output(KeyValue::Enter)), - "LEFT" => Ok(get_output(KeyValue::Left)), - "RIGHT" => Ok(get_output(KeyValue::Right)), - "UP" => Ok(get_output(KeyValue::Up)), - "DOWN" => Ok(get_output(KeyValue::Down)), - "HOME" => Ok(get_output(KeyValue::Home)), - "END" => Ok(get_output(KeyValue::End)), - "PAGEUP" => Ok(get_output(KeyValue::PageUp)), - "PAGEDOWN" => Ok(get_output(KeyValue::PageDown)), - "TAB" => Ok(get_output(KeyValue::Tab)), - "BACKTAB" => Ok(get_output(KeyValue::BackTab)), - "DELETE" => Ok(get_output(KeyValue::Delete)), - "INSERT" => Ok(get_output(KeyValue::Insert)), - "ESC" => Ok(get_output(KeyValue::Esc)), - "CAPSLOCK" => Ok(get_output(KeyValue::CapsLock)), - "SCROLLlOCK" => Ok(get_output(KeyValue::ScrollLock)), - "NUMLOCK" => Ok(get_output(KeyValue::NumLock)), - "PRINTSCREEN" => Ok(get_output(KeyValue::PrintScreen)), - "PAUSE" => Ok(get_output(KeyValue::Pause)), - "MENU" => Ok(get_output(KeyValue::Menu)), - "KEYPADBEGIN" => Ok(get_output(KeyValue::KeypadBegin)), - - "DASH" => Ok(get_output(KeyValue::Char('-'))), - "ANGULAR_BRACKET_OPEN" | "ABO" => Ok(get_output(KeyValue::Char('<'))), - "ANGULAR_BRACKET_CLOSE" | "ABC" => Ok(get_output(KeyValue::Char('>'))), - other_str => bail!( - "The String ('{}') is not a correct special key name!", - other_str - ), - } - } - } else { - bail!( - "You need to put something into the angulare brackets (<>) - parse_buffer: '{}'; - chars: '{:#?}';", - &parse_buffer, - &chars, - ) - } - } - } -} -impl Display for Key { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.to_string_repr()) - } -} -impl Into for Key { - fn into(self) -> Event { - let mut modifiers; - { - modifiers = KeyModifiers::all(); - if !self.alt { - modifiers.remove(KeyModifiers::ALT); - } - if !self.ctrl { - modifiers.remove(KeyModifiers::CONTROL); - } - if !self.meta { - modifiers.remove(KeyModifiers::META); - modifiers.remove(KeyModifiers::SUPER); - } - if !self.shift { - modifiers.remove(KeyModifiers::SHIFT); - } - modifiers.remove(KeyModifiers::HYPER); - modifiers.remove(KeyModifiers::META); - if self.alt || self.ctrl || self.meta || self.shift { - modifiers.remove(KeyModifiers::NONE); - } - } - - let output = Event::Key(KeyEvent { - code: { - // We sorta hit a edge case here, if we have a shifted char we need to tell - // crossterm about that. Thus we need to manually apply the shift modifier on the - // value. - if self.shift { - if let Some(KeyValue::Char(char)) = self.value { - let upper_case_char: char = { - let chars = char.to_uppercase().collect::>(); - if chars.len() != 1 { - unimplemented!( - " - I have no idea how we handle this case, - we'll just have to hope, that it never comes up. - " - ); - } else { - *chars.first().expect("We checked the length") - } - }; - KeyCode::Char(upper_case_char) - } else { - self.value.unwrap_or(KeyValue::Null).into() - } - } else { - self.value.unwrap_or(KeyValue::Null).into() - } - }, - modifiers, - kind: KeyEventKind::Press, - state: KeyEventState::NONE, - }); - output - } -} - -impl TryFrom<&Event> for Key { - type Error = anyhow::Error; - - fn try_from(value: &Event) -> std::result::Result { - let mut output_key: Key = Key::new(); - match value { - Event::Key(key_event) => { - { - let key_mods = key_event.modifiers; - - output_key.alt = KeyModifiers::intersects(&key_mods, KeyModifiers::ALT); - output_key.ctrl = KeyModifiers::intersects(&key_mods, KeyModifiers::CONTROL); - output_key.meta = KeyModifiers::intersects(&key_mods, KeyModifiers::META) - || KeyModifiers::intersects(&key_mods, KeyModifiers::SUPER); - output_key.shift = KeyModifiers::intersects(&key_mods, KeyModifiers::SHIFT); - // let hyper = KeyModifiers::intersects(&key_mods, KeyModifiers::HYPER); - // let none = KeyModifiers::intersects(&key_mods, KeyModifiers::NONE); - } - - { - let key_code = key_event.code; - if output_key.shift { - // We need to deal have an edge case for shift, as the value will be in the - // shifted form (for example 'A'). If we left that, we would get "" as - // Key representation, which can't be parsed. So we simply unshift the 'A' - // here, turning the representation into: "" - if let KeyCode::Char(char) = key_code { - let lower_case_char: char = { - let chars = char.to_lowercase().collect::>(); - if chars.len() != 1 { - unimplemented!( - " - I have no idea how we handle this case, - we'll just have to hope, that it never comes up. - " - ); - } else { - *chars.first().expect("We checked the length") - } - }; - info!( - "Had to translate key ('{}') to it's lowercase variant ('{}')", - char, lower_case_char - ); - output_key.value = Some(KeyValue::Char(lower_case_char)); - } else { - debug!( - "Key ('{}') is shifted but not a character!", - Into::::into(key_code) - ); - output_key.value = Some(key_code.into()); - } - } else { - output_key.value = Some(key_code.into()); - } - } - - Ok(output_key) - } - Event::Mouse(_) - | Event::Paste(_) - | Event::Resize(_, _) - | Event::FocusGained - | Event::FocusLost => bail!("Only supports parsing from key event"), - } - } -} -impl FromStr for Key { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let mut keys: Keys = s.parse().context("Failed to parse string as keys")?; - if keys.0.len() == 1 { - let key = keys - .0 - .pop() - .expect("The vec should have exactly one element"); - return Ok(key); - } else { - bail!("The string ('{}') contains more than one key", &s); - } - } -} diff --git a/keymaps/src/key/key/crossterm.rs b/keymaps/src/key/key/crossterm.rs new file mode 100644 index 0000000..8730907 --- /dev/null +++ b/keymaps/src/key/key/crossterm.rs @@ -0,0 +1,134 @@ +use log::{debug, info}; + +use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}; + + +impl Into for Key { + fn into(self) -> Event { + let mut modifiers; + { + modifiers = KeyModifiers::all(); + if !self.alt { + modifiers.remove(KeyModifiers::ALT); + } + if !self.ctrl { + modifiers.remove(KeyModifiers::CONTROL); + } + if !self.meta { + modifiers.remove(KeyModifiers::META); + modifiers.remove(KeyModifiers::SUPER); + } + if !self.shift { + modifiers.remove(KeyModifiers::SHIFT); + } + modifiers.remove(KeyModifiers::HYPER); + modifiers.remove(KeyModifiers::META); + if self.alt || self.ctrl || self.meta || self.shift { + modifiers.remove(KeyModifiers::NONE); + } + } + + let output = Event::Key(KeyEvent { + code: { + // We sorta hit a edge case here, if we have a shifted char we need to tell + // crossterm about that. Thus we need to manually apply the shift modifier on the + // value. + if self.shift { + if let Some(KeyValue::Char(char)) = self.value { + let upper_case_char: char = { + let chars = char.to_uppercase().collect::>(); + if chars.len() != 1 { + unimplemented!( + " + I have no idea how we handle this case, + we'll just have to hope, that it never comes up. + " + ); + } else { + *chars.first().expect("We checked the length") + } + }; + KeyCode::Char(upper_case_char) + } else { + self.value.unwrap_or(KeyValue::Null).into() + } + } else { + self.value.unwrap_or(KeyValue::Null).into() + } + }, + modifiers, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }); + output + } +} + +#[cfg(crossterm)] +impl TryFrom<&Event> for Key { + type Error = anyhow::Error; + + fn try_from(value: &Event) -> std::result::Result { + let mut output_key: Key = Key::new(); + match value { + Event::Key(key_event) => { + { + let key_mods = key_event.modifiers; + + output_key.alt = KeyModifiers::intersects(&key_mods, KeyModifiers::ALT); + output_key.ctrl = KeyModifiers::intersects(&key_mods, KeyModifiers::CONTROL); + output_key.meta = KeyModifiers::intersects(&key_mods, KeyModifiers::META) + || KeyModifiers::intersects(&key_mods, KeyModifiers::SUPER); + output_key.shift = KeyModifiers::intersects(&key_mods, KeyModifiers::SHIFT); + // let hyper = KeyModifiers::intersects(&key_mods, KeyModifiers::HYPER); + // let none = KeyModifiers::intersects(&key_mods, KeyModifiers::NONE); + } + + { + let key_code = key_event.code; + if output_key.shift { + // We need to deal have an edge case for shift, as the value will be in the + // shifted form (for example 'A'). If we left that, we would get "" as + // Key representation, which can't be parsed. So we simply unshift the 'A' + // here, turning the representation into: "" + if let KeyCode::Char(char) = key_code { + let lower_case_char: char = { + let chars = char.to_lowercase().collect::>(); + if chars.len() != 1 { + unimplemented!( + " + I have no idea how we handle this case, + we'll just have to hope, that it never comes up. + " + ); + } else { + *chars.first().expect("We checked the length") + } + }; + info!( + "Had to translate key ('{}') to it's lowercase variant ('{}')", + char, lower_case_char + ); + output_key.value = Some(KeyValue::Char(lower_case_char)); + } else { + debug!( + "Key ('{}') is shifted but not a character!", + Into::::into(key_code) + ); + output_key.value = Some(key_code.into()); + } + } else { + output_key.value = Some(key_code.into()); + } + } + + Ok(output_key) + } + Event::Mouse(_) + | Event::Paste(_) + | Event::Resize(_, _) + | Event::FocusGained + | Event::FocusLost => bail!("Only supports parsing from key event"), + } + } +} diff --git a/keymaps/src/key/key/mod.rs b/keymaps/src/key/key/mod.rs new file mode 100644 index 0000000..1f07255 --- /dev/null +++ b/keymaps/src/key/key/mod.rs @@ -0,0 +1,80 @@ +#[cfg(crossterm)] +mod crossterm; +mod parsing; + +use std::fmt::Display; + +use pest_derive::Parser; + +use super::KeyValue; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default, Parser)] +#[grammar = "./key/keys.pest"] +pub struct Key { + // Modifiers + pub(super) alt: bool, + pub(super) ctrl: bool, + pub(super) meta: bool, + pub(super) shift: bool, + + pub(super) value: Option, +} + +impl Key { + pub fn new() -> Self { + Self::default() + } + + pub fn value(&self) -> Option<&KeyValue> { + self.value.as_ref() + } + + pub fn to_string_repr(self) -> String { + let mut output = String::new(); + if self.alt || self.ctrl || self.meta || self.shift { + output.push('<') + } + if self.alt { + output.push('A'); + } + if self.ctrl { + output.push('C'); + } + if self.meta { + output.push('M'); + } + if self.shift { + output.push('S'); + } + if self.alt || self.ctrl || self.meta || self.shift { + output.push('-') + } + output.push_str( + &self + .value + .expect("There can be no `None`s here, if the Key comes from the public api") + .to_string(), + ); + if self.alt || self.ctrl || self.meta || self.shift { + output.push('>') + } + output + } + + fn merge_with(mut self, other: Key) -> Self { + // Modifiers + self.alt = self.alt || other.alt; + self.ctrl = self.ctrl || other.ctrl; + self.meta = self.meta || other.meta; + self.shift = self.shift || other.shift; + + self.value = Some(self.value.unwrap_or(other.value.unwrap_or(KeyValue::Null))); + self + } +} + +impl Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string_repr()) + } +} diff --git a/keymaps/src/key/key/parsing/mod.rs b/keymaps/src/key/key/parsing/mod.rs new file mode 100644 index 0000000..dfc1b05 --- /dev/null +++ b/keymaps/src/key/key/parsing/mod.rs @@ -0,0 +1,213 @@ +use std::{fmt::Debug, hash::Hash, marker, mem, str::FromStr}; + +use pest::{iterators::Pair, Parser}; + +use crate::{error, key::KeyValue}; + +use super::{Key, Rule}; + +impl Key { + pub fn from_pair(pair: Pair) -> Self + where + R: marker::Copy + Debug + Hash + Ord, + { + let pairs = pair.into_inner(); + let mut output = Self::default(); + + let modifiers: Vec<_> = pairs.clone().find_tagged("modifiers").collect(); + let values: Vec<_> = pairs.clone().find_tagged("value").collect(); + let special_keys: Vec<_> = pairs.find_tagged("special_key").collect(); + + for modifier in modifiers { + match modifier.as_span().as_str() { + "A" => output.alt = true, + "C" => output.ctrl = true, + "M" => output.meta = true, + "S" => output.shift = true, + _ => unreachable!("This modifier should not have been parsed"), + } + } + + if values.is_empty() { + // Check that we have parsed *something* + debug_assert!(!special_keys.is_empty()); + // Check, that we have only parsed one special key + debug_assert_eq!(special_keys.len(), 1); + + let special_key_value: KeyValue = special_keys + .first() + .expect("This should exist (and be the only value)") + .as_str() + .parse() + .expect("This was parsed, and should thus be a valid special_key"); + output.value = Some(special_key_value); + } else { + // Check, that we have only parsed one key + debug_assert_eq!(values.len(), 1); + + let key_str = values.first().expect("This should exist").as_str(); + // Check that we really only have a one char &str + debug_assert_eq!(key_str.chars().count(), 1); + + let key_value: KeyValue = KeyValue::Char( + key_str + .chars() + .nth(0) + .expect("This &str should have a length of exactly one char"), + ); + + output.value = Some(key_value); + } + output + } + // pub(super) fn parse_old(chars: &mut Chars) -> anyhow::Result { + // assert_eq!(chars.pop().expect("This is a developer error"), '<'); + // let mut parse_buffer: Vec = Vec::new(); + // + // let mut reached_non_modifier = false; + // let mut output_key_filled = false; + // let mut output_key = Key::new(); + // while let Some(char) = chars.pop() { + // if char == '>' { + // break; + // } else { + // if char.is_ascii_uppercase() + // || char.is_numeric() && !reached_non_modifier && !output_key_filled + // { + // parse_buffer.push(char); + // } else if char == '-' && !reached_non_modifier && !output_key_filled { + // // We moved to the modified char + // reached_non_modifier = true; + // + // // Our parse_buffer should only contain modifiers: + // let mut alt = false; + // let mut ctrl = false; + // let mut meta = false; + // let mut shift = false; + // + // for char in &parse_buffer { + // match char { + // 'A' => alt = true, + // 'C' => ctrl = true, + // 'M' => meta = true, + // 'S' => shift = true, + // char => bail!( + // "The char ('{}') is not a valid descriptor of a modifier", + // char + // ), + // } + // } + // output_key = Key { + // alt, + // ctrl, + // meta, + // shift, + // value: None, + // }; + // } else if reached_non_modifier && !output_key_filled { + // if char == '<' { + // chars.prepend('<'); + // let key = Key::parse(chars)?; + // output_key = output_key.merge_with(key); + // } else { + // output_key.value = Some(KeyValue::Char(char)); + // } + // output_key_filled = true; + // } else { + // bail!( + // "Your can not put a this char here! + // parse_buffer: '{}'; + // char: '{}'; + // chars: '{:#?}'; + // output_key: '{:#?}' ", + // &parse_buffer.iter().collect::(), + // &char, + // &chars, + // &output_key + // ) + // } + // } + // } + // if output_key_filled { + // Ok(output_key) + // } else { + // let mut parse_buffer = Chars(parse_buffer.into()); + // let get_output = |value: KeyValue| -> Key { + // let key = Key { + // alt: false, + // ctrl: false, + // meta: false, + // shift: false, + // value: Some(value), + // }; + // + // return key.merge_with(output_key); + // }; + // if let Some(char) = parse_buffer.peek() { + // if char == &'F' { + // let _f = parse_buffer.pop(); + // let number: u8 = parse_buffer.to_string().parse().with_context(|| { + // format!("Failed to parse buffer ('{}') as u8", &parse_buffer) + // })?; + // Ok(get_output(KeyValue::F(number))) + // } else { + // match &parse_buffer.to_string()[..] { + // "BACKSPACE" => Ok(get_output(KeyValue::Backspace)), + // "ENTER" => Ok(get_output(KeyValue::Enter)), + // "LEFT" => Ok(get_output(KeyValue::Left)), + // "RIGHT" => Ok(get_output(KeyValue::Right)), + // "UP" => Ok(get_output(KeyValue::Up)), + // "DOWN" => Ok(get_output(KeyValue::Down)), + // "HOME" => Ok(get_output(KeyValue::Home)), + // "END" => Ok(get_output(KeyValue::End)), + // "PAGEUP" => Ok(get_output(KeyValue::PageUp)), + // "PAGEDOWN" => Ok(get_output(KeyValue::PageDown)), + // "TAB" => Ok(get_output(KeyValue::Tab)), + // "BACKTAB" => Ok(get_output(KeyValue::BackTab)), + // "DELETE" => Ok(get_output(KeyValue::Delete)), + // "INSERT" => Ok(get_output(KeyValue::Insert)), + // "ESC" => Ok(get_output(KeyValue::Esc)), + // "CAPSLOCK" => Ok(get_output(KeyValue::CapsLock)), + // "SCROLLlOCK" => Ok(get_output(KeyValue::ScrollLock)), + // "NUMLOCK" => Ok(get_output(KeyValue::NumLock)), + // "PRINTSCREEN" => Ok(get_output(KeyValue::PrintScreen)), + // "PAUSE" => Ok(get_output(KeyValue::Pause)), + // "MENU" => Ok(get_output(KeyValue::Menu)), + // "KEYPADBEGIN" => Ok(get_output(KeyValue::KeypadBegin)), + // + // "DASH" => Ok(get_output(KeyValue::Char('-'))), + // "ANGULAR_BRACKET_OPEN" | "ABO" => Ok(get_output(KeyValue::Char('<'))), + // "ANGULAR_BRACKET_CLOSE" | "ABC" => Ok(get_output(KeyValue::Char('>'))), + // other_str => bail!( + // "The String ('{}') is not a correct special key name!", + // other_str + // ), + // } + // } + // } else { + // bail!( + // "You need to put something into the angulare brackets (<>) + // parse_buffer: '{}'; + // chars: '{:#?}';", + // &parse_buffer, + // &chars, + // ) + // } + // } + // } +} + +impl FromStr for Key { + type Err = error::KeyParseError; + + fn from_str(s: &str) -> Result { + let mut key_pairs: Vec<_> = Self::parse(Rule::key, s)?.find_tagged("key").collect(); + debug_assert_eq!(key_pairs.len(), 1); + let key: Key = Self::from_pair::( + key_pairs + .pop() + .expect("This should contain only this first (and last) element"), + ); + Ok(key) + } +} diff --git a/keymaps/src/key/key_value.rs b/keymaps/src/key/key_value.rs index 8a526df..72d3c32 100644 --- a/keymaps/src/key/key_value.rs +++ b/keymaps/src/key/key_value.rs @@ -1,6 +1,9 @@ -use std::fmt::{Display, Write}; +use std::{ + fmt::{Display, Write}, + str::FromStr, +}; -use crossterm::event::KeyCode; +use crate::error; // taken directly from crossterm #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] @@ -21,7 +24,7 @@ pub enum KeyValue { Insert, F(u8), Char(char), - Null, // TODO(@soispha): What is this key? <2023-10-15> + Null, // TODO(@soispha): Should we keep this key (as it's effectively not a key)?<2023-10-15> Esc, CapsLock, ScrollLock, @@ -34,6 +37,50 @@ pub enum KeyValue { // Media(MediaKeyCode), } +impl FromStr for KeyValue { + type Err = error::KeyValueParseError; + + fn from_str(s: &str) -> Result { + match s { + "BACKSPACE" => Ok(Self::Backspace), + "ENTER" => Ok(Self::Enter), + "LEFT" => Ok(Self::Left), + "RIGHT" => Ok(Self::Right), + "UP" => Ok(Self::Up), + "DOWN" => Ok(Self::Down), + "HOME" => Ok(Self::Home), + "END" => Ok(Self::End), + "PAGEUP" => Ok(Self::PageUp), + "PAGEDOWN" => Ok(Self::PageDown), + "TAB" => Ok(Self::Tab), + "BACKTAB" => Ok(Self::BackTab), + "DELETE" => Ok(Self::Delete), + "INSERT" => Ok(Self::Insert), + "ESC" => Ok(Self::Esc), + "CAPSLOCK" => Ok(Self::CapsLock), + "SCROLLlOCK" => Ok(Self::ScrollLock), + "NUMLOCK" => Ok(Self::NumLock), + "PRINTSCREEN" => Ok(Self::PrintScreen), + "PAUSE" => Ok(Self::Pause), + "MENU" => Ok(Self::Menu), + "KEYPADBEGIN" => Ok(Self::KeypadBegin), + f_str + // FIXME(@soispha): This should check the full string, as something + // like 'FA12' would also match this case <2023-11-07> + if f_str.starts_with("F") + && f_str.ends_with(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) => + { + Ok(Self::F(f_str.trim_matches('F').parse()?)) + } + + "DASH" => Ok(Self::Char('-')), + "ANGULAR_BRACKET_OPEN" | "ABO" => Ok(Self::Char('<')), + "ANGULAR_BRACKET_CLOSE" | "ABC" => Ok(Self::Char('>')), + other_str => return Err(Self::Err::NoMatch(other_str.to_owned())), + } + } +} + impl Display for KeyValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut w = |str| return f.write_str(str); @@ -53,14 +100,11 @@ impl Display for KeyValue { KeyValue::Delete => w(""), KeyValue::Insert => w(""), KeyValue::F(n) => f.write_fmt(format_args!("", n)), - KeyValue::Char(c) => { - match c { - '<' => w(""), - '>' => w(""), - '-' => w(""), - c => f.write_char(*c), - } - + KeyValue::Char(c) => match c { + '<' => w(""), + '>' => w(""), + '-' => w(""), + c => f.write_char(*c), }, KeyValue::Null => w(""), KeyValue::Esc => w(""), @@ -74,70 +118,3 @@ impl Display for KeyValue { } } } - -impl From for KeyValue { - fn from(value: KeyCode) -> Self { - match value { - KeyCode::Backspace => Self::Backspace, - KeyCode::Enter => Self::Enter, - KeyCode::Left => Self::Left, - KeyCode::Right => Self::Right, - KeyCode::Up => Self::Up, - KeyCode::Down => Self::Down, - KeyCode::Home => Self::Home, - KeyCode::End => Self::End, - KeyCode::PageUp => Self::PageUp, - KeyCode::PageDown => Self::PageDown, - KeyCode::Tab => Self::Tab, - KeyCode::BackTab => Self::BackTab, - KeyCode::Delete => Self::Delete, - KeyCode::Insert => Self::Insert, - KeyCode::F(n) => Self::F(n), - KeyCode::Char(c) => Self::Char(c), - KeyCode::Null => Self::Null, - KeyCode::Esc => Self::Esc, - KeyCode::CapsLock => Self::CapsLock, - KeyCode::ScrollLock => Self::ScrollLock, - KeyCode::NumLock => Self::NumLock, - KeyCode::PrintScreen => Self::PrintScreen, - KeyCode::Pause => Self::Pause, - KeyCode::Menu => Self::Menu, - KeyCode::KeypadBegin => Self::KeypadBegin, - // FIXME(@soispha): This reduces our information, casting a KeyCode to a KeyValue - // and back again would not equal the original KeyCode <2023-10-15> - KeyCode::Media(_) => Self::Null, - KeyCode::Modifier(_) => Self::Null, - } - } -} -impl Into for KeyValue { - fn into(self) -> KeyCode { - match self { - Self::Backspace => KeyCode::Backspace, - Self::Enter => KeyCode::Enter, - Self::Left => KeyCode::Left, - Self::Right => KeyCode::Right, - Self::Up => KeyCode::Up, - Self::Down => KeyCode::Down, - Self::Home => KeyCode::Home, - Self::End => KeyCode::End, - Self::PageUp => KeyCode::PageUp, - Self::PageDown => KeyCode::PageDown, - Self::Tab => KeyCode::Tab, - Self::BackTab => KeyCode::BackTab, - Self::Delete => KeyCode::Delete, - Self::Insert => KeyCode::Insert, - Self::F(n) => KeyCode::F(n), - Self::Char(c) => KeyCode::Char(c), - Self::Null => KeyCode::Null, - Self::Esc => KeyCode::Esc, - Self::CapsLock => KeyCode::CapsLock, - Self::ScrollLock => KeyCode::ScrollLock, - Self::NumLock => KeyCode::NumLock, - Self::PrintScreen => KeyCode::PrintScreen, - Self::Pause => KeyCode::Pause, - Self::Menu => KeyCode::Menu, - Self::KeypadBegin => KeyCode::KeypadBegin, - } - } -} diff --git a/keymaps/src/key/keys.pest b/keymaps/src/key/keys.pest new file mode 100644 index 0000000..6527bde --- /dev/null +++ b/keymaps/src/key/keys.pest @@ -0,0 +1,60 @@ +angular_bracket_open = _{ "<" } +abo = _{ angular_bracket_open } +angular_bracket_close = _{ ">" } +abc = _{ angular_bracket_close } + +special_key = { + "BACKSPACE" + | "ENTER" + | "LEFT" + | "RIGHT" + | "UP" + | "DOWN" + | "HOME" + | "END" + | "PAGEUP" + | "PAGEDOWN" + | "TAB" + | "BACKTAB" + | "DELETE" + | "INSERT" + | "ESC" + | "CAPSLOCK" + | "SCROLLlOCK" + | "NUMLOCK" + | "PRINTSCREEN" + | "PAUSE" + | "MENU" + | "KEYPADBEGIN" + | ("F" ~ ASCII_DIGIT+) + + // Aliases to make using them easier, as they are also used in the notation + | "DASH" + | "ANGULAR_BRACKET_OPEN" + | "ABO" + | "ANGULAR_BRACKET_CLOSE" + | "ABC" +} + +modifier = { + "A" | // Alt + "C" | // Ctrl + "M" | // Meta + "S" // Shift +} + +value = { + !abo ~ ANY +} + +raw_key = { + ((abo ~ (#modifiers = modifier)+ ~ "-" ~ (#special_key = (abo ~ special_key ~ abc) | #value = value) ~ abc) | #special_key = (abo ~ special_key ~ abc) | #value = value) +} + +// api +key = { + SOI ~ #key = raw_key ~ EOI +} +keys = { + SOI ~ (#keys = raw_key)+ ~ EOI +} diff --git a/keymaps/src/key/keys.rs b/keymaps/src/key/keys.rs index 746465c..1eabe12 100644 --- a/keymaps/src/key/keys.rs +++ b/keymaps/src/key/keys.rs @@ -1,10 +1,14 @@ -use anyhow::Context; +use pest::Parser; +use pest_derive::Parser; -use super::{Chars, Key, KeyValue}; +use crate::error; + +use super::Key; use std::{fmt::Display, str::FromStr}; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Parser)] +#[grammar = "./key/keys.pest"] pub struct Keys(pub(super) Vec); impl Keys { @@ -90,28 +94,37 @@ impl Display for Keys { } impl FromStr for Keys { - type Err = anyhow::Error; + type Err = error::KeysParseError; fn from_str(s: &str) -> Result { let mut output: Vec = vec![]; - let mut chars = Chars(s.chars().collect()); - while let Some(char) = chars.pop() { - match char { - '<' => { - chars.prepend('<'); - let key = Key::parse(&mut chars) - .with_context(|| format!("Failed to parse keymapping ('{}')", &chars))?; - output.push(key) - } - other_char => output.push(Key { - alt: false, - ctrl: false, - meta: false, - shift: false, - value: Some(KeyValue::Char(other_char)), - }), - } + let parsed = Self::parse(Rule::keys, s)?; + + let keys: Vec<_> = parsed.find_tagged("keys").collect(); + + for key in keys { + let output_key: Key = Key::from_pair(key); + output.push(output_key) } + + // let mut chars = Chars(s.chars().collect()); + // while let Some(char) = chars.pop() { + // match char { + // '<' => { + // chars.prepend('<'); + // let key = Key::parse(&mut chars) + // .with_context(|| format!("Failed to parse keymapping ('{}')", &chars))?; + // output.push(key) + // } + // other_char => output.push(Key { + // alt: false, + // ctrl: false, + // meta: false, + // shift: false, + // value: Some(KeyValue::Char(other_char)), + // }), + // } + // } Ok(Keys(output)) } } diff --git a/keymaps/src/key/mod.rs b/keymaps/src/key/mod.rs index 4a02938..44d3a2b 100644 --- a/keymaps/src/key/mod.rs +++ b/keymaps/src/key/mod.rs @@ -1,18 +1,15 @@ -pub mod key; -pub mod keys; pub mod chars; +pub mod key; pub mod key_value; +pub mod keys; -pub use key::*; -pub use keys::*; -pub(self) use chars::*; +pub use key::Key; pub use key_value::*; - +pub use keys::Keys; #[cfg(test)] mod test { - use crate::app::keymappings::key::{Key, KeyValue}; - use anyhow::Error; + use crate::key::{Key, KeyValue}; use pretty_assertions::assert_eq; use super::Keys; @@ -80,7 +77,10 @@ mod test { shift: true, value: Some(KeyValue::Char('<')), }; - assert_eq!(">".to_owned(), key.to_string_repr()); + assert_eq!( + ">".to_owned(), + key.to_string_repr() + ); } #[test] @@ -124,13 +124,28 @@ mod test { #[test] fn test_false_pattern() { - let keys: Result = "".parse(); + let keys: Result = "".parse(); assert!(keys.is_err()) } + #[test] + fn test_f_keys() { + let key: Key = ">".parse().unwrap(); + let expected = Key { + alt: false, + ctrl: true, + meta: false, + shift: false, + value: Some(KeyValue::F(255)), + }; + assert_eq!(key, expected); + } + #[test] fn test_complex() { - let keys: Keys = ">a 🙂".parse().unwrap(); + let keys: Keys = ">a 🙂" + .parse() + .unwrap_or_else(|e| panic!("{}", e)); assert_eq!( keys, Keys(vec![ diff --git a/keymaps/src/lib.rs b/keymaps/src/lib.rs index 1346c2a..6c1d071 100644 --- a/keymaps/src/lib.rs +++ b/keymaps/src/lib.rs @@ -1,2 +1,3 @@ pub mod key; pub mod trie; +pub mod error; diff --git a/keymaps/src/trie.rs b/keymaps/src/trie.rs index cc905eb..5b65ec9 100644 --- a/keymaps/src/trie.rs +++ b/keymaps/src/trie.rs @@ -1,19 +1,20 @@ use std::collections::HashMap; -use anyhow::bail; -use cli_log::info; +use log::info; + +use crate::error; use super::key::{Key, Keys}; #[derive(Debug, PartialEq, Eq)] -pub struct Node { +pub struct Node> { children: HashMap>>, value: Option, is_terminal: bool, is_child: bool, } -impl Node { +impl> Node { pub fn new() -> Node { Node { children: HashMap::new(), @@ -87,7 +88,7 @@ impl Node { /// Insert a key value pair into the trie. The key is supplied as a string to facilitate the /// creation of nested nodes. - pub fn insert(&mut self, keys: &Keys, value: V) -> anyhow::Result<()> { + pub fn insert(&mut self, keys: &Keys, value: V) -> Result<(), error::TrieInsertError> { let mut current_node = self; for char in keys { let child_node = current_node @@ -95,25 +96,22 @@ impl Node { .entry(char.to_owned()) .or_insert(Box::new(Node::new_child())); if current_node.value.is_some() { - bail!( - "The key ('{}') contains nodes, which already have a value set!", - keys - ); + return Err(error::TrieInsertError::KeyPathBlocked(keys.to_owned())); } current_node.is_terminal = false; current_node = child_node } if current_node.value.is_some() { - bail!( - "The key ('{}') is already set! The value is: '{}'", - keys, - current_node.value.as_ref().expect("This should be set") - ) + return Err(error::TrieInsertError::KeyAlreadySet { + key: keys.to_owned(), + value: current_node + .value + .as_ref() + .expect("This should be set") + .to_owned(), + }); } else if current_node.children.len() > 0 { - bail!( - "The node accessed by this key ('{}') has children! You can't set a value for it", - keys - ) + return Err(error::TrieInsertError::NodeHasChildren(keys.to_owned())); } else { current_node.value = Some(value); Ok(()) @@ -145,7 +143,7 @@ impl Node { #[cfg(test)] mod test { - use crate::app::keymappings::key::{Key, Keys}; + use crate::key::{Key, Keys}; use pretty_assertions::assert_eq; use super::Node; @@ -156,7 +154,9 @@ mod test { fn k(str: &str) -> Key { str.parse::().unwrap() } - fn collect(nodes: Option<(Vec<&Box>>, bool)>) -> Vec<&V> { + fn collect>( + nodes: Option<(Vec<&Box>>, bool)>, + ) -> Vec<&V> { let (nodes, _should_call) = nodes.unwrap(); nodes .iter() @@ -218,7 +218,7 @@ mod test { output.insert(&i("ace"), true).unwrap(); let a_children = &output.children.get(&k("a")).unwrap(); - let output_nodes = vec![&a_children.children[&k("a")], &a_children.children[&k("b")]]; + let output_nodes = vec![&a_children.children[&k("b")], &a_children.children[&k("a")]]; let (nodes, _should_call) = trie.get(&i("a")).unwrap(); assert_eq!(nodes, output_nodes);