1use bitflags::bitflags;
43use std::fmt;
44
45bitflags! {
46 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
60 pub struct KeyModifiers: u16 {
61 const SHIFT = 0b0000_0000_0000_0001;
63 const ALT = 0b0000_0000_0000_0010;
65 const CTRL = 0b0000_0000_0000_0100;
67 const META = 0b0000_0000_0000_1000;
69 const HYPER = 0b0000_0000_0001_0000;
71 const SUPER = 0b0000_0000_0010_0000;
73 const CAPS_LOCK = 0b0000_0000_0100_0000;
75 const NUM_LOCK = 0b0000_0000_1000_0000;
77 const SCROLL_LOCK = 0b0000_0001_0000_0000;
79
80 const LOCK_MASK = Self::CAPS_LOCK.bits()
88 | Self::NUM_LOCK.bits()
89 | Self::SCROLL_LOCK.bits();
90 }
91}
92
93#[derive(Debug, Clone)]
111pub struct Key {
112 pub code: KeyCode,
114 pub modifiers: KeyModifiers,
116 pub text: Option<String>,
123 pub shifted_key: Option<char>,
127 pub base_key: Option<char>,
131}
132
133impl PartialEq for Key {
134 fn eq(&self, other: &Self) -> bool {
135 self.code == other.code
136 && self.modifiers.difference(KeyModifiers::LOCK_MASK)
137 == other.modifiers.difference(KeyModifiers::LOCK_MASK)
138 }
139}
140
141impl Eq for Key {}
142
143impl std::hash::Hash for Key {
144 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
145 self.code.hash(state);
146 self.modifiers
147 .difference(KeyModifiers::LOCK_MASK)
148 .hash(state);
149 }
150}
151
152impl Key {
153 pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
162 Self {
163 code,
164 modifiers,
165 text: None,
166 shifted_key: None,
167 base_key: None,
168 }
169 }
170
171 pub fn normalized(mut self) -> Self {
176 self.normalize();
177 self
178 }
179
180 pub fn char(&self) -> Option<char> {
183 match self.code {
184 KeyCode::Char(c) => Some(c),
185 KeyCode::Space => Some(' '),
186 _ => None,
187 }
188 }
189
190 pub fn matches(&self, pattern: &str) -> bool {
229 if let Some(text) = self.text.as_deref()
230 && text == pattern
231 {
232 return true;
233 }
234 pattern.parse::<Key>().is_ok_and(|p| *self == p)
235 }
236
237 pub fn matches_any<I, S>(&self, patterns: I) -> bool
254 where
255 I: IntoIterator<Item = S>,
256 S: AsRef<str>,
257 {
258 patterns.into_iter().any(|p| self.matches(p.as_ref()))
259 }
260
261 pub fn normalize(&mut self) {
276 if let KeyCode::Char(c) = self.code {
278 if c.is_uppercase() {
279 let mut iter = c.to_lowercase();
280 if let Some(lower) = iter.next()
281 && iter.next().is_none()
282 && lower != c
283 {
284 self.code = KeyCode::Char(lower);
285 if self.shifted_key.is_none() {
286 self.shifted_key = Some(c);
287 }
288 if !self.modifiers.contains(KeyModifiers::CAPS_LOCK) {
289 self.modifiers |= KeyModifiers::SHIFT;
290 }
291 }
292 } else if c.is_lowercase()
293 && self
294 .modifiers
295 .intersects(KeyModifiers::SHIFT | KeyModifiers::CAPS_LOCK)
296 && self.shifted_key.is_none()
297 {
298 let mut iter = c.to_uppercase();
299 if let Some(upper) = iter.next()
300 && iter.next().is_none()
301 && upper != c
302 {
303 self.shifted_key = Some(upper);
304 }
305 }
306 }
307
308 if self.text.is_none() {
310 const PRINTABLE_ALLOWED: KeyModifiers = KeyModifiers::SHIFT
311 .union(KeyModifiers::CAPS_LOCK)
312 .union(KeyModifiers::NUM_LOCK);
313 if (self.modifiers - PRINTABLE_ALLOWED).is_empty() {
314 let glyph: Option<char> = match self.code {
334 KeyCode::Char(c) if !c.is_control() => {
335 let shifted = if c.is_lowercase() {
336 self.modifiers
337 .intersects(KeyModifiers::SHIFT | KeyModifiers::CAPS_LOCK)
338 } else {
339 self.modifiers.contains(KeyModifiers::SHIFT)
340 };
341 Some(if shifted {
342 self.shifted_key.unwrap_or(c)
343 } else {
344 c
345 })
346 }
347 KeyCode::Space => Some(' '),
348 _ => None,
349 };
350 if let Some(g) = glyph {
351 self.text = Some(g.to_string());
352 }
353 }
354 }
355 }
356}
357
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
369pub enum KeyCode {
370 Char(char),
376 F(u8),
382 Find,
385 Select,
388 Up,
391 Down,
393 Left,
395 Right,
397 Home,
399 End,
401 PageUp,
403 PageDown,
405 Backspace,
408 Delete,
410 Insert,
412 Tab,
414 Enter,
416 Space,
427 Escape,
430 CapsLock,
432 ScrollLock,
434 NumLock,
436 PrintScreen,
438 Pause,
440 Menu,
442 KpEnter,
445 KpAdd,
447 KpSubtract,
449 KpMultiply,
451 KpDivide,
453 KpDecimal,
455 KpEqual,
457 KpSeparator,
459 KpLeft,
461 KpRight,
463 KpUp,
465 KpDown,
467 KpPageUp,
469 KpPageDown,
471 KpHome,
473 KpEnd,
475 KpInsert,
477 KpDelete,
479 KpBegin,
481 Kp0,
483 Kp1,
485 Kp2,
487 Kp3,
489 Kp4,
491 Kp5,
493 Kp6,
495 Kp7,
497 Kp8,
499 Kp9,
501 MediaPlay,
504 MediaPause,
506 MediaPlayPause,
508 MediaReverse,
510 MediaStop,
512 MediaRewind,
514 MediaFastForward,
516 MediaNext,
518 MediaPrev,
520 MediaRecord,
522 VolumeUp,
524 VolumeDown,
526 VolumeMute,
528 LeftShift,
531 RightShift,
533 LeftCtrl,
535 RightCtrl,
537 LeftAlt,
539 RightAlt,
541 LeftSuper,
543 RightSuper,
545 LeftHyper,
547 RightHyper,
549 LeftMeta,
551 RightMeta,
553 IsoLevel3Shift,
555 IsoLevel5Shift,
557}
558
559impl KeyCode {
560 pub const FUNCTION_KEY_MAX: u8 = 35;
565
566 pub fn function(n: u8) -> Option<KeyCode> {
574 if (1..=Self::FUNCTION_KEY_MAX).contains(&n) {
575 Some(KeyCode::F(n))
576 } else {
577 None
578 }
579 }
580}
581
582impl fmt::Display for Key {
600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601 let mods = self.modifiers;
602 if mods.contains(KeyModifiers::CTRL) {
606 f.write_str("ctrl+")?;
607 }
608 if mods.contains(KeyModifiers::ALT) {
609 f.write_str("alt+")?;
610 }
611 if mods.contains(KeyModifiers::SHIFT) {
612 f.write_str("shift+")?;
613 }
614 if mods.contains(KeyModifiers::SUPER) {
615 f.write_str("super+")?;
616 }
617 if mods.contains(KeyModifiers::HYPER) {
618 f.write_str("hyper+")?;
619 }
620 if mods.contains(KeyModifiers::META) {
621 f.write_str("meta+")?;
622 }
623
624 match self.code {
625 KeyCode::Char('+') => f.write_str("plus"),
626 KeyCode::Char('-') => f.write_str("minus"),
627 KeyCode::Char('=') => f.write_str("equals"),
628 KeyCode::Char(c) => write!(f, "{c}"),
629 KeyCode::F(n) => write!(f, "f{n}"),
630 KeyCode::Up => f.write_str("up"),
631 KeyCode::Down => f.write_str("down"),
632 KeyCode::Left => f.write_str("left"),
633 KeyCode::Right => f.write_str("right"),
634 KeyCode::Home => f.write_str("home"),
635 KeyCode::End => f.write_str("end"),
636 KeyCode::Find => f.write_str("find"),
637 KeyCode::Select => f.write_str("select"),
638 KeyCode::PageUp => f.write_str("pageup"),
639 KeyCode::PageDown => f.write_str("pagedown"),
640 KeyCode::Backspace => f.write_str("backspace"),
641 KeyCode::Delete => f.write_str("delete"),
642 KeyCode::Insert => f.write_str("insert"),
643 KeyCode::Tab => f.write_str("tab"),
644 KeyCode::Enter => f.write_str("enter"),
645 KeyCode::Space => f.write_str("space"),
646 KeyCode::Escape => f.write_str("escape"),
647 KeyCode::CapsLock => f.write_str("capslock"),
648 KeyCode::ScrollLock => f.write_str("scrolllock"),
649 KeyCode::NumLock => f.write_str("numlock"),
650 KeyCode::PrintScreen => f.write_str("printscreen"),
651 KeyCode::Pause => f.write_str("pause"),
652 KeyCode::Menu => f.write_str("menu"),
653 KeyCode::KpEnter => f.write_str("kpenter"),
655 KeyCode::KpAdd => f.write_str("kpadd"),
656 KeyCode::KpSubtract => f.write_str("kpsubtract"),
657 KeyCode::KpMultiply => f.write_str("kpmultiply"),
658 KeyCode::KpDivide => f.write_str("kpdivide"),
659 KeyCode::KpDecimal => f.write_str("kpdecimal"),
660 KeyCode::KpEqual => f.write_str("kpequal"),
661 KeyCode::KpSeparator => f.write_str("kpseparator"),
662 KeyCode::KpLeft => f.write_str("kpleft"),
663 KeyCode::KpRight => f.write_str("kpright"),
664 KeyCode::KpUp => f.write_str("kpup"),
665 KeyCode::KpDown => f.write_str("kpdown"),
666 KeyCode::KpPageUp => f.write_str("kppageup"),
667 KeyCode::KpPageDown => f.write_str("kppagedown"),
668 KeyCode::KpHome => f.write_str("kphome"),
669 KeyCode::KpEnd => f.write_str("kpend"),
670 KeyCode::KpInsert => f.write_str("kpinsert"),
671 KeyCode::KpDelete => f.write_str("kpdelete"),
672 KeyCode::KpBegin => f.write_str("kpbegin"),
673 KeyCode::Kp0 => f.write_str("kp0"),
674 KeyCode::Kp1 => f.write_str("kp1"),
675 KeyCode::Kp2 => f.write_str("kp2"),
676 KeyCode::Kp3 => f.write_str("kp3"),
677 KeyCode::Kp4 => f.write_str("kp4"),
678 KeyCode::Kp5 => f.write_str("kp5"),
679 KeyCode::Kp6 => f.write_str("kp6"),
680 KeyCode::Kp7 => f.write_str("kp7"),
681 KeyCode::Kp8 => f.write_str("kp8"),
682 KeyCode::Kp9 => f.write_str("kp9"),
683 KeyCode::MediaPlay => f.write_str("mediaplay"),
685 KeyCode::MediaPause => f.write_str("mediapause"),
686 KeyCode::MediaPlayPause => f.write_str("mediaplaypause"),
687 KeyCode::MediaReverse => f.write_str("mediareverse"),
688 KeyCode::MediaStop => f.write_str("mediastop"),
689 KeyCode::MediaRewind => f.write_str("mediarewind"),
690 KeyCode::MediaFastForward => f.write_str("mediafastforward"),
691 KeyCode::MediaNext => f.write_str("medianext"),
692 KeyCode::MediaPrev => f.write_str("mediaprev"),
693 KeyCode::MediaRecord => f.write_str("mediarecord"),
694 KeyCode::VolumeUp => f.write_str("volumeup"),
695 KeyCode::VolumeDown => f.write_str("volumedown"),
696 KeyCode::VolumeMute => f.write_str("volumemute"),
697 KeyCode::LeftShift => f.write_str("leftshift"),
699 KeyCode::RightShift => f.write_str("rightshift"),
700 KeyCode::LeftCtrl => f.write_str("leftctrl"),
701 KeyCode::RightCtrl => f.write_str("rightctrl"),
702 KeyCode::LeftAlt => f.write_str("leftalt"),
703 KeyCode::RightAlt => f.write_str("rightalt"),
704 KeyCode::LeftSuper => f.write_str("leftsuper"),
705 KeyCode::RightSuper => f.write_str("rightsuper"),
706 KeyCode::LeftHyper => f.write_str("lefthyper"),
707 KeyCode::RightHyper => f.write_str("righthyper"),
708 KeyCode::LeftMeta => f.write_str("leftmeta"),
709 KeyCode::RightMeta => f.write_str("rightmeta"),
710 KeyCode::IsoLevel3Shift => f.write_str("isolevel3shift"),
711 KeyCode::IsoLevel5Shift => f.write_str("isolevel5shift"),
712 }
713 }
714}
715
716impl fmt::Display for KeyCode {
717 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
718 write!(
720 f,
721 "{}",
722 Key {
723 code: *self,
724 modifiers: KeyModifiers::empty(),
725 text: None,
726 shifted_key: None,
727 base_key: None,
728 }
729 )
730 }
731}
732
733#[derive(Debug, Clone, PartialEq, Eq)]
739pub enum ParseKeyError {
740 Empty,
742 EmptyComponent,
747 UnknownModifier(String),
752 UnknownKey(String),
758 InvalidFunctionKey(String),
763}
764
765impl fmt::Display for ParseKeyError {
766 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767 match self {
768 Self::Empty => f.write_str("empty key string"),
769 Self::EmptyComponent => f.write_str("empty `+`-separated component"),
770 Self::UnknownModifier(s) => write!(f, "unknown modifier: {s:?}"),
771 Self::UnknownKey(s) => write!(f, "unknown key name: {s:?}"),
772 Self::InvalidFunctionKey(s) => write!(f, "invalid function key: {s:?}"),
773 }
774 }
775}
776
777impl std::error::Error for ParseKeyError {}
778
779fn parse_modifier(token: &str) -> Result<KeyModifiers, ParseKeyError> {
780 let mods = if token.eq_ignore_ascii_case("ctrl") || token.eq_ignore_ascii_case("control") {
781 KeyModifiers::CTRL
782 } else if token.eq_ignore_ascii_case("alt")
783 || token.eq_ignore_ascii_case("opt")
784 || token.eq_ignore_ascii_case("option")
785 {
786 KeyModifiers::ALT
787 } else if token.eq_ignore_ascii_case("shift") {
788 KeyModifiers::SHIFT
789 } else if token.eq_ignore_ascii_case("super")
790 || token.eq_ignore_ascii_case("win")
791 || token.eq_ignore_ascii_case("cmd")
792 || token.eq_ignore_ascii_case("command")
793 {
794 KeyModifiers::SUPER
795 } else if token.eq_ignore_ascii_case("hyper") {
796 KeyModifiers::HYPER
797 } else if token.eq_ignore_ascii_case("meta") {
798 KeyModifiers::META
799 } else {
800 return Err(ParseKeyError::UnknownModifier(token.to_string()));
801 };
802 Ok(mods)
803}
804
805const KEY_KEYWORD_MAX_LEN: usize = 16;
812
813fn parse_key_code(token: &str) -> Result<KeyCode, ParseKeyError> {
814 let mut chars = token.chars();
820 if let Some(first) = chars.next()
821 && chars.next().is_none()
822 {
823 return Ok(KeyCode::Char(first));
824 }
825
826 if token.len() > KEY_KEYWORD_MAX_LEN || !token.is_ascii() {
832 return Err(ParseKeyError::UnknownKey(token.to_string()));
833 }
834 let mut buf = [0u8; KEY_KEYWORD_MAX_LEN];
835 for (i, b) in token.as_bytes().iter().enumerate() {
836 buf[i] = b.to_ascii_lowercase();
837 }
838 let lower = std::str::from_utf8(&buf[..token.len()])
839 .expect("ASCII lowercase of ASCII input is valid UTF-8");
840
841 if let Some(rest) = lower.strip_prefix('f')
843 && !rest.is_empty()
844 && rest.bytes().all(|c| c.is_ascii_digit())
845 {
846 let n: u8 = rest
847 .parse()
848 .map_err(|_| ParseKeyError::InvalidFunctionKey(token.to_string()))?;
849 return KeyCode::function(n)
850 .ok_or_else(|| ParseKeyError::InvalidFunctionKey(token.to_string()));
851 }
852
853 Ok(match lower {
854 "up" => KeyCode::Up,
856 "down" => KeyCode::Down,
857 "left" => KeyCode::Left,
858 "right" => KeyCode::Right,
859 "home" => KeyCode::Home,
860 "end" => KeyCode::End,
861 "find" => KeyCode::Find,
862 "select" => KeyCode::Select,
863 "pgup" | "pageup" => KeyCode::PageUp,
864 "pgdn" | "pgdown" | "pagedown" => KeyCode::PageDown,
865 "backspace" | "bs" => KeyCode::Backspace,
867 "delete" | "del" => KeyCode::Delete,
868 "insert" | "ins" => KeyCode::Insert,
869 "tab" => KeyCode::Tab,
870 "enter" | "return" | "ret" => KeyCode::Enter,
871 "space" | "spc" => KeyCode::Space,
873 "esc" | "escape" => KeyCode::Escape,
874 "capslock" => KeyCode::CapsLock,
875 "scrolllock" => KeyCode::ScrollLock,
876 "numlock" => KeyCode::NumLock,
877 "printscreen" | "prtsc" => KeyCode::PrintScreen,
878 "pause" => KeyCode::Pause,
879 "menu" => KeyCode::Menu,
880 "plus" => KeyCode::Char('+'),
886 "minus" | "dash" | "hyphen" => KeyCode::Char('-'),
887 "equals" | "equal" => KeyCode::Char('='),
888 "kpenter" => KeyCode::KpEnter,
890 "kpadd" => KeyCode::KpAdd,
891 "kpsubtract" => KeyCode::KpSubtract,
892 "kpmultiply" => KeyCode::KpMultiply,
893 "kpdivide" => KeyCode::KpDivide,
894 "kpdecimal" => KeyCode::KpDecimal,
895 "kpequal" => KeyCode::KpEqual,
896 "kpseparator" => KeyCode::KpSeparator,
897 "kpleft" => KeyCode::KpLeft,
898 "kpright" => KeyCode::KpRight,
899 "kpup" => KeyCode::KpUp,
900 "kpdown" => KeyCode::KpDown,
901 "kppgup" | "kppageup" => KeyCode::KpPageUp,
902 "kppgdn" | "kppgdown" | "kppagedown" => KeyCode::KpPageDown,
903 "kphome" => KeyCode::KpHome,
904 "kpend" => KeyCode::KpEnd,
905 "kpinsert" => KeyCode::KpInsert,
906 "kpdelete" => KeyCode::KpDelete,
907 "kpbegin" => KeyCode::KpBegin,
908 "kp0" => KeyCode::Kp0,
909 "kp1" => KeyCode::Kp1,
910 "kp2" => KeyCode::Kp2,
911 "kp3" => KeyCode::Kp3,
912 "kp4" => KeyCode::Kp4,
913 "kp5" => KeyCode::Kp5,
914 "kp6" => KeyCode::Kp6,
915 "kp7" => KeyCode::Kp7,
916 "kp8" => KeyCode::Kp8,
917 "kp9" => KeyCode::Kp9,
918 "mediaplay" => KeyCode::MediaPlay,
920 "mediapause" => KeyCode::MediaPause,
921 "mediaplaypause" => KeyCode::MediaPlayPause,
922 "mediareverse" => KeyCode::MediaReverse,
923 "mediastop" => KeyCode::MediaStop,
924 "mediarewind" => KeyCode::MediaRewind,
925 "mediafastforward" => KeyCode::MediaFastForward,
926 "medianext" => KeyCode::MediaNext,
927 "mediaprev" => KeyCode::MediaPrev,
928 "mediarecord" => KeyCode::MediaRecord,
929 "volumeup" => KeyCode::VolumeUp,
930 "volumedown" => KeyCode::VolumeDown,
931 "volumemute" => KeyCode::VolumeMute,
932 "leftshift" => KeyCode::LeftShift,
934 "rightshift" => KeyCode::RightShift,
935 "leftctrl" => KeyCode::LeftCtrl,
936 "rightctrl" => KeyCode::RightCtrl,
937 "leftalt" => KeyCode::LeftAlt,
938 "rightalt" => KeyCode::RightAlt,
939 "leftsuper" => KeyCode::LeftSuper,
940 "rightsuper" => KeyCode::RightSuper,
941 "lefthyper" => KeyCode::LeftHyper,
942 "righthyper" => KeyCode::RightHyper,
943 "leftmeta" => KeyCode::LeftMeta,
944 "rightmeta" => KeyCode::RightMeta,
945 "isolevel3shift" => KeyCode::IsoLevel3Shift,
946 "isolevel5shift" => KeyCode::IsoLevel5Shift,
947 _ => return Err(ParseKeyError::UnknownKey(token.to_string())),
948 })
949}
950
951impl std::str::FromStr for Key {
952 type Err = ParseKeyError;
953
954 fn from_str(s: &str) -> Result<Self, Self::Err> {
955 let s = s.trim();
956 if s.is_empty() {
957 return Err(ParseKeyError::Empty);
958 }
959
960 if s.chars().nth(1).is_none() {
964 let code = parse_key_code(s)?;
965 return Ok(Key::new(code, KeyModifiers::empty()).normalized());
966 }
967
968 let (mod_part, key_part) = if let Some(head) = s.strip_suffix('+') {
976 match head.strip_suffix('+') {
977 Some(mods) => (mods, "+"),
978 None => return Err(ParseKeyError::EmptyComponent),
979 }
980 } else {
981 match s.rsplit_once('+') {
982 Some(("", _)) => return Err(ParseKeyError::EmptyComponent),
983 Some(parts) => parts,
984 None => ("", s),
985 }
986 };
987
988 if key_part.is_empty() {
989 return Err(ParseKeyError::EmptyComponent);
990 }
991
992 let mut modifiers = KeyModifiers::empty();
993 if !mod_part.is_empty() {
994 for tok in mod_part.split('+') {
995 if tok.is_empty() {
996 return Err(ParseKeyError::EmptyComponent);
997 }
998 modifiers |= parse_modifier(tok)?;
999 }
1000 }
1001
1002 if key_part.eq_ignore_ascii_case("backtab") {
1008 return Ok(Key::new(KeyCode::Tab, modifiers | KeyModifiers::SHIFT).normalized());
1009 }
1010
1011 let code = parse_key_code(key_part)?;
1012 Ok(Key::new(code, modifiers).normalized())
1013 }
1014}
1015
1016impl std::str::FromStr for KeyCode {
1017 type Err = ParseKeyError;
1018
1019 fn from_str(s: &str) -> Result<Self, Self::Err> {
1020 let s = s.trim();
1021 if s.is_empty() {
1022 return Err(ParseKeyError::Empty);
1023 }
1024 parse_key_code(s)
1025 }
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030 use super::*;
1031
1032 #[test]
1033 fn test_key_display() {
1034 let k = Key::new(KeyCode::Char('a'), KeyModifiers::CTRL).normalized();
1035 assert_eq!(k.to_string(), "ctrl+a");
1036 }
1037
1038 #[test]
1039 fn test_key_display_function() {
1040 let k = Key::new(KeyCode::F(12), KeyModifiers::empty()).normalized();
1041 assert_eq!(k.to_string(), "f12");
1042 }
1043
1044 #[test]
1045 fn test_key_char() {
1046 let k = Key::new(KeyCode::Char('x'), KeyModifiers::empty()).normalized();
1047 assert_eq!(k.char(), Some('x'));
1048 }
1049
1050 #[test]
1051 fn test_key_char_special() {
1052 let k = Key::new(KeyCode::Enter, KeyModifiers::empty()).normalized();
1053 assert_eq!(k.char(), None);
1054 }
1055
1056 #[test]
1057 fn new_uppercase_ascii_lowers_code_and_adds_shift() {
1058 let k = Key::new(KeyCode::Char('A'), KeyModifiers::empty()).normalized();
1059 assert_eq!(k.code, KeyCode::Char('a'));
1060 assert_eq!(k.shifted_key, Some('A'));
1061 assert!(k.modifiers.contains(KeyModifiers::SHIFT));
1062 assert_eq!(k.text.as_deref(), Some("A"));
1063 }
1064
1065 #[test]
1066 fn new_uppercase_with_caps_lock_does_not_add_shift() {
1067 let k = Key::new(KeyCode::Char('A'), KeyModifiers::CAPS_LOCK).normalized();
1068 assert_eq!(k.code, KeyCode::Char('a'));
1069 assert_eq!(k.shifted_key, Some('A'));
1070 assert!(!k.modifiers.contains(KeyModifiers::SHIFT));
1071 assert!(k.modifiers.contains(KeyModifiers::CAPS_LOCK));
1072 assert_eq!(k.text.as_deref(), Some("A"));
1073 }
1074
1075 #[test]
1076 fn new_lowercase_with_shift_populates_shifted_key() {
1077 let k = Key::new(KeyCode::Char('a'), KeyModifiers::SHIFT).normalized();
1078 assert_eq!(k.code, KeyCode::Char('a'));
1079 assert_eq!(k.shifted_key, Some('A'));
1080 assert_eq!(k.text.as_deref(), Some("A"));
1081 }
1082
1083 #[test]
1084 fn shifted_key_does_not_pollute_text_without_shift() {
1085 let mut k = Key {
1090 code: KeyCode::Char('a'),
1091 modifiers: KeyModifiers::empty(),
1092 text: None,
1093 shifted_key: Some('A'),
1094 base_key: None,
1095 };
1096 k.normalize();
1097 assert_eq!(k.text.as_deref(), Some("a"));
1098 let mut k = Key {
1100 code: KeyCode::Char('a'),
1101 modifiers: KeyModifiers::SHIFT,
1102 text: None,
1103 shifted_key: Some('A'),
1104 base_key: None,
1105 };
1106 k.normalize();
1107 assert_eq!(k.text.as_deref(), Some("A"));
1108 }
1109
1110 #[test]
1111 fn caps_lock_shifts_cased_letters_only() {
1112 let mut k = Key {
1117 code: KeyCode::Char('2'),
1118 modifiers: KeyModifiers::CAPS_LOCK,
1119 text: None,
1120 shifted_key: Some('@'),
1121 base_key: None,
1122 };
1123 k.normalize();
1124 assert_eq!(k.text.as_deref(), Some("2"));
1125
1126 let mut k = Key {
1128 code: KeyCode::Char('a'),
1129 modifiers: KeyModifiers::CAPS_LOCK,
1130 text: None,
1131 shifted_key: Some('A'),
1132 base_key: None,
1133 };
1134 k.normalize();
1135 assert_eq!(k.text.as_deref(), Some("A"));
1136
1137 let mut k = Key {
1141 code: KeyCode::Char('ä'),
1142 modifiers: KeyModifiers::CAPS_LOCK,
1143 text: None,
1144 shifted_key: Some('Ä'),
1145 base_key: None,
1146 };
1147 k.normalize();
1148 assert_eq!(k.text.as_deref(), Some("Ä"));
1149
1150 let mut k = Key {
1151 code: KeyCode::Char('д'),
1152 modifiers: KeyModifiers::CAPS_LOCK,
1153 text: None,
1154 shifted_key: Some('Д'),
1155 base_key: None,
1156 };
1157 k.normalize();
1158 assert_eq!(k.text.as_deref(), Some("Д"));
1159
1160 let mut k = Key {
1162 code: KeyCode::Char('2'),
1163 modifiers: KeyModifiers::SHIFT,
1164 text: None,
1165 shifted_key: Some('@'),
1166 base_key: None,
1167 };
1168 k.normalize();
1169 assert_eq!(k.text.as_deref(), Some("@"));
1170 }
1171
1172 #[test]
1173 fn new_lowercase_without_shift_populates_text() {
1174 let k = Key::new(KeyCode::Char('a'), KeyModifiers::empty()).normalized();
1175 assert_eq!(k.code, KeyCode::Char('a'));
1176 assert_eq!(k.shifted_key, None);
1177 assert!(k.modifiers.is_empty());
1178 assert_eq!(k.text.as_deref(), Some("a"));
1179 }
1180
1181 #[test]
1182 fn new_ctrl_uppercase_does_not_set_text() {
1183 let k = Key::new(KeyCode::Char('A'), KeyModifiers::CTRL).normalized();
1184 assert_eq!(k.code, KeyCode::Char('a'));
1185 assert_eq!(k.shifted_key, Some('A'));
1186 assert!(k.modifiers.contains(KeyModifiers::CTRL));
1187 assert!(k.modifiers.contains(KeyModifiers::SHIFT));
1188 assert!(k.text.is_none());
1189 }
1190
1191 #[test]
1192 fn new_ctrl_shift_lowercase_does_not_set_text() {
1193 let k = Key::new(KeyCode::Char('a'), KeyModifiers::CTRL | KeyModifiers::SHIFT).normalized();
1194 assert_eq!(k.code, KeyCode::Char('a'));
1195 assert_eq!(k.shifted_key, Some('A'));
1196 assert!(k.text.is_none());
1197 }
1198
1199 #[test]
1200 fn new_cyrillic_uppercase() {
1201 let k = Key::new(KeyCode::Char('Ц'), KeyModifiers::empty()).normalized();
1202 assert_eq!(k.code, KeyCode::Char('ц'));
1203 assert_eq!(k.shifted_key, Some('Ц'));
1204 assert!(k.modifiers.contains(KeyModifiers::SHIFT));
1205 assert_eq!(k.text.as_deref(), Some("Ц"));
1206 }
1207
1208 #[test]
1209 fn new_greek_lowercase_with_shift() {
1210 let k = Key::new(KeyCode::Char('α'), KeyModifiers::SHIFT).normalized();
1211 assert_eq!(k.code, KeyCode::Char('α'));
1212 assert_eq!(k.shifted_key, Some('Α'));
1213 assert_eq!(k.text.as_deref(), Some("Α"));
1214 }
1215
1216 #[test]
1217 fn new_multi_codepoint_lower_left_alone() {
1218 let k = Key::new(KeyCode::Char('İ'), KeyModifiers::empty()).normalized();
1220 assert_eq!(k.code, KeyCode::Char('İ'));
1221 assert_eq!(k.shifted_key, None);
1222 assert!(k.modifiers.is_empty());
1223 assert_eq!(k.text.as_deref(), Some("İ"));
1225 }
1226
1227 #[test]
1228 fn new_titlecase_digraph_left_alone() {
1229 let k = Key::new(KeyCode::Char('Dž'), KeyModifiers::empty()).normalized();
1231 assert_eq!(k.code, KeyCode::Char('Dž'));
1232 assert_eq!(k.shifted_key, None);
1233 assert_eq!(k.text.as_deref(), Some("Dž"));
1234 }
1235
1236 #[test]
1237 fn new_digit_with_shift_keeps_digit_text() {
1238 let k = Key::new(KeyCode::Char('1'), KeyModifiers::SHIFT).normalized();
1240 assert_eq!(k.code, KeyCode::Char('1'));
1241 assert_eq!(k.shifted_key, None);
1242 assert!(k.modifiers.contains(KeyModifiers::SHIFT));
1243 assert_eq!(k.text.as_deref(), Some("1"));
1244 }
1245
1246 #[test]
1247 fn new_non_char_no_text() {
1248 let k = Key::new(KeyCode::Enter, KeyModifiers::SHIFT).normalized();
1249 assert_eq!(k.code, KeyCode::Enter);
1250 assert_eq!(k.shifted_key, None);
1251 assert!(k.text.is_none());
1252 }
1253
1254 #[test]
1255 fn new_space_populates_text() {
1256 let k = Key::new(KeyCode::Space, KeyModifiers::empty()).normalized();
1257 assert_eq!(k.code, KeyCode::Space);
1258 assert_eq!(k.text.as_deref(), Some(" "));
1259 }
1260
1261 #[test]
1262 fn new_space_with_ctrl_no_text() {
1263 let k = Key::new(KeyCode::Space, KeyModifiers::CTRL).normalized();
1264 assert_eq!(k.code, KeyCode::Space);
1265 assert!(k.text.is_none());
1266 }
1267
1268 #[test]
1269 fn direct_field_mutation_overrides_auto_text() {
1270 let mut k = Key::new(KeyCode::Char('2'), KeyModifiers::SHIFT).normalized();
1271 k.shifted_key = Some('@');
1273 k.text = Some("@".to_string());
1274 assert_eq!(k.text.as_deref(), Some("@"));
1275 assert_eq!(k.shifted_key, Some('@'));
1276 }
1277
1278 #[test]
1279 fn eq_ignores_informational_fields() {
1280 let bare = Key::new(KeyCode::Char('a'), KeyModifiers::SHIFT).normalized();
1281 let mut decorated = Key::new(KeyCode::Char('a'), KeyModifiers::SHIFT).normalized();
1282 decorated.text = Some("custom".to_string());
1283 decorated.shifted_key = Some('Z');
1284 decorated.base_key = Some('q');
1285 assert_eq!(bare, decorated);
1286 }
1287
1288 #[test]
1289 fn hash_ignores_informational_fields() {
1290 use std::collections::HashMap;
1291 let mut map: HashMap<Key, &'static str> = HashMap::new();
1292 map.insert(
1293 Key::new(KeyCode::Char('a'), KeyModifiers::CTRL).normalized(),
1294 "ctrl-a",
1295 );
1296
1297 let mut lookup = Key::new(KeyCode::Char('a'), KeyModifiers::CTRL).normalized();
1298 lookup.text = Some("ignored".to_string());
1299 lookup.shifted_key = Some('X');
1300 assert_eq!(map.get(&lookup), Some(&"ctrl-a"));
1301 }
1302
1303 #[test]
1304 fn eq_distinguishes_code_and_modifiers() {
1305 let a = Key::new(KeyCode::Char('a'), KeyModifiers::empty()).normalized();
1306 let ctrl_a = Key::new(KeyCode::Char('a'), KeyModifiers::CTRL).normalized();
1307 let b = Key::new(KeyCode::Char('b'), KeyModifiers::empty()).normalized();
1308 assert_ne!(a, ctrl_a);
1309 assert_ne!(a, b);
1310 }
1311
1312 #[test]
1313 fn display_canonical_mod_order() {
1314 let mods = KeyModifiers::META
1318 | KeyModifiers::HYPER
1319 | KeyModifiers::SUPER
1320 | KeyModifiers::SHIFT
1321 | KeyModifiers::ALT
1322 | KeyModifiers::CTRL;
1323 let k = Key {
1324 code: KeyCode::Char('a'),
1325 modifiers: mods,
1326 text: None,
1327 shifted_key: None,
1328 base_key: None,
1329 };
1330 assert_eq!(k.to_string(), "ctrl+alt+shift+super+hyper+meta+a");
1331 }
1332
1333 #[test]
1334 fn display_omits_lock_state() {
1335 let k = Key {
1336 code: KeyCode::Char('a'),
1337 modifiers: KeyModifiers::CAPS_LOCK | KeyModifiers::NUM_LOCK | KeyModifiers::SCROLL_LOCK,
1338 text: None,
1339 shifted_key: None,
1340 base_key: None,
1341 };
1342 assert_eq!(k.to_string(), "a");
1343 }
1344
1345 #[test]
1346 fn display_lock_state_combined_with_binding_mod() {
1347 let k = Key {
1350 code: KeyCode::Char('a'),
1351 modifiers: KeyModifiers::CTRL | KeyModifiers::CAPS_LOCK,
1352 text: None,
1353 shifted_key: None,
1354 base_key: None,
1355 };
1356 assert_eq!(k.to_string(), "ctrl+a");
1357 }
1358
1359 #[test]
1360 fn display_char_plus_uses_word() {
1361 let k = Key::new(KeyCode::Char('+'), KeyModifiers::CTRL).normalized();
1362 assert_eq!(k.to_string(), "ctrl+plus");
1363 }
1364
1365 #[test]
1366 fn display_modifier_key_variants() {
1367 let k = Key::new(KeyCode::LeftShift, KeyModifiers::empty()).normalized();
1368 assert_eq!(k.to_string(), "leftshift");
1369 let k = Key::new(KeyCode::RightAlt, KeyModifiers::empty()).normalized();
1370 assert_eq!(k.to_string(), "rightalt");
1371 let k = Key::new(KeyCode::IsoLevel3Shift, KeyModifiers::empty()).normalized();
1372 assert_eq!(k.to_string(), "isolevel3shift");
1373 }
1374
1375 #[test]
1376 fn display_uses_full_words() {
1377 assert_eq!(
1380 Key::new(KeyCode::PageUp, KeyModifiers::empty())
1381 .normalized()
1382 .to_string(),
1383 "pageup"
1384 );
1385 assert_eq!(
1386 Key::new(KeyCode::PageDown, KeyModifiers::empty())
1387 .normalized()
1388 .to_string(),
1389 "pagedown"
1390 );
1391 assert_eq!(
1392 Key::new(KeyCode::Escape, KeyModifiers::empty())
1393 .normalized()
1394 .to_string(),
1395 "escape"
1396 );
1397 assert_eq!(parse("pgup"), parse("pageup"));
1400 assert_eq!(parse("pgdn"), parse("pagedown"));
1401 assert_eq!(parse("pgdown"), parse("pagedown"));
1402 assert_eq!(parse("esc"), parse("escape"));
1403 }
1404
1405 #[test]
1406 fn display_keypad_variants() {
1407 assert_eq!(
1408 Key::new(KeyCode::Kp0, KeyModifiers::empty())
1409 .normalized()
1410 .to_string(),
1411 "kp0"
1412 );
1413 assert_eq!(
1414 Key::new(KeyCode::KpEnter, KeyModifiers::empty())
1415 .normalized()
1416 .to_string(),
1417 "kpenter"
1418 );
1419 assert_eq!(
1420 Key::new(KeyCode::KpPageUp, KeyModifiers::empty())
1421 .normalized()
1422 .to_string(),
1423 "kppageup"
1424 );
1425 }
1426
1427 #[test]
1428 fn display_media_variants() {
1429 assert_eq!(
1430 Key::new(KeyCode::MediaPlayPause, KeyModifiers::empty())
1431 .normalized()
1432 .to_string(),
1433 "mediaplaypause"
1434 );
1435 assert_eq!(
1436 Key::new(KeyCode::VolumeMute, KeyModifiers::empty())
1437 .normalized()
1438 .to_string(),
1439 "volumemute"
1440 );
1441 }
1442
1443 fn parse(s: &str) -> Key {
1446 s.parse::<Key>()
1447 .unwrap_or_else(|e| panic!("parse {s:?}: {e}"))
1448 }
1449
1450 #[test]
1451 fn fromstr_single_char_lowercase() {
1452 let k = parse("a");
1453 assert_eq!(k.code, KeyCode::Char('a'));
1454 assert!(k.modifiers.is_empty());
1455 }
1456
1457 #[test]
1458 fn fromstr_uppercase_char_canonicalizes_to_shift_lowercase() {
1459 assert_eq!(
1462 "A".parse::<Key>().unwrap(),
1463 "shift+a".parse::<Key>().unwrap()
1464 );
1465 }
1466
1467 #[test]
1468 fn fromstr_modifier_order_independent() {
1469 let canonical = parse("ctrl+shift+a");
1470 assert_eq!(parse("shift+ctrl+a"), canonical);
1471 assert_eq!(parse("Shift+Ctrl+a"), canonical);
1472 }
1473
1474 #[test]
1475 fn fromstr_function_keys() {
1476 assert_eq!(parse("f1").code, KeyCode::F(1));
1477 assert_eq!(parse("f12").code, KeyCode::F(12));
1478 assert_eq!(parse("f24").code, KeyCode::F(24));
1479 assert_eq!(parse("f35").code, KeyCode::F(35));
1480 }
1481
1482 #[test]
1483 fn fromstr_function_key_out_of_range() {
1484 assert!(matches!(
1485 "f0".parse::<Key>(),
1486 Err(ParseKeyError::InvalidFunctionKey(_))
1487 ));
1488 assert!(matches!(
1489 "f36".parse::<Key>(),
1490 Err(ParseKeyError::InvalidFunctionKey(_))
1491 ));
1492 }
1493
1494 #[test]
1495 fn keycode_function_validates_range() {
1496 assert_eq!(KeyCode::function(1), Some(KeyCode::F(1)));
1497 assert_eq!(
1498 KeyCode::function(KeyCode::FUNCTION_KEY_MAX),
1499 Some(KeyCode::F(KeyCode::FUNCTION_KEY_MAX))
1500 );
1501 assert_eq!(KeyCode::function(0), None);
1502 assert_eq!(KeyCode::function(KeyCode::FUNCTION_KEY_MAX + 1), None);
1503 }
1504
1505 #[test]
1506 fn fromstr_named_keys() {
1507 assert_eq!(parse("esc").code, KeyCode::Escape);
1508 assert_eq!(parse("escape").code, KeyCode::Escape);
1509 assert_eq!(parse("pgup").code, KeyCode::PageUp);
1510 assert_eq!(parse("pageup").code, KeyCode::PageUp);
1511 assert_eq!(parse("enter").code, KeyCode::Enter);
1512 assert_eq!(parse("return").code, KeyCode::Enter);
1513 assert_eq!(parse("ret").code, KeyCode::Enter);
1514 assert_eq!(parse("backspace").code, KeyCode::Backspace);
1515 assert_eq!(parse("bs").code, KeyCode::Backspace);
1516 let backtab = parse("backtab");
1520 assert_eq!(backtab.code, KeyCode::Tab);
1521 assert_eq!(backtab.modifiers, KeyModifiers::SHIFT);
1522 assert_eq!(parse("shift+tab"), backtab);
1523 assert_eq!(backtab.to_string(), "shift+tab");
1525 let alt_backtab = parse("alt+backtab");
1526 assert_eq!(alt_backtab.code, KeyCode::Tab);
1527 assert_eq!(
1528 alt_backtab.modifiers,
1529 KeyModifiers::SHIFT | KeyModifiers::ALT
1530 );
1531 assert_eq!(parse("alt+shift+tab"), alt_backtab);
1532 assert_eq!(alt_backtab.to_string(), "alt+shift+tab");
1533 assert_eq!(parse("delete").code, KeyCode::Delete);
1534 assert_eq!(parse("del").code, KeyCode::Delete);
1535 assert_eq!(parse("space").code, KeyCode::Space);
1536 }
1537
1538 #[test]
1539 fn fromstr_modifier_aliases() {
1540 assert_eq!(parse("control+a"), parse("ctrl+a"));
1541 assert_eq!(parse("option+a"), parse("alt+a"));
1542 assert_eq!(parse("cmd+a"), parse("super+a"));
1543 assert_eq!(parse("command+a"), parse("super+a"));
1544 assert_eq!(parse("win+a"), parse("super+a"));
1545 }
1546
1547 #[test]
1548 fn fromstr_plus_alias_round_trips() {
1549 let k = parse("ctrl+plus");
1550 assert_eq!(k.code, KeyCode::Char('+'));
1551 assert!(k.modifiers.contains(KeyModifiers::CTRL));
1552 assert_eq!(k.to_string(), "ctrl+plus");
1553 }
1554
1555 #[test]
1556 fn fromstr_keypad_and_media() {
1557 assert_eq!(parse("kp0").code, KeyCode::Kp0);
1558 assert_eq!(parse("kpenter").code, KeyCode::KpEnter);
1559 assert_eq!(parse("mediaplaypause").code, KeyCode::MediaPlayPause);
1560 assert_eq!(parse("volumemute").code, KeyCode::VolumeMute);
1561 }
1562
1563 #[test]
1564 fn fromstr_modifier_keys_themselves() {
1565 assert_eq!(parse("leftshift").code, KeyCode::LeftShift);
1566 assert_eq!(parse("rightalt").code, KeyCode::RightAlt);
1567 assert_eq!(parse("isolevel3shift").code, KeyCode::IsoLevel3Shift);
1568 }
1569
1570 #[test]
1571 fn fromstr_unicode_char() {
1572 assert_eq!(parse("ц").code, KeyCode::Char('ц'));
1573 assert_eq!(parse("α").code, KeyCode::Char('α'));
1574 }
1575
1576 #[test]
1577 fn fromstr_trims_whitespace() {
1578 assert_eq!(parse(" ctrl+a "), parse("ctrl+a"));
1579 }
1580
1581 #[test]
1582 fn fromstr_errors() {
1583 assert_eq!("".parse::<Key>(), Err(ParseKeyError::Empty));
1584 assert_eq!(" ".parse::<Key>(), Err(ParseKeyError::Empty));
1585 assert_eq!("ctrl+".parse::<Key>(), Err(ParseKeyError::EmptyComponent));
1586 assert_eq!("ctrl++a".parse::<Key>(), Err(ParseKeyError::EmptyComponent));
1589 assert_eq!("+a".parse::<Key>(), Err(ParseKeyError::EmptyComponent));
1591 assert_eq!("+ctrl+a".parse::<Key>(), Err(ParseKeyError::EmptyComponent));
1592 assert!(matches!(
1593 "foo+a".parse::<Key>(),
1594 Err(ParseKeyError::UnknownModifier(_))
1595 ));
1596 assert!(matches!(
1597 "ctrl+xyz".parse::<Key>(),
1598 Err(ParseKeyError::UnknownKey(_))
1599 ));
1600 }
1601
1602 #[test]
1603 fn fromstr_keycode_only() {
1604 let kc: KeyCode = "esc".parse().unwrap();
1605 assert_eq!(kc, KeyCode::Escape);
1606 let kc: KeyCode = "f5".parse().unwrap();
1607 assert_eq!(kc, KeyCode::F(5));
1608 assert!(matches!(
1609 "ctrl+a".parse::<KeyCode>(),
1610 Err(ParseKeyError::UnknownKey(_))
1611 ));
1612 }
1613
1614 #[test]
1615 fn fromstr_literal_plus() {
1616 assert_eq!(parse("+").code, KeyCode::Char('+'));
1618 assert_eq!(parse("plus").code, KeyCode::Char('+'));
1620 assert_eq!(
1622 parse("ctrl++"),
1623 Key::new(KeyCode::Char('+'), KeyModifiers::CTRL).normalized()
1624 );
1625 assert_eq!(parse("ctrl++"), parse("ctrl+plus"));
1627 }
1628
1629 #[test]
1630 fn fromstr_literal_symbols() {
1631 assert_eq!(parse("-").code, KeyCode::Char('-'));
1633 assert_eq!(parse("*").code, KeyCode::Char('*'));
1634 assert_eq!(parse("/").code, KeyCode::Char('/'));
1635 assert_eq!(parse("=").code, KeyCode::Char('='));
1636 assert_eq!(parse("[").code, KeyCode::Char('['));
1637 assert_eq!(parse("ctrl+-").code, KeyCode::Char('-'));
1638 assert_eq!(parse("ctrl+/").code, KeyCode::Char('/'));
1639 assert_eq!(parse("alt+[").code, KeyCode::Char('['));
1640 }
1641
1642 #[test]
1643 fn fromstr_minus_aliases() {
1644 assert_eq!(parse("minus").code, KeyCode::Char('-'));
1645 assert_eq!(parse("dash").code, KeyCode::Char('-'));
1646 assert_eq!(parse("hyphen").code, KeyCode::Char('-'));
1647 assert_eq!(parse("ctrl+minus"), parse("ctrl+-"));
1648 assert_eq!(parse("ctrl+dash"), parse("ctrl+-"));
1649 }
1650
1651 #[test]
1652 fn fromstr_equals_aliases() {
1653 assert_eq!(parse("equals").code, KeyCode::Char('='));
1654 assert_eq!(parse("equal").code, KeyCode::Char('='));
1655 assert_eq!(parse("ctrl+equals"), parse("ctrl+="));
1656 }
1657
1658 #[test]
1659 fn display_uses_named_symbol_forms() {
1660 assert_eq!(
1661 Key::new(KeyCode::Char('-'), KeyModifiers::empty())
1662 .normalized()
1663 .to_string(),
1664 "minus"
1665 );
1666 assert_eq!(
1667 Key::new(KeyCode::Char('='), KeyModifiers::empty())
1668 .normalized()
1669 .to_string(),
1670 "equals"
1671 );
1672 assert_eq!(
1673 Key::new(KeyCode::Char('+'), KeyModifiers::empty())
1674 .normalized()
1675 .to_string(),
1676 "plus"
1677 );
1678 assert_eq!(
1679 Key::new(KeyCode::Char('-'), KeyModifiers::CTRL)
1680 .normalized()
1681 .to_string(),
1682 "ctrl+minus"
1683 );
1684 }
1685
1686 #[test]
1687 fn fromstr_rejects_hyphen_aliases() {
1688 assert!(matches!(
1691 "page-up".parse::<Key>(),
1692 Err(ParseKeyError::UnknownKey(_))
1693 ));
1694 assert!(matches!(
1695 "back-tab".parse::<Key>(),
1696 Err(ParseKeyError::UnknownKey(_))
1697 ));
1698 assert!(matches!(
1699 "caps-lock".parse::<Key>(),
1700 Err(ParseKeyError::UnknownKey(_))
1701 ));
1702 }
1703
1704 #[test]
1705 fn display_kp_pageup_long_form() {
1706 assert_eq!(
1707 Key::new(KeyCode::KpPageDown, KeyModifiers::empty())
1708 .normalized()
1709 .to_string(),
1710 "kppagedown"
1711 );
1712 assert_eq!(parse("kppgup").code, KeyCode::KpPageUp);
1714 assert_eq!(parse("kppgdn").code, KeyCode::KpPageDown);
1715 }
1716
1717 #[test]
1718 fn display_fromstr_roundtrip_named_variants() {
1719 let cases: &[(KeyCode, KeyModifiers)] = &[
1722 (KeyCode::Char('a'), KeyModifiers::empty()),
1723 (KeyCode::Char('a'), KeyModifiers::CTRL),
1724 (KeyCode::Char('a'), KeyModifiers::CTRL | KeyModifiers::ALT),
1725 (
1726 KeyCode::Char('a'),
1727 KeyModifiers::CTRL
1728 | KeyModifiers::ALT
1729 | KeyModifiers::SHIFT
1730 | KeyModifiers::SUPER
1731 | KeyModifiers::HYPER
1732 | KeyModifiers::META,
1733 ),
1734 (KeyCode::Char('+'), KeyModifiers::CTRL),
1735 (KeyCode::Char('-'), KeyModifiers::CTRL),
1736 (KeyCode::Char('='), KeyModifiers::CTRL),
1737 (KeyCode::Char('-'), KeyModifiers::empty()),
1738 (KeyCode::Char('='), KeyModifiers::empty()),
1739 (KeyCode::Char('ц'), KeyModifiers::empty()),
1740 (KeyCode::F(1), KeyModifiers::empty()),
1741 (KeyCode::F(24), KeyModifiers::CTRL),
1742 (KeyCode::F(35), KeyModifiers::empty()),
1743 (KeyCode::Up, KeyModifiers::empty()),
1744 (KeyCode::Down, KeyModifiers::SHIFT),
1745 (KeyCode::Left, KeyModifiers::ALT),
1746 (KeyCode::Right, KeyModifiers::CTRL),
1747 (KeyCode::Home, KeyModifiers::empty()),
1748 (KeyCode::End, KeyModifiers::empty()),
1749 (KeyCode::PageUp, KeyModifiers::empty()),
1750 (KeyCode::PageDown, KeyModifiers::empty()),
1751 (KeyCode::Backspace, KeyModifiers::empty()),
1752 (KeyCode::Delete, KeyModifiers::empty()),
1753 (KeyCode::Insert, KeyModifiers::empty()),
1754 (KeyCode::Tab, KeyModifiers::empty()),
1755 (KeyCode::Tab, KeyModifiers::SHIFT),
1756 (KeyCode::Enter, KeyModifiers::empty()),
1757 (KeyCode::Space, KeyModifiers::empty()),
1758 (KeyCode::Escape, KeyModifiers::empty()),
1759 (KeyCode::CapsLock, KeyModifiers::empty()),
1760 (KeyCode::ScrollLock, KeyModifiers::empty()),
1761 (KeyCode::NumLock, KeyModifiers::empty()),
1762 (KeyCode::PrintScreen, KeyModifiers::empty()),
1763 (KeyCode::Pause, KeyModifiers::empty()),
1764 (KeyCode::Menu, KeyModifiers::empty()),
1765 (KeyCode::Kp0, KeyModifiers::empty()),
1766 (KeyCode::Kp9, KeyModifiers::empty()),
1767 (KeyCode::KpEnter, KeyModifiers::empty()),
1768 (KeyCode::KpPageUp, KeyModifiers::empty()),
1769 (KeyCode::KpPageDown, KeyModifiers::empty()),
1770 (KeyCode::KpBegin, KeyModifiers::empty()),
1771 (KeyCode::MediaPlay, KeyModifiers::empty()),
1772 (KeyCode::MediaPlayPause, KeyModifiers::empty()),
1773 (KeyCode::VolumeUp, KeyModifiers::empty()),
1774 (KeyCode::VolumeMute, KeyModifiers::empty()),
1775 (KeyCode::LeftShift, KeyModifiers::empty()),
1776 (KeyCode::RightMeta, KeyModifiers::empty()),
1777 (KeyCode::IsoLevel3Shift, KeyModifiers::empty()),
1778 (KeyCode::IsoLevel5Shift, KeyModifiers::empty()),
1779 ];
1780 for (code, mods) in cases {
1781 let k = Key::new(*code, *mods).normalized();
1782 let s = k.to_string();
1783 let parsed = s
1784 .parse::<Key>()
1785 .unwrap_or_else(|e| panic!("failed to parse {s:?} (from {code:?}, {mods:?}): {e}"));
1786 assert_eq!(parsed, k, "round-trip mismatch for {s:?}");
1787 }
1788 }
1789
1790 #[test]
1791 fn eq_ignores_lock_state() {
1792 let plain = Key::new(KeyCode::Char('c'), KeyModifiers::CTRL).normalized();
1793 let with_caps = Key::new(
1794 KeyCode::Char('c'),
1795 KeyModifiers::CTRL | KeyModifiers::CAPS_LOCK,
1796 )
1797 .normalized();
1798 let with_num = Key::new(
1799 KeyCode::Char('c'),
1800 KeyModifiers::CTRL | KeyModifiers::NUM_LOCK,
1801 )
1802 .normalized();
1803 let with_scroll = Key::new(
1804 KeyCode::Char('c'),
1805 KeyModifiers::CTRL | KeyModifiers::SCROLL_LOCK,
1806 )
1807 .normalized();
1808 let with_all_locks = Key::new(
1809 KeyCode::Char('c'),
1810 KeyModifiers::CTRL | KeyModifiers::LOCK_MASK,
1811 )
1812 .normalized();
1813 assert_eq!(plain, with_caps);
1814 assert_eq!(plain, with_num);
1815 assert_eq!(plain, with_scroll);
1816 assert_eq!(plain, with_all_locks);
1817 }
1818
1819 #[test]
1820 fn hash_ignores_lock_state() {
1821 use std::collections::hash_map::DefaultHasher;
1822 use std::hash::{Hash, Hasher};
1823 fn h(k: &Key) -> u64 {
1824 let mut s = DefaultHasher::new();
1825 k.hash(&mut s);
1826 s.finish()
1827 }
1828 let plain = Key::new(KeyCode::Char('c'), KeyModifiers::CTRL).normalized();
1829 let with_locks = Key::new(
1830 KeyCode::Char('c'),
1831 KeyModifiers::CTRL | KeyModifiers::LOCK_MASK,
1832 )
1833 .normalized();
1834 assert_eq!(h(&plain), h(&with_locks));
1835 }
1836
1837 #[test]
1838 fn eq_still_distinguishes_binding_modifiers() {
1839 let ctrl = Key::new(KeyCode::Char('c'), KeyModifiers::CTRL).normalized();
1840 let alt = Key::new(KeyCode::Char('c'), KeyModifiers::ALT).normalized();
1841 let ctrl_alt =
1842 Key::new(KeyCode::Char('c'), KeyModifiers::CTRL | KeyModifiers::ALT).normalized();
1843 assert_ne!(ctrl, alt);
1844 assert_ne!(ctrl, ctrl_alt);
1845 }
1846
1847 #[test]
1848 fn parsed_key_equals_key_with_lock_state() {
1849 let parsed: Key = "ctrl+c".parse().expect("parse");
1850 let live = Key::new(
1851 KeyCode::Char('c'),
1852 KeyModifiers::CTRL | KeyModifiers::CAPS_LOCK,
1853 )
1854 .normalized();
1855 assert_eq!(parsed, live);
1856 }
1857
1858 #[test]
1859 fn matches_simple_char() {
1860 let k = Key::new(KeyCode::Char('a'), KeyModifiers::empty()).normalized();
1861 assert!(k.matches("a"));
1862 assert!(!k.matches("b"));
1863 }
1864
1865 #[test]
1866 fn matches_modifier_combo() {
1867 let k = Key::new(KeyCode::Char('c'), KeyModifiers::CTRL).normalized();
1868 assert!(k.matches("ctrl+c"));
1869 assert!(!k.matches("alt+c"));
1870 assert!(!k.matches("ctrl+x"));
1871 }
1872
1873 #[test]
1874 fn matches_named_key() {
1875 let k = Key::new(KeyCode::F(5), KeyModifiers::SHIFT).normalized();
1876 assert!(k.matches("shift+f5"));
1877 assert!(!k.matches("f5"));
1878 }
1879
1880 #[test]
1881 fn matches_ignores_lock_state() {
1882 let k = Key::new(
1883 KeyCode::Char('a'),
1884 KeyModifiers::CTRL | KeyModifiers::CAPS_LOCK | KeyModifiers::NUM_LOCK,
1885 )
1886 .normalized();
1887 assert!(k.matches("ctrl+a"));
1888 }
1889
1890 #[test]
1891 fn matches_invalid_pattern_is_false() {
1892 let k = Key::new(KeyCode::Char('a'), KeyModifiers::empty()).normalized();
1893 assert!(!k.matches(""));
1894 assert!(!k.matches("frob+nargle"));
1895 assert!(!k.matches("ctrl+"));
1896 }
1897
1898 #[test]
1899 fn matches_any_basic() {
1900 let k = Key::new(KeyCode::Escape, KeyModifiers::empty()).normalized();
1901 assert!(k.matches_any(["esc", "ctrl+c", "q"]));
1902 assert!(!k.matches_any(["enter", "tab"]));
1903 }
1904
1905 #[test]
1906 fn matches_any_empty_is_false() {
1907 let k = Key::new(KeyCode::Char('a'), KeyModifiers::empty()).normalized();
1908 let none: [&str; 0] = [];
1909 assert!(!k.matches_any(none));
1910 }
1911
1912 #[test]
1913 fn matches_any_accepts_string_and_str() {
1914 let k = Key::new(KeyCode::Char('q'), KeyModifiers::empty()).normalized();
1915 let owned: Vec<String> = vec!["esc".into(), "q".into()];
1916 assert!(k.matches_any(&owned));
1917 let borrowed: Vec<&str> = vec!["esc", "q"];
1918 assert!(k.matches_any(borrowed));
1919 }
1920
1921 #[test]
1922 fn matches_is_case_sensitive_for_letters() {
1923 let plain_g = Key::new(KeyCode::Char('g'), KeyModifiers::empty()).normalized();
1925 assert!(plain_g.matches("g"));
1926 assert!(!plain_g.matches("G"));
1927 assert!(!plain_g.matches("shift+g"));
1928
1929 let big_g = Key::new(KeyCode::Char('G'), KeyModifiers::empty()).normalized();
1932 assert_eq!(big_g.code, KeyCode::Char('g'));
1933 assert!(big_g.modifiers.contains(KeyModifiers::SHIFT));
1934 assert!(big_g.matches("G"));
1935 assert!(big_g.matches("shift+g"));
1936 assert!(!big_g.matches("g"));
1937 }
1938
1939 #[test]
1940 fn matches_modifier_combos_with_letters() {
1941 let ctrl_g = Key::new(KeyCode::Char('g'), KeyModifiers::CTRL).normalized();
1942 assert!(ctrl_g.matches("ctrl+g"));
1943 assert!(!ctrl_g.matches("ctrl+G"));
1944 assert!(!ctrl_g.matches("ctrl+shift+g"));
1945 assert!(!ctrl_g.matches("g"));
1946 assert!(!ctrl_g.matches("alt+g"));
1947 }
1948
1949 #[test]
1950 fn matches_text_first_layout_independent() {
1951 let mut shift_1 = Key::new(KeyCode::Char('1'), KeyModifiers::SHIFT).normalized();
1954 shift_1.text = Some("!".to_string());
1955 assert!(shift_1.matches("!"));
1956 assert!(shift_1.matches("shift+1"));
1958 }
1959
1960 #[test]
1961 fn matches_text_first_respects_binding_modifiers() {
1962 let mut shift_1 = Key::new(KeyCode::Char('1'), KeyModifiers::SHIFT).normalized();
1965 shift_1.text = Some("!".to_string());
1966 assert!(!shift_1.matches("ctrl+!"));
1967 }
1968
1969 #[test]
1970 fn matches_text_first_handles_layout_glyph() {
1971 let mut shift_slash = Key::new(KeyCode::Char('/'), KeyModifiers::SHIFT).normalized();
1973 shift_slash.text = Some("?".to_string());
1974 assert!(shift_slash.matches("?"));
1975 assert!(shift_slash.matches("shift+/"));
1976 }
1977}