1use std::fmt;
10
11#[cfg(all(unix, feature = "termion"))]
12mod via_termion;
13#[cfg(all(unix, feature = "termion"))]
14pub use via_termion::from_termion;
15#[cfg(feature = "termwiz")]
16mod via_termwiz;
17#[cfg(feature = "termwiz")]
18pub use via_termwiz::{from_termwiz, from_termwiz_kitty};
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
22pub struct Modifiers {
23 pub shift: bool,
24 pub ctrl: bool,
25 pub alt: bool,
26}
27
28impl Modifiers {
29 pub const NONE: Modifiers = Modifiers {
30 shift: false,
31 ctrl: false,
32 alt: false,
33 };
34 pub const CTRL: Modifiers = Modifiers {
35 shift: false,
36 ctrl: true,
37 alt: false,
38 };
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43pub enum KeyCode {
44 Char(char),
45 Enter,
46 Tab,
47 BackTab,
49 Backspace,
50 Delete,
51 Insert,
52 Escape,
53 Up,
54 Down,
55 Left,
56 Right,
57 Home,
58 End,
59 PageUp,
60 PageDown,
61 F(u8),
62 Menu,
67 Modifier(ModifierKey),
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77pub enum ModifierKey {
78 Shift,
79 Ctrl,
80 Alt,
81 Super,
83}
84
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
90pub enum Releases {
91 #[default]
94 None,
95 ExceptText,
99 All,
103}
104
105impl Releases {
106 pub fn reported(self, key: &Key) -> bool {
119 match self {
120 Releases::None => false,
121 Releases::All => true,
122 Releases::ExceptText => {
123 let text = match key.code {
124 KeyCode::Char(_) => !key.modifiers.ctrl && !key.modifiers.alt,
125 KeyCode::Enter | KeyCode::Tab | KeyCode::Backspace => true,
126 KeyCode::Modifier(_) => true,
128 _ => false,
129 };
130 !text
131 }
132 }
133 }
134}
135
136#[derive(Clone, Copy, Debug)]
144pub struct Key {
145 pub code: KeyCode,
146 pub modifiers: Modifiers,
147 exact: bool,
149}
150
151impl PartialEq for Key {
152 fn eq(&self, other: &Key) -> bool {
153 self.code == other.code && self.modifiers == other.modifiers
154 }
155}
156
157impl Eq for Key {}
158
159impl std::hash::Hash for Key {
160 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
161 self.code.hash(state);
162 self.modifiers.hash(state);
163 }
164}
165
166impl Key {
167 pub const fn new(code: KeyCode) -> Key {
168 Key::with(code, Modifiers::NONE)
169 }
170
171 pub const fn with(code: KeyCode, modifiers: Modifiers) -> Key {
173 Key {
174 code,
175 modifiers,
176 exact: false,
177 }
178 }
179
180 pub const fn ctrl(c: char) -> Key {
181 Key::with(KeyCode::Char(c), Modifiers::CTRL)
182 }
183
184 pub const fn char(c: char) -> Key {
185 Key::new(KeyCode::Char(c))
186 }
187
188 pub fn parse(text: &str) -> Option<Key> {
200 if text == "+" {
201 return Some(Key::char('+'));
202 }
203 let mut modifiers = Modifiers::NONE;
204 let mut parts: Vec<&str> = text.split('+').collect();
205 if text.ends_with("++") {
207 parts.truncate(parts.len() - 2);
208 parts.push("+");
209 }
210 let (name, held) = parts.split_last()?;
211 for modifier in held {
212 match modifier.to_ascii_lowercase().as_str() {
213 "ctrl" | "control" => modifiers.ctrl = true,
214 "alt" | "meta" => modifiers.alt = true,
215 "shift" => modifiers.shift = true,
216 _ => return None,
217 }
218 }
219 let mut chars = name.chars();
220 let code = match (chars.next(), chars.next()) {
221 (Some(c), None) if modifiers.shift => {
226 if !c.is_alphabetic() {
227 return None;
228 }
229 modifiers.shift = false;
230 KeyCode::Char(c.to_uppercase().next().unwrap_or(c))
231 }
232 (Some(c), None) if modifiers.ctrl => KeyCode::Char(c.to_ascii_lowercase()),
235 (Some(c), None) => KeyCode::Char(c),
236 _ => match name.to_ascii_lowercase().as_str() {
237 "enter" | "return" => KeyCode::Enter,
238 "tab" if modifiers.shift => {
239 modifiers.shift = false;
240 KeyCode::BackTab
241 }
242 "tab" => KeyCode::Tab,
243 "backtab" => KeyCode::BackTab,
244 "backspace" => KeyCode::Backspace,
245 "delete" | "del" => KeyCode::Delete,
246 "insert" => KeyCode::Insert,
247 "menu" => KeyCode::Menu,
248 "shift" => KeyCode::Modifier(ModifierKey::Shift),
249 "ctrl" | "control" => KeyCode::Modifier(ModifierKey::Ctrl),
250 "alt" | "meta" => KeyCode::Modifier(ModifierKey::Alt),
251 "super" | "cmd" | "win" => KeyCode::Modifier(ModifierKey::Super),
252 "escape" | "esc" => KeyCode::Escape,
253 "space" => KeyCode::Char(' '),
254 "comma" => KeyCode::Char(','),
256 other if punctuation(other).is_some() && modifiers.shift => return None,
258 other if punctuation(other).is_some() => KeyCode::Char(punctuation(other)?),
259 "up" => KeyCode::Up,
260 "down" => KeyCode::Down,
261 "left" => KeyCode::Left,
262 "right" => KeyCode::Right,
263 "home" => KeyCode::Home,
264 "end" => KeyCode::End,
265 "pageup" => KeyCode::PageUp,
266 "pagedown" => KeyCode::PageDown,
267 other => match other.strip_prefix('f')?.parse().ok()? {
268 n @ 1..=24 => KeyCode::F(n),
270 _ => return None,
271 },
272 },
273 };
274 Some(Key::with(code, modifiers))
275 }
276
277 #[must_use]
282 pub const fn exact(self) -> Key {
283 Key {
284 exact: true,
285 ..self
286 }
287 }
288
289 pub const fn is_exact(&self) -> bool {
291 self.exact
292 }
293
294 #[must_use]
300 pub fn legacy(self) -> Key {
301 let mut key = Key {
302 exact: false,
303 ..self
304 };
305 let KeyCode::Char(c) = key.code else {
306 return key;
307 };
308 if !key.modifiers.ctrl {
309 return key;
310 }
311 key.code = match c.to_ascii_lowercase() {
312 'i' => KeyCode::Tab,
313 'm' => KeyCode::Enter,
314 '[' => KeyCode::Escape,
315 '\\' => KeyCode::Char('4'),
316 ']' => KeyCode::Char('5'),
317 '^' => KeyCode::Char('6'),
318 '_' => KeyCode::Char('7'),
319 '@' => KeyCode::Char(' '),
320 c => KeyCode::Char(c),
321 };
322 if matches!(key.code, KeyCode::Tab | KeyCode::Enter | KeyCode::Escape) {
324 key.modifiers.ctrl = false;
325 }
326 key
327 }
328
329 pub fn matches(&self, binding: &Key) -> bool {
336 if self == binding {
337 return true;
338 }
339 if self.exact {
340 return false;
341 }
342 if binding.legacy() == *self {
343 return true;
344 }
345 binding.code == KeyCode::Backspace
346 && !binding.modifiers.ctrl
347 && self.code == KeyCode::Char('h')
348 && self.modifiers
349 == Modifiers {
350 ctrl: true,
351 ..binding.modifiers
352 }
353 }
354
355 pub fn matches_any<'a>(&self, bindings: impl IntoIterator<Item = &'a Key>) -> bool {
357 bindings.into_iter().any(|binding| self.matches(binding))
358 }
359
360 pub fn pick<'a>(&self, sets: impl IntoIterator<Item = &'a [Key]>) -> Option<usize> {
365 let sets: Vec<&[Key]> = sets.into_iter().collect();
366 sets.iter()
367 .position(|keys| keys.contains(self))
368 .or_else(|| sets.iter().position(|keys| self.matches_any(*keys)))
369 }
370
371 pub fn is_interrupt(&self) -> bool {
374 self.modifiers.ctrl && self.code == KeyCode::Char('c')
375 }
376}
377
378pub fn punctuation(name: &str) -> Option<char> {
398 Some(match name {
399 "equals" => '=',
400 "minus" | "dash" => '-',
401 "plus" => '+',
402 "underscore" => '_',
403 "slash" => '/',
404 "backslash" => '\\',
405 "period" | "dot" => '.',
406 "comma" => ',',
407 "semicolon" => ';',
408 "colon" => ':',
409 "quote" | "apostrophe" => '\'',
410 "doublequote" => '"',
411 "backtick" | "grave" => '`',
412 "tilde" => '~',
413 "bracketleft" => '[',
414 "bracketright" => ']',
415 "braceleft" => '{',
416 "braceright" => '}',
417 "parenleft" => '(',
418 "parenright" => ')',
419 "lt" | "less" => '<',
420 "gt" | "greater" => '>',
421 "question" => '?',
422 "exclam" | "bang" => '!',
423 "at" => '@',
424 "hash" => '#',
425 "dollar" => '$',
426 "percent" => '%',
427 "caret" => '^',
428 "ampersand" => '&',
429 "asterisk" | "star" => '*',
430 "pipe" => '|',
431 _ => return None,
432 })
433}
434
435impl fmt::Display for Key {
436 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437 if self.modifiers.ctrl {
438 f.write_str("ctrl+")?;
439 }
440 if self.modifiers.alt {
441 f.write_str("alt+")?;
442 }
443 let capital =
446 matches!(self.code, KeyCode::Char(c) if self.modifiers.ctrl && c.is_ascii_uppercase());
447 if self.modifiers.shift || self.code == KeyCode::BackTab || capital {
448 f.write_str("shift+")?;
449 }
450 match self.code {
451 KeyCode::BackTab => f.write_str("tab"),
452 KeyCode::Char(' ') => f.write_str("space"),
453 KeyCode::Char(c) if capital => write!(f, "{}", c.to_ascii_lowercase()),
454 KeyCode::Char(c) => write!(f, "{c}"),
455 KeyCode::F(n) => write!(f, "f{n}"),
456 KeyCode::Modifier(key) => f.write_str(match key {
457 ModifierKey::Shift => "shift",
458 ModifierKey::Ctrl => "ctrl",
459 ModifierKey::Alt => "alt",
460 ModifierKey::Super => "super",
461 }),
462 code => f.write_str(&format!("{code:?}").to_ascii_lowercase()),
463 }
464 }
465}
466
467#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
469pub enum Button {
470 Left,
471 Right,
472 Middle,
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
477pub enum MouseKind {
478 Down(Button),
479 Up(Button),
480 Drag(Button),
481 Moved,
482 ScrollUp,
483 ScrollDown,
484}
485
486#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
490pub struct Mouse {
491 pub kind: MouseKind,
492 pub column: u16,
493 pub row: u16,
494 pub modifiers: Modifiers,
495 pub pixel: Option<Subcell>,
501}
502
503#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
507pub struct Subcell {
508 pub x: u16,
510 pub y: u16,
512 pub width: u16,
514 pub height: u16,
516}
517
518impl Mouse {
519 pub const fn new(kind: MouseKind, column: u16, row: u16) -> Mouse {
520 Mouse {
521 kind,
522 column,
523 row,
524 modifiers: Modifiers::NONE,
525 pixel: None,
526 }
527 }
528
529 pub fn from_pixels(kind: MouseKind, x: u32, y: u32, cell: (u16, u16)) -> Option<Mouse> {
544 let (width, height) = cell;
545 if width == 0 || height == 0 {
546 return None;
547 }
548 let (w, h) = (u32::from(width), u32::from(height));
549 let cells = |at: u32, size: u32| u16::try_from(at / size).unwrap_or(u16::MAX);
550 Some(Mouse {
551 pixel: Some(Subcell {
552 x: (x % w) as u16,
553 y: (y % h) as u16,
554 width,
555 height,
556 }),
557 ..Mouse::new(kind, cells(x, w), cells(y, h))
558 })
559 }
560
561 pub fn precise(&self) -> (f32, f32) {
572 match self.pixel {
573 Some(p) if p.width > 0 && p.height > 0 => (
574 f32::from(self.column) + f32::from(p.x) / f32::from(p.width),
575 f32::from(self.row) + f32::from(p.y) / f32::from(p.height),
576 ),
577 _ => (f32::from(self.column) + 0.5, f32::from(self.row) + 0.5),
578 }
579 }
580
581 pub fn reread_as_pixels(self, cell: (u16, u16)) -> Option<Mouse> {
586 Mouse::from_pixels(self.kind, u32::from(self.column), u32::from(self.row), cell).map(
587 |read| Mouse {
588 modifiers: self.modifiers,
589 ..read
590 },
591 )
592 }
593
594 pub fn is_click(&self) -> bool {
596 self.kind == MouseKind::Down(Button::Left)
597 }
598}
599
600#[derive(Clone, Debug, PartialEq, Eq)]
602pub enum Event {
603 Key(Key),
605 KeyUp(Key),
610 Mouse(Mouse),
611 Resize {
613 columns: u16,
614 rows: u16,
615 },
616 Paste(String),
618 Tick,
621 Returned(Option<i32>),
625 Link(String),
631}
632
633impl Event {
634 pub fn key(&self) -> Option<Key> {
636 match self {
637 Event::Key(key) => Some(*key),
638 _ => None,
639 }
640 }
641}
642
643impl Event {
644 pub fn mouse(&self) -> Option<Mouse> {
646 match self {
647 Event::Mouse(mouse) => Some(*mouse),
648 _ => None,
649 }
650 }
651}
652
653impl From<Key> for Event {
654 fn from(key: Key) -> Event {
655 Event::Key(key)
656 }
657}
658
659#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
664pub struct HeldButton(Option<Button>);
665
666impl HeldButton {
667 pub fn get(self) -> Option<Button> {
669 self.0
670 }
671}
672
673#[cfg(any(unix, feature = "termwiz"))]
678fn char_key(c: char, mut modifiers: Modifiers) -> Key {
679 let code = match c {
680 '\r' | '\n' => KeyCode::Enter,
681 '\t' => KeyCode::Tab,
682 '\x1b' => KeyCode::Escape,
683 '\x7f' => KeyCode::Backspace,
684 '\0' => {
685 modifiers.ctrl = true;
686 KeyCode::Char(' ')
687 }
688 '\x01'..='\x1a' => {
689 modifiers.ctrl = true;
690 KeyCode::Char((c as u8 - 1 + b'a') as char)
691 }
692 '\x1c'..='\x1f' => {
693 modifiers.ctrl = true;
694 KeyCode::Char((c as u8 - 0x1c + b'4') as char)
695 }
696 c => KeyCode::Char(c),
697 };
698 Key::with(code, modifiers)
699}
700
701#[cfg(unix)]
709pub(crate) fn from_legacy_bytes(bytes: &[u8]) -> Vec<Event> {
710 let text = String::from_utf8_lossy(bytes);
711 let chars: Vec<char> = text.chars().collect();
712 let mut events = Vec::new();
713 let mut at = 0;
714 while at < chars.len() {
715 let c = chars[at];
716 if c != '\x1b' {
717 events.push(Event::Key(char_key(c, Modifiers::NONE)));
718 at += 1;
719 continue;
720 }
721 match chars.get(at + 1) {
722 None | Some('\x1b') => {
723 events.push(Event::Key(Key::new(KeyCode::Escape)));
724 at += 1;
725 }
726 Some('[') => {
727 let start = at + 2;
728 let mut end = start;
729 while chars
730 .get(end)
731 .is_some_and(|c| ('\x20'..='\x3f').contains(c))
732 {
733 end += 1;
734 }
735 let Some(&last) = chars.get(end) else {
736 break;
737 };
738 let parameters: String = chars[start..end].iter().collect();
739 if let Some(key) = csi_key(¶meters, last) {
740 events.push(Event::Key(key));
741 }
742 at = end + 1;
743 }
744 Some('O') if at + 2 < chars.len() => {
745 if let Some(key) = csi_key("", chars[at + 2]) {
746 events.push(Event::Key(key));
747 }
748 at += 3;
749 }
750 Some(&c) => {
751 let alt = Modifiers {
752 alt: true,
753 ..Modifiers::NONE
754 };
755 events.push(Event::Key(char_key(c, alt)));
756 at += 2;
757 }
758 }
759 }
760 events
761}
762
763#[cfg(unix)]
766fn csi_key(parameters: &str, last: char) -> Option<Key> {
767 let mut parts = parameters.split(';');
768 let first = parts.next().unwrap_or("");
769 let modifiers = match parts.next().map(str::parse::<u8>) {
770 Some(Ok(code)) => {
771 let bits = code.saturating_sub(1);
772 Modifiers {
773 shift: bits & 1 != 0,
774 alt: bits & 2 != 0,
775 ctrl: bits & 4 != 0,
776 }
777 }
778 Some(Err(_)) => return None,
779 None => Modifiers::NONE,
780 };
781 let code = match last {
782 'A' => KeyCode::Up,
783 'B' => KeyCode::Down,
784 'C' => KeyCode::Right,
785 'D' => KeyCode::Left,
786 'H' => KeyCode::Home,
787 'F' => KeyCode::End,
788 'P'..='S' => KeyCode::F(1 + (last as u8 - b'P')),
789 'Z' => return Some(Key::new(KeyCode::BackTab)),
790 '~' => match first.parse::<u8>().ok()? {
791 1 | 7 => KeyCode::Home,
792 2 => KeyCode::Insert,
793 3 => KeyCode::Delete,
794 4 | 8 => KeyCode::End,
795 5 => KeyCode::PageUp,
796 6 => KeyCode::PageDown,
797 n @ 11..=15 => KeyCode::F(n - 10),
798 n @ 17..=21 => KeyCode::F(n - 11),
799 n @ 23..=24 => KeyCode::F(n - 12),
800 _ => return None,
801 },
802 _ => return None,
803 };
804 Some(Key::with(code, modifiers))
805}
806
807#[cfg(not(target_arch = "wasm32"))]
808fn modifiers(state: crossterm::event::KeyModifiers) -> Modifiers {
809 use crossterm::event::KeyModifiers as M;
810 Modifiers {
811 shift: state.contains(M::SHIFT),
812 ctrl: state.contains(M::CONTROL),
813 alt: state.contains(M::ALT),
814 }
815}
816
817#[cfg(not(target_arch = "wasm32"))]
818fn button(button: crossterm::event::MouseButton) -> Button {
819 use crossterm::event::MouseButton as B;
820 match button {
821 B::Left => Button::Left,
822 B::Right => Button::Right,
823 B::Middle => Button::Middle,
824 }
825}
826
827#[cfg(not(target_arch = "wasm32"))]
834pub fn from_crossterm(event: crossterm::event::Event) -> Option<Event> {
835 translate(event, false)
836}
837
838#[cfg(not(target_arch = "wasm32"))]
842pub fn from_crossterm_kitty(event: crossterm::event::Event) -> Option<Event> {
843 translate(event, true)
844}
845
846#[cfg(not(target_arch = "wasm32"))]
847fn translate(event: crossterm::event::Event, exact: bool) -> Option<Event> {
848 use crossterm::event::{
849 Event as E, KeyCode as K, KeyEventKind, KeyModifiers as M, MouseEventKind as MK,
850 };
851 Some(match event {
852 E::Key(key) if matches!(key.code, crossterm::event::KeyCode::Modifier(_)) => {
853 use crossterm::event::{KeyCode as K, ModifierKeyCode as MK};
854 let K::Modifier(which) = key.code else {
855 return None;
856 };
857 let code = KeyCode::Modifier(match which {
858 MK::LeftShift | MK::RightShift => ModifierKey::Shift,
859 MK::LeftControl | MK::RightControl => ModifierKey::Ctrl,
860 MK::LeftAlt | MK::RightAlt | MK::LeftMeta | MK::RightMeta => ModifierKey::Alt,
861 MK::LeftSuper | MK::RightSuper => ModifierKey::Super,
862 _ => return None,
863 });
864 let mut read = Key::new(code);
866 read.exact = exact;
867 if key.kind == KeyEventKind::Release {
868 Event::KeyUp(read)
869 } else {
870 Event::Key(read)
871 }
872 }
873 E::Key(key) => {
874 if key.modifiers.intersects(M::SUPER | M::HYPER) {
877 return None;
878 }
879 let mut held = modifiers(key.modifiers);
880 held.alt |= key.modifiers.contains(M::META);
881 let code = match key.code {
882 K::Char(c) => {
883 if held.shift && c.is_ascii_lowercase() {
886 held.shift = false;
887 KeyCode::Char(c.to_ascii_uppercase())
888 } else {
889 if c.is_uppercase() {
890 held.shift = false;
891 }
892 KeyCode::Char(c)
893 }
894 }
895 K::Enter => KeyCode::Enter,
896 K::Tab => KeyCode::Tab,
897 K::BackTab => {
898 held.shift = false;
899 KeyCode::BackTab
900 }
901 K::Backspace => KeyCode::Backspace,
902 K::Delete => KeyCode::Delete,
903 K::Insert => KeyCode::Insert,
904 K::Esc => KeyCode::Escape,
905 K::Up => KeyCode::Up,
906 K::Down => KeyCode::Down,
907 K::Left => KeyCode::Left,
908 K::Right => KeyCode::Right,
909 K::Home => KeyCode::Home,
910 K::End => KeyCode::End,
911 K::PageUp => KeyCode::PageUp,
912 K::PageDown => KeyCode::PageDown,
913 K::F(n) => KeyCode::F(n),
914 K::Menu => KeyCode::Menu,
915 _ => return None,
916 };
917 let mut read = Key::with(code, held);
918 read.exact = exact;
919 if key.kind == KeyEventKind::Release {
920 Event::KeyUp(read)
921 } else {
922 Event::Key(read)
923 }
924 }
925 E::Mouse(mouse) => Event::Mouse(Mouse {
926 kind: match mouse.kind {
927 MK::Down(b) => MouseKind::Down(button(b)),
928 MK::Up(b) => MouseKind::Up(button(b)),
929 MK::Drag(b) => MouseKind::Drag(button(b)),
930 MK::Moved => MouseKind::Moved,
931 MK::ScrollUp => MouseKind::ScrollUp,
932 MK::ScrollDown => MouseKind::ScrollDown,
933 _ => return None,
934 },
935 column: mouse.column,
936 row: mouse.row,
937 modifiers: modifiers(mouse.modifiers),
938 pixel: None,
939 }),
940 E::Resize(columns, rows) => Event::Resize { columns, rows },
941 E::Paste(text) => Event::Paste(text),
942 _ => return None,
943 })
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949
950 #[cfg(unix)]
951 #[test]
952 fn reads_legacy_bytes() {
953 let keys = |bytes: &[u8]| -> Vec<String> {
954 from_legacy_bytes(bytes)
955 .into_iter()
956 .map(|event| event.key().expect("a key").to_string())
957 .collect()
958 };
959 assert_eq!(
960 keys(b"qA \r\t\x7f\x03"),
961 ["q", "A", "space", "enter", "tab", "backspace", "ctrl+c"]
962 );
963 assert_eq!(
964 keys(b"\x1b[A\x1b[1;5C\x1bOP\x1b[5~\x1b[15~\x1b[Z"),
965 ["up", "ctrl+right", "f1", "pageup", "f5", "shift+tab"]
966 );
967 assert_eq!(keys("\x1bxé\x1b".as_bytes()), ["alt+x", "é", "escape"]);
968 assert_eq!(keys(b"a\x1b[99q b\x1b[1;"), ["a", "space", "b"]);
970 }
971
972 #[test]
973 fn parses_key_names() {
974 assert_eq!(Key::parse("enter"), Some(Key::new(KeyCode::Enter)));
975 assert_eq!(Key::parse("Ctrl+C"), Some(Key::ctrl('c')));
976 assert!(Key::parse("ctrl+c").unwrap().is_interrupt());
977 assert_eq!(Key::parse("shift+tab"), Some(Key::new(KeyCode::BackTab)));
978 assert_eq!(Key::parse("f12"), Some(Key::new(KeyCode::F(12))));
979 assert_eq!(Key::parse("space"), Some(Key::char(' ')));
980 assert_eq!(Key::parse("comma"), Some(Key::char(',')));
981 assert_eq!(Key::parse("ctrl+comma"), Some(Key::ctrl(',')));
982 assert_eq!(Key::parse("x"), Some(Key::char('x')));
983 assert_eq!(Key::parse("ctrl++").unwrap().code, KeyCode::Char('+'));
984 assert_eq!(Key::parse("+"), Some(Key::char('+')));
985 assert_eq!(Key::parse("hyper+x"), None);
986 assert_eq!(Key::parse("fx"), None);
987 }
988
989 #[test]
990 fn punctuation_keys_have_names() {
991 for (name, c) in [
992 ("equals", '='),
993 ("minus", '-'),
994 ("plus", '+'),
995 ("underscore", '_'),
996 ("slash", '/'),
997 ("backslash", '\\'),
998 ("period", '.'),
999 ("dot", '.'),
1000 ("semicolon", ';'),
1001 ("colon", ':'),
1002 ("quote", '\''),
1003 ("backtick", '`'),
1004 ("tilde", '~'),
1005 ("bracketleft", '['),
1006 ("bracketright", ']'),
1007 ("lt", '<'),
1008 ("gt", '>'),
1009 ("question", '?'),
1010 ] {
1011 assert_eq!(Key::parse(name), Some(Key::char(c)), "{name}");
1012 assert_eq!(Key::parse(name).unwrap().to_string(), c.to_string());
1014 }
1015 assert_eq!(Key::parse("Ctrl+Equals"), Some(Key::ctrl('=')));
1016 let alt = Modifiers {
1017 alt: true,
1018 ..Modifiers::NONE
1019 };
1020 assert_eq!(
1021 Key::parse("alt+slash"),
1022 Some(Key::with(KeyCode::Char('/'), alt))
1023 );
1024 assert_eq!(Key::parse("shift+minus"), None);
1026 }
1027
1028 #[test]
1029 fn the_menu_key_has_a_name() {
1030 assert_eq!(Key::parse("menu"), Some(Key::new(KeyCode::Menu)));
1031 assert_eq!(Key::parse("Menu").unwrap().to_string(), "menu");
1032 assert_eq!(Key::parse("shift+menu").unwrap().to_string(), "shift+menu");
1033 }
1034
1035 fn key(name: &str) -> Key {
1036 Key::parse(name).expect("a key name")
1037 }
1038
1039 #[test]
1040 fn names_are_the_keys_terminals_send() {
1041 assert_eq!(Key::parse("shift+a"), Some(Key::char('A')));
1043 assert_eq!(Key::parse("alt+shift+a"), Key::parse("alt+A"));
1044 assert_eq!(
1045 Key::parse("ctrl+shift+a"),
1046 Some(Key::with(KeyCode::Char('A'), Modifiers::CTRL))
1047 );
1048 assert_eq!(Key::parse("ctrl+A"), Some(Key::ctrl('a')));
1049 assert_eq!(Key::parse("shift+1"), None);
1051 assert_eq!(Key::parse("ctrl+i"), Some(Key::ctrl('i')));
1053 assert_eq!(Key::parse("ctrl+m"), Some(Key::ctrl('m')));
1054 assert_eq!(Key::parse("ctrl+["), Some(Key::ctrl('[')));
1055 assert_eq!(Key::parse("ctrl+]"), Some(Key::ctrl(']')));
1056 assert_eq!(Key::parse("ctrl+@"), Some(Key::ctrl('@')));
1057 assert_eq!(Key::parse("ctrl+h"), Some(Key::ctrl('h')));
1058 assert!(key("tab").matches(&key("ctrl+i")));
1061 assert!(key("enter").matches(&key("ctrl+m")));
1062 assert!(key("esc").matches(&key("ctrl+[")));
1063 assert!(Key::ctrl('5').matches(&key("ctrl+]")));
1064 assert!(key("ctrl+space").matches(&key("ctrl+@")));
1065 assert!(key("ctrl+a").matches(&key("ctrl+shift+a")));
1066 assert_eq!(Key::parse("f24"), Some(Key::new(KeyCode::F(24))));
1068 assert_eq!(Key::parse("f0"), None);
1069 assert_eq!(Key::parse("f99"), None);
1070 }
1071
1072 #[test]
1073 fn legacy_keys_match_every_key_they_could_be() {
1074 for (name, sent) in [
1076 ("ctrl+i", "tab"),
1077 ("ctrl+m", "enter"),
1078 ("ctrl+[", "esc"),
1079 ("ctrl+\\", "ctrl+4"),
1080 ("ctrl+]", "ctrl+5"),
1081 ("ctrl+^", "ctrl+6"),
1082 ("ctrl+_", "ctrl+7"),
1083 ("ctrl+@", "ctrl+space"),
1084 ("alt+ctrl+i", "alt+tab"),
1085 ("ctrl+shift+a", "ctrl+a"),
1086 ("ctrl+h", "ctrl+h"),
1087 ("tab", "tab"),
1088 ("f5", "f5"),
1089 ] {
1090 assert_eq!(key(name).legacy(), key(sent), "{name}");
1091 assert!(key(sent).matches(&key(name)), "{sent} fires {name}");
1093 assert!(key(sent).matches(&key(sent)), "{sent} fires {sent}");
1094 }
1095 assert!(key("ctrl+h").matches(&key("backspace")));
1097 assert!(key("alt+ctrl+h").matches(&key("alt+backspace")));
1098 assert!(!key("backspace").matches(&key("ctrl+h")));
1099 assert!(!key("ctrl+i").matches(&key("tab")));
1101 assert!(!key("tab").matches(&key("ctrl+j")));
1102 assert!(!key("ctrl+a").matches(&key("ctrl+b")));
1103 assert!(key("tab").matches_any(&keys(&["x", "ctrl+i"])));
1104 }
1105
1106 fn keys(names: &[&str]) -> Vec<Key> {
1107 names.iter().map(|name| key(name)).collect()
1108 }
1109
1110 #[test]
1111 fn exact_keys_match_only_themselves() {
1112 assert!(key("tab").exact().matches(&key("tab")));
1113 assert!(!key("tab").exact().matches(&key("ctrl+i")));
1114 assert!(key("ctrl+i").exact().matches(&key("ctrl+i")));
1115 assert!(!key("ctrl+i").exact().matches(&key("tab")));
1116 assert!(!key("enter").exact().matches(&key("ctrl+m")));
1117 assert!(!key("esc").exact().matches(&key("ctrl+[")));
1118 assert!(!key("ctrl+h").exact().matches(&key("backspace")));
1119 assert!(!key("ctrl+a").exact().matches(&key("ctrl+shift+a")));
1120 assert_eq!(key("tab").exact(), key("tab"));
1122 assert!(key("tab").exact().is_exact());
1123 assert!(!key("tab").exact().legacy().is_exact());
1124 let mut set = std::collections::HashSet::new();
1125 set.insert(key("q").exact());
1126 assert!(set.contains(&key("q")));
1127 }
1128
1129 #[test]
1130 fn an_exact_name_wins_over_a_key_it_could_be() {
1131 let bindings = [keys(&["ctrl+i"]), keys(&["x", "tab"])];
1132 let sets = || bindings.iter().map(Vec::as_slice);
1133 assert_eq!(key("tab").pick(sets()), Some(1));
1134 assert_eq!(key("tab").exact().pick(sets()), Some(1));
1135 assert_eq!(key("ctrl+i").pick(sets()), Some(0));
1136 let only = [keys(&["ctrl+i"])];
1137 assert_eq!(key("tab").pick(only.iter().map(Vec::as_slice)), Some(0));
1138 assert_eq!(
1139 key("tab").exact().pick(only.iter().map(Vec::as_slice)),
1140 None
1141 );
1142 }
1143
1144 #[test]
1145 fn keys_display_as_they_parse() {
1146 for name in [
1147 "ctrl+c",
1148 "enter",
1149 "pagedown",
1150 "space",
1151 "f3",
1152 "alt+x",
1153 "ctrl+i",
1154 "ctrl+[",
1155 "ctrl+shift+a",
1156 "shift+tab",
1157 "A",
1158 ] {
1159 assert_eq!(key(name).to_string(), name);
1160 }
1161 }
1162
1163 #[test]
1164 fn translates_crossterm_events() {
1165 use crossterm::event::{Event as E, KeyCode as K, KeyEvent, KeyModifiers as M};
1166 let press = E::Key(KeyEvent::new(K::Char('c'), M::CONTROL));
1167 assert_eq!(from_crossterm(press), Some(Event::Key(Key::ctrl('c'))));
1168 let upper = E::Key(KeyEvent::new(K::Char('A'), M::SHIFT));
1169 assert_eq!(from_crossterm(upper), Some(Event::Key(Key::char('A'))));
1170 assert_eq!(
1171 from_crossterm(E::Resize(80, 24)),
1172 Some(Event::Resize {
1173 columns: 80,
1174 rows: 24
1175 })
1176 );
1177 }
1178
1179 #[test]
1180 fn releases_and_repeats_from_crossterm() {
1181 use crossterm::event::{
1182 Event as E, KeyCode as K, KeyEvent, KeyEventKind, KeyModifiers as M,
1183 };
1184 let with = |code, modifiers, kind| {
1185 let mut event = KeyEvent::new(code, modifiers);
1186 event.kind = kind;
1187 E::Key(event)
1188 };
1189 let release = with(K::Char('q'), M::NONE, KeyEventKind::Release);
1190 assert_eq!(from_crossterm(release), Some(Event::KeyUp(Key::char('q'))));
1191 let repeat = with(K::Down, M::NONE, KeyEventKind::Repeat);
1192 assert_eq!(
1193 from_crossterm(repeat),
1194 Some(Event::Key(Key::new(KeyCode::Down)))
1195 );
1196 let up = with(K::Tab, M::NONE, KeyEventKind::Release);
1198 let Some(Event::KeyUp(tab)) = from_crossterm_kitty(up) else {
1199 panic!("a release");
1200 };
1201 assert!(tab.is_exact());
1202 }
1203
1204 #[test]
1205 fn kitty_keys_from_crossterm_are_exact() {
1206 use crossterm::event::{Event as E, KeyCode as K, KeyEvent, KeyModifiers as M};
1207 let read =
1208 |code, modifiers| match from_crossterm_kitty(E::Key(KeyEvent::new(code, modifiers))) {
1209 Some(Event::Key(key)) => Some(key),
1210 _ => None,
1211 };
1212 let ctrl_i = read(K::Char('i'), M::CONTROL).unwrap();
1213 assert!(ctrl_i.is_exact() && ctrl_i.matches(&key("ctrl+i")));
1214 let tab = read(K::Tab, M::NONE).unwrap();
1215 assert!(tab.matches(&key("tab")) && !tab.matches(&key("ctrl+i")));
1216 assert_eq!(
1218 read(K::Char('a'), M::CONTROL | M::SHIFT),
1219 Key::parse("ctrl+shift+a")
1220 );
1221 assert_eq!(read(K::Char('a'), M::SUPER), None);
1223 let Some(Event::Key(legacy_tab)) = from_crossterm(E::Key(KeyEvent::new(K::Tab, M::NONE)))
1225 else {
1226 panic!("a key");
1227 };
1228 assert!(!legacy_tab.is_exact() && legacy_tab.matches(&key("ctrl+i")));
1229 }
1230}