From 81b71950802bec610595e211a53b7889f48cc769 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 27 Nov 2019 01:44:26 +0200 Subject: [PATCH] ui: add Ctrl-* Alt-* and F1..F12 parsers and tests --- ui/src/terminal/keys.rs | 129 ++++++++++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 18 deletions(-) diff --git a/ui/src/terminal/keys.rs b/ui/src/terminal/keys.rs index 6051927d..b112dded 100644 --- a/ui/src/terminal/keys.rs +++ b/ui/src/terminal/keys.rs @@ -267,7 +267,8 @@ impl<'de> Deserialize<'de> for Key { type Value = Key; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("`secs` or `nanos`") + formatter + .write_str("a valid key value. Please consult the manual for valid key inputs.") } fn visit_str(self, value: &str) -> Result @@ -275,20 +276,61 @@ impl<'de> Deserialize<'de> for Key { E: de::Error, { match value { - "Backspace" => Ok(Key::Backspace), - "Left" => Ok(Key::Left), - "Right" => Ok(Key::Right), - "Up" => Ok(Key::Up), - "Down" => Ok(Key::Down), - "Home" => Ok(Key::Home), - "End" => Ok(Key::End), - "PageUp" => Ok(Key::PageUp), - "PageDown" => Ok(Key::PageDown), - "Delete" => Ok(Key::Delete), - "Insert" => Ok(Key::Insert), - "Esc" => Ok(Key::Esc), + "Backspace" | "backspace" => Ok(Key::Backspace), + "Left" | "left" => Ok(Key::Left), + "Right" | "right" => Ok(Key::Right), + "Up" | "up" => Ok(Key::Up), + "Down" | "down" => Ok(Key::Down), + "Home" | "home" => Ok(Key::Home), + "End" | "end" => Ok(Key::End), + "PageUp" | "pageup" => Ok(Key::PageUp), + "PageDown" | "pagedown" => Ok(Key::PageDown), + "Delete" | "delete" => Ok(Key::Delete), + "Insert" | "insert" => Ok(Key::Insert), + "Enter" | "enter" => Ok(Key::Char('\n')), + "Tab" | "tab" => Ok(Key::Char('\t')), + "Esc" | "esc" => Ok(Key::Esc), ref s if s.len() == 1 => Ok(Key::Char(s.chars().nth(0).unwrap())), - _ => Err(de::Error::unknown_field(value, FIELDS)), + ref s if s.starts_with("F") && (s.len() == 2 || s.len() == 3) => { + use std::str::FromStr; + + if let Ok(n) = u8::from_str(&s[1..]) { + if n >= 1 && n <= 12 { + return Ok(Key::F(n)); + } + } + Err(de::Error::custom(format!( + "`{}` should be a number 1 <= n <= 12 instead.", + &s[1..] + ))) + } + ref s if s.starts_with("M-") && s.len() == 3 => { + let c = s.as_bytes()[2] as char; + + if c.is_lowercase() || c.is_numeric() { + return Ok(Key::Alt(c)); + } + + Err(de::Error::custom(format!( + "`{}` should be a lowercase and alphanumeric character instead.", + &s[2..] + ))) + } + ref s if s.starts_with("C-") && s.len() == 3 => { + let c = s.as_bytes()[2] as char; + + if c.is_lowercase() || c.is_numeric() { + return Ok(Key::Ctrl(c)); + } + Err(de::Error::custom(format!( + "`{}` should be a lowercase and alphanumeric character instead.", + &s[2..] + ))) + } + _ => Err(de::Error::custom(format!( + "Cannot derive shortcut from `{}`. Please consult the manual for valid key inputs.", + value + ))), } } } @@ -315,14 +357,65 @@ impl Serialize for Key { Key::Delete => serializer.serialize_str("Delete"), Key::Insert => serializer.serialize_str("Insert"), Key::Esc => serializer.serialize_str("Esc"), + Key::Char('\n') => serializer.serialize_str("Enter"), + Key::Char('\t') => serializer.serialize_str("Tab"), Key::Char(c) => serializer.serialize_char(*c), Key::F(n) => serializer.serialize_str(&format!("F{}", n)), Key::Alt(c) => serializer.serialize_str(&format!("M-{}", c)), Key::Ctrl(c) => serializer.serialize_str(&format!("C-{}", c)), - v => Err(serde::ser::Error::custom(format!( - "`{}` is not a valid key", - v - ))), + Key::Null => serializer.serialize_str("Null"), + Key::Paste(s) => serializer.serialize_str(s), } } } + +#[test] +fn test_key_serde() { + #[derive(Debug, Deserialize, PartialEq)] + struct V { + k: Key, + } + + macro_rules! test_key { + ($s:literal, ok $v:expr) => { + assert_eq!( + toml::from_str::(std::concat!("k = \"", $s, "\"")), + Ok(V { k: $v }) + ); + }; + ($s:literal, err $v:literal) => { + assert_eq!( + toml::from_str::(std::concat!("k = \"", $s, "\"")) + .unwrap_err() + .to_string(), + $v.to_string() + ); + }; + } + test_key!("Backspace", ok Key::Backspace); + test_key!("Left", ok Key::Left ); + test_key!("Right", ok Key::Right); + test_key!("Up", ok Key::Up ); + test_key!("Down", ok Key::Down ); + test_key!("Home", ok Key::Home ); + test_key!("End", ok Key::End ); + test_key!("PageUp", ok Key::PageUp ); + test_key!("PageDown", ok Key::PageDown ); + test_key!("Delete", ok Key::Delete ); + test_key!("Insert", ok Key::Insert ); + test_key!("Enter", ok Key::Char('\n') ); + test_key!("Tab", ok Key::Char('\t') ); + test_key!("k", ok Key::Char('k') ); + test_key!("1", ok Key::Char('1') ); + test_key!("Esc", ok Key::Esc ); + test_key!("C-a", ok Key::Ctrl('a') ); + test_key!("C-1", ok Key::Ctrl('1') ); + test_key!("M-a", ok Key::Alt('a') ); + test_key!("F1", ok Key::F(1) ); + test_key!("F12", ok Key::F(12) ); + test_key!("C-V", err "`V` should be a lowercase and alphanumeric character instead. for key `k` at line 1 column 5"); + test_key!("M-V", err "`V` should be a lowercase and alphanumeric character instead. for key `k` at line 1 column 5"); + test_key!("F13", err "`13` should be a number 1 <= n <= 12 instead. for key `k` at line 1 column 5"); + test_key!("Fc", err "`c` should be a number 1 <= n <= 12 instead. for key `k` at line 1 column 5"); + test_key!("adsfsf", err "Cannot derive shortcut from `adsfsf`. Please consult the manual for valid key inputs. for key `k` at line 1 column 5"); +}