diff --git a/examples/termion.rs b/examples/termion.rs index 371d5ac..098fd4c 100644 --- a/examples/termion.rs +++ b/examples/termion.rs @@ -20,7 +20,7 @@ fn main() { let evt = c.unwrap(); if let Event::Key(key) = evt { - if let Some((k, action)) = bindings.keys.get_key_value(&KeyMap::from(key)) { + if let Some((k, action)) = bindings.0.get_key_value(&KeyMap::from(key)) { if *action == Action::Quit { break; } diff --git a/src/backend/crossterm.rs b/src/backend/crossterm.rs index 1ed3d20..4c9a39c 100644 --- a/src/backend/crossterm.rs +++ b/src/backend/crossterm.rs @@ -2,7 +2,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use serde::{de, Deserialize, Deserializer}; use crate::parser::{self, Key as Keys, Modifier, Node}; -use super::Key; +use super::{Key, NodeModifiers}; pub type KeyMap = Key; @@ -12,7 +12,7 @@ pub fn parse(s: &str) -> Result { impl From for KeyMap { fn from(value: KeyEvent) -> Self { - Self { event: value, node: None } + Self { event: value, node: Some(Node::from(value)) } } } @@ -25,6 +25,38 @@ impl From for KeyMap { } } +impl From for Node { + fn from(value: KeyEvent) -> Self { + match value { + KeyEvent { code, modifiers, .. } => { + let key = match code { + KeyCode::BackTab => Keys::BackTab, + KeyCode::Backspace => Keys::Backspace, + KeyCode::Char(' ') => Keys::Space, + KeyCode::Char(c) => Keys::Char(c), + KeyCode::Delete => Keys::Delete, + KeyCode::Down => Keys::Down, + KeyCode::End => Keys::End, + KeyCode::Enter => Keys::Enter, + KeyCode::Esc => Keys::Esc, + KeyCode::F(n) => Keys::F(n), + KeyCode::Home => Keys::Home, + KeyCode::Insert => Keys::Insert, + KeyCode::Left => Keys::Left, + KeyCode::PageDown => Keys::PageDown, + KeyCode::PageUp => Keys::PageUp, + KeyCode::Right => Keys::Right, + KeyCode::Tab => Keys::Tab, + KeyCode::Up => Keys::Up, + code => panic!("Unsupport KeyEvent {code:?}"), + }; + + Self { key, modifiers: NodeModifiers::from(modifiers).into() } + } + } + } +} + impl<'a> From<&'a Node> for KeyEvent { fn from(node: &'a Node) -> Self { let key = match node.key { @@ -48,26 +80,37 @@ impl<'a> From<&'a Node> for KeyEvent { Keys::Up => KeyCode::Up.into(), }; - Self::new(key, modifiers(node.modifiers)) + Self::new(key, NodeModifiers::from(node.modifiers).into()) } } -fn modifiers(m: u8) -> KeyModifiers { - let mut mods = KeyModifiers::NONE; - if m & Modifier::Alt as u8 != 0 { - mods |= KeyModifiers::ALT - } - if m & Modifier::Cmd as u8 != 0 { - mods |= KeyModifiers::META - } - if m & Modifier::Ctrl as u8 != 0 { - mods |= KeyModifiers::CONTROL - } - if m & Modifier::Shift as u8 != 0 { - mods |= KeyModifiers::SHIFT +const MODIFIERS: [(KeyModifiers, parser::Modifier); 4] = [ + (KeyModifiers::ALT, Modifier::Alt), + (KeyModifiers::CONTROL, Modifier::Ctrl), + (KeyModifiers::META, Modifier::Cmd), + (KeyModifiers::SHIFT, Modifier::Shift), +]; + +impl From for NodeModifiers { + fn from(value: KeyModifiers) -> Self { + Self(MODIFIERS.into_iter().fold(0, |mut m, (m1, m2)| { + if value.contains(m1) { + m |= m2 as u8; + } + m + })) } +} - mods +impl From for KeyModifiers { + fn from(value: NodeModifiers) -> Self { + MODIFIERS.into_iter().fold(KeyModifiers::NONE, |mut m, (m1, m2)| { + if value.0 & m2 as u8 != 0 { + m|= m1 + } + m + }) + } } /// Deserializes into Key @@ -86,10 +129,10 @@ mod tests { use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use serde::Deserialize; - use crate::backend::{ + use crate::{backend::{ crossterm::{parse, KeyMap}, Key - }; + }, parser::{Node, self}}; #[test] fn test_parse() { @@ -110,6 +153,24 @@ mod tests { }); } + #[test] + fn test_from_key_to_node() { + let alt_a = KeyEvent::new( + KeyCode::Char('a'), + KeyModifiers::ALT | KeyModifiers::CONTROL | KeyModifiers::SHIFT, + ); + + [ + (KeyEvent::from(KeyCode::Char('[')), "["), + (KeyEvent::from(KeyCode::Delete), "del"), + (alt_a, "alt-ctrl-shift-a"), + ] + .map(|(key, code)| { + let node = parser::parse(code).unwrap(); + assert_eq!(Node::from(key), node); + }); + } + #[test] fn test_deserialize() { use std::collections::HashMap; diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 693f93d..93995ee 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -13,7 +13,7 @@ mod termion; #[cfg(feature = "termion")] pub use self::termion::{KeyMap, parse}; -use crate::parser::Node; +use crate::parser::{Node, Modifiers}; #[derive(Debug, Eq)] pub struct Key { @@ -41,3 +41,19 @@ impl Display for KeyMap { } } } + +/// A wrapper that allows conversion between backend's modifier +/// and Node's modifier. +struct NodeModifiers(Modifiers); + +impl From for Modifiers { + fn from(value: NodeModifiers) -> Self { + value.0 + } +} + +impl From for NodeModifiers { + fn from(value: Modifiers) -> Self { + Self(value) + } +} diff --git a/src/backend/termion.rs b/src/backend/termion.rs index 310c44f..b47a2f7 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -16,11 +16,41 @@ impl From for KeyMap { fn from(value: KeyEvent) -> Self { Self { event: value, - node: None, + node: Some(Node::from(value)), } } } +impl From for Node { + fn from(value: KeyEvent) -> Self { + let (key, modifiers) = match value { + KeyEvent::BackTab => (Keys::BackTab, 0), + KeyEvent::Backspace => (Keys::Backspace, 0), + KeyEvent::Delete => (Keys::Delete, 0), + KeyEvent::Down => (Keys::Down, 0), + KeyEvent::End => (Keys::End, 0), + KeyEvent::Esc => (Keys::Esc, 0), + KeyEvent::Home => (Keys::Home, 0), + KeyEvent::F(n) => (Keys::F(n), 0), + KeyEvent::Insert => (Keys::Insert, 0), + KeyEvent::Left => (Keys::Left, 0), + KeyEvent::PageDown => (Keys::PageDown, 0), + KeyEvent::PageUp => (Keys::PageUp, 0), + KeyEvent::Right => (Keys::Right, 0), + KeyEvent::Up => (Keys::Up, 0), + KeyEvent::Char(' ') => (Keys::Space, 0), + KeyEvent::Char('\n') => (Keys::Enter, 0), + KeyEvent::Char('\t') => (Keys::Tab, 0), + KeyEvent::Char(c) => (Keys::Char(c), 0), + KeyEvent::Alt(c) => (Keys::Char(c), Modifier::Alt as u8), + KeyEvent::Ctrl(c) => (Keys::Char(c), Modifier::Ctrl as u8), + key => panic!("Unsupport Key {key:?}"), + }; + + Self { key, modifiers } + } +} + impl From for KeyMap { fn from(node: Node) -> Self { let key = match node.key { @@ -81,7 +111,7 @@ impl<'s> Deserialize<'s> for KeyMap { #[cfg(test)] mod tests { - use crate::{backend::termion::parse, Key}; + use crate::{backend::termion::parse, Key, parser::{self, Node}}; use termion::event::Key as KeyEvent; #[test] @@ -91,7 +121,7 @@ mod tests { ("del", KeyEvent::Delete), ("alt-a", KeyEvent::Alt('a')), ("shift-a", KeyEvent::Char('A')), - ("shift-=", KeyEvent::Char('+')), + ("A", KeyEvent::Char('A')), ("enter", KeyEvent::Char('\n')), ("ctrl-a", KeyEvent::Ctrl('a')), ] @@ -99,4 +129,19 @@ mod tests { assert_eq!(Key::from(node), parse(s).unwrap()); }); } + + #[test] + fn test_from_key_to_node() { + let alt_a = KeyEvent::Alt('a'); + + [ + (KeyEvent::Char('['), "["), + (KeyEvent::Delete, "del"), + (alt_a, "alt-a"), + ] + .map(|(key, code)| { + let node = parser::parse(code).unwrap(); + assert_eq!(Node::from(key), node); + }); + } } diff --git a/src/parser.rs b/src/parser.rs index ad940f0..9e178e2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -36,7 +36,8 @@ pub(crate) enum Modifier { Shift = 0b1000, } -type Modifiers = u8; +pub(crate) type Modifiers = u8; + const MODIFIERS: [Modifier; 4] = [ Modifier::Alt, Modifier::Cmd,