1#![forbid(unsafe_code)]
2
3use bitflags::bitflags;
17use crossterm::event as cte;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum Event {
25 Key(KeyEvent),
27
28 Mouse(MouseEvent),
30
31 Resize {
33 width: u16,
35 height: u16,
37 },
38
39 Paste(PasteEvent),
41
42 Focus(bool),
46
47 Clipboard(ClipboardEvent),
49
50 Tick,
56}
57
58impl Event {
59 #[must_use]
61 pub fn from_crossterm(event: cte::Event) -> Option<Self> {
62 map_crossterm_event_internal(event)
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct KeyEvent {
69 pub code: KeyCode,
71
72 pub modifiers: Modifiers,
74
75 pub kind: KeyEventKind,
77}
78
79impl KeyEvent {
80 #[must_use]
82 pub const fn new(code: KeyCode) -> Self {
83 Self {
84 code,
85 modifiers: Modifiers::NONE,
86 kind: KeyEventKind::Press,
87 }
88 }
89
90 #[must_use]
92 pub const fn with_modifiers(mut self, modifiers: Modifiers) -> Self {
93 self.modifiers = modifiers;
94 self
95 }
96
97 #[must_use]
99 pub const fn with_kind(mut self, kind: KeyEventKind) -> Self {
100 self.kind = kind;
101 self
102 }
103
104 #[must_use]
106 pub fn is_char(&self, c: char) -> bool {
107 matches!(self.code, KeyCode::Char(ch) if ch == c)
108 }
109
110 #[must_use]
112 pub const fn ctrl(&self) -> bool {
113 self.modifiers.contains(Modifiers::CTRL)
114 }
115
116 #[must_use]
118 pub const fn alt(&self) -> bool {
119 self.modifiers.contains(Modifiers::ALT)
120 }
121
122 #[must_use]
124 pub const fn shift(&self) -> bool {
125 self.modifiers.contains(Modifiers::SHIFT)
126 }
127
128 #[must_use]
130 pub const fn super_key(&self) -> bool {
131 self.modifiers.contains(Modifiers::SUPER)
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137pub enum KeyCode {
138 Char(char),
140
141 Enter,
143
144 Escape,
146
147 Backspace,
149
150 Tab,
152
153 BackTab,
155
156 Delete,
158
159 Insert,
161
162 Home,
164
165 End,
167
168 PageUp,
170
171 PageDown,
173
174 Up,
176
177 Down,
179
180 Left,
182
183 Right,
185
186 F(u8),
188
189 Null,
191
192 MediaPlayPause,
194
195 MediaStop,
197
198 MediaNextTrack,
200
201 MediaPrevTrack,
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
207pub enum KeyEventKind {
208 #[default]
210 Press,
211
212 Repeat,
214
215 Release,
217}
218
219bitflags! {
220 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222 pub struct Modifiers: u8 {
223 const NONE = 0b0000;
225 const SHIFT = 0b0001;
227 const ALT = 0b0010;
229 const CTRL = 0b0100;
231 const SUPER = 0b1000;
233 }
234}
235
236impl Default for Modifiers {
237 fn default() -> Self {
238 Self::NONE
239 }
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub struct MouseEvent {
245 pub kind: MouseEventKind,
247
248 pub x: u16,
250
251 pub y: u16,
253
254 pub modifiers: Modifiers,
256}
257
258impl MouseEvent {
259 #[must_use]
261 pub const fn new(kind: MouseEventKind, x: u16, y: u16) -> Self {
262 Self {
263 kind,
264 x,
265 y,
266 modifiers: Modifiers::NONE,
267 }
268 }
269
270 #[must_use]
272 pub const fn with_modifiers(mut self, modifiers: Modifiers) -> Self {
273 self.modifiers = modifiers;
274 self
275 }
276
277 #[must_use]
279 pub const fn position(&self) -> (u16, u16) {
280 (self.x, self.y)
281 }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
286pub enum MouseEventKind {
287 Down(MouseButton),
289
290 Up(MouseButton),
292
293 Drag(MouseButton),
295
296 Moved,
298
299 ScrollUp,
301
302 ScrollDown,
304
305 ScrollLeft,
307
308 ScrollRight,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
314pub enum MouseButton {
315 Left,
317
318 Right,
320
321 Middle,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
327pub struct PasteEvent {
328 pub text: String,
330
331 pub bracketed: bool,
337}
338
339impl PasteEvent {
340 #[must_use]
342 pub fn new(text: impl Into<String>, bracketed: bool) -> Self {
343 Self {
344 text: text.into(),
345 bracketed,
346 }
347 }
348
349 #[must_use]
351 pub fn bracketed(text: impl Into<String>) -> Self {
352 Self::new(text, true)
353 }
354}
355
356#[derive(Debug, Clone, PartialEq, Eq)]
360pub struct ClipboardEvent {
361 pub content: String,
363
364 pub source: ClipboardSource,
366}
367
368impl ClipboardEvent {
369 #[must_use]
371 pub fn new(content: impl Into<String>, source: ClipboardSource) -> Self {
372 Self {
373 content: content.into(),
374 source,
375 }
376 }
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
381pub enum ClipboardSource {
382 Osc52,
384
385 #[default]
387 Unknown,
388}
389
390fn map_crossterm_event_internal(event: cte::Event) -> Option<Event> {
391 match event {
392 cte::Event::Key(key) => map_key_event(key).map(Event::Key),
393 cte::Event::Mouse(mouse) => Some(Event::Mouse(map_mouse_event(mouse))),
394 cte::Event::Resize(width, height) => Some(Event::Resize { width, height }),
395 cte::Event::Paste(text) => Some(Event::Paste(PasteEvent::bracketed(text))),
396 cte::Event::FocusGained => Some(Event::Focus(true)),
397 cte::Event::FocusLost => Some(Event::Focus(false)),
398 }
399}
400
401fn map_key_event(event: cte::KeyEvent) -> Option<KeyEvent> {
402 let code = map_key_code(event.code)?;
403 let modifiers = map_modifiers(event.modifiers);
404 let kind = map_key_kind(event.kind);
405 Some(KeyEvent {
406 code,
407 modifiers,
408 kind,
409 })
410}
411
412fn map_key_kind(kind: cte::KeyEventKind) -> KeyEventKind {
413 match kind {
414 cte::KeyEventKind::Press => KeyEventKind::Press,
415 cte::KeyEventKind::Repeat => KeyEventKind::Repeat,
416 cte::KeyEventKind::Release => KeyEventKind::Release,
417 }
418}
419
420pub(crate) fn map_key_code(code: cte::KeyCode) -> Option<KeyCode> {
421 match code {
422 cte::KeyCode::Backspace => Some(KeyCode::Backspace),
423 cte::KeyCode::Enter => Some(KeyCode::Enter),
424 cte::KeyCode::Left => Some(KeyCode::Left),
425 cte::KeyCode::Right => Some(KeyCode::Right),
426 cte::KeyCode::Up => Some(KeyCode::Up),
427 cte::KeyCode::Down => Some(KeyCode::Down),
428 cte::KeyCode::Home => Some(KeyCode::Home),
429 cte::KeyCode::End => Some(KeyCode::End),
430 cte::KeyCode::PageUp => Some(KeyCode::PageUp),
431 cte::KeyCode::PageDown => Some(KeyCode::PageDown),
432 cte::KeyCode::Tab => Some(KeyCode::Tab),
433 cte::KeyCode::BackTab => Some(KeyCode::BackTab),
434 cte::KeyCode::Delete => Some(KeyCode::Delete),
435 cte::KeyCode::Insert => Some(KeyCode::Insert),
436 cte::KeyCode::F(n) => Some(KeyCode::F(n)),
437 cte::KeyCode::Char(c) => Some(KeyCode::Char(c)),
438 cte::KeyCode::Null => Some(KeyCode::Null),
439 cte::KeyCode::Esc => Some(KeyCode::Escape),
440 cte::KeyCode::Media(media) => map_media_key(media),
441 _ => None,
442 }
443}
444
445fn map_media_key(code: cte::MediaKeyCode) -> Option<KeyCode> {
446 match code {
447 cte::MediaKeyCode::Play | cte::MediaKeyCode::Pause | cte::MediaKeyCode::PlayPause => {
448 Some(KeyCode::MediaPlayPause)
449 }
450 cte::MediaKeyCode::Stop => Some(KeyCode::MediaStop),
451 cte::MediaKeyCode::TrackNext => Some(KeyCode::MediaNextTrack),
452 cte::MediaKeyCode::TrackPrevious => Some(KeyCode::MediaPrevTrack),
453 _ => None,
454 }
455}
456
457fn map_modifiers(modifiers: cte::KeyModifiers) -> Modifiers {
458 let mut mapped = Modifiers::NONE;
459 if modifiers.contains(cte::KeyModifiers::SHIFT) {
460 mapped |= Modifiers::SHIFT;
461 }
462 if modifiers.contains(cte::KeyModifiers::ALT) {
463 mapped |= Modifiers::ALT;
464 }
465 if modifiers.contains(cte::KeyModifiers::CONTROL) {
466 mapped |= Modifiers::CTRL;
467 }
468 if modifiers.contains(cte::KeyModifiers::SUPER)
469 || modifiers.contains(cte::KeyModifiers::HYPER)
470 || modifiers.contains(cte::KeyModifiers::META)
471 {
472 mapped |= Modifiers::SUPER;
473 }
474 mapped
475}
476
477fn map_mouse_event(event: cte::MouseEvent) -> MouseEvent {
478 let kind = match event.kind {
479 cte::MouseEventKind::Down(button) => MouseEventKind::Down(map_mouse_button(button)),
480 cte::MouseEventKind::Up(button) => MouseEventKind::Up(map_mouse_button(button)),
481 cte::MouseEventKind::Drag(button) => MouseEventKind::Drag(map_mouse_button(button)),
482 cte::MouseEventKind::Moved => MouseEventKind::Moved,
483 cte::MouseEventKind::ScrollUp => MouseEventKind::ScrollUp,
484 cte::MouseEventKind::ScrollDown => MouseEventKind::ScrollDown,
485 cte::MouseEventKind::ScrollLeft => MouseEventKind::ScrollLeft,
486 cte::MouseEventKind::ScrollRight => MouseEventKind::ScrollRight,
487 };
488
489 MouseEvent::new(kind, event.column, event.row).with_modifiers(map_modifiers(event.modifiers))
490}
491
492fn map_mouse_button(button: cte::MouseButton) -> MouseButton {
493 match button {
494 cte::MouseButton::Left => MouseButton::Left,
495 cte::MouseButton::Right => MouseButton::Right,
496 cte::MouseButton::Middle => MouseButton::Middle,
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503 use crossterm::event as ct_event;
504
505 #[test]
506 fn key_event_is_char() {
507 let event = KeyEvent::new(KeyCode::Char('q'));
508 assert!(event.is_char('q'));
509 assert!(!event.is_char('x'));
510 }
511
512 #[test]
513 fn key_event_modifiers() {
514 let event = KeyEvent::new(KeyCode::Char('c')).with_modifiers(Modifiers::CTRL);
515 assert!(event.ctrl());
516 assert!(!event.alt());
517 assert!(!event.shift());
518 assert!(!event.super_key());
519 }
520
521 #[test]
522 fn key_event_combined_modifiers() {
523 let event =
524 KeyEvent::new(KeyCode::Char('s')).with_modifiers(Modifiers::CTRL | Modifiers::SHIFT);
525 assert!(event.ctrl());
526 assert!(event.shift());
527 assert!(!event.alt());
528 }
529
530 #[test]
531 fn key_event_kind() {
532 let press = KeyEvent::new(KeyCode::Enter);
533 assert_eq!(press.kind, KeyEventKind::Press);
534
535 let release = press.with_kind(KeyEventKind::Release);
536 assert_eq!(release.kind, KeyEventKind::Release);
537 }
538
539 #[test]
540 fn mouse_event_position() {
541 let event = MouseEvent::new(MouseEventKind::Down(MouseButton::Left), 10, 20);
542 assert_eq!(event.position(), (10, 20));
543 assert_eq!(event.x, 10);
544 assert_eq!(event.y, 20);
545 }
546
547 #[test]
548 fn mouse_event_with_modifiers() {
549 let event = MouseEvent::new(MouseEventKind::Moved, 0, 0).with_modifiers(Modifiers::ALT);
550 assert_eq!(event.modifiers, Modifiers::ALT);
551 }
552
553 #[test]
554 fn paste_event_creation() {
555 let paste = PasteEvent::bracketed("hello world");
556 assert_eq!(paste.text, "hello world");
557 assert!(paste.bracketed);
558 }
559
560 #[test]
561 fn clipboard_event_creation() {
562 let clip = ClipboardEvent::new("copied text", ClipboardSource::Osc52);
563 assert_eq!(clip.content, "copied text");
564 assert_eq!(clip.source, ClipboardSource::Osc52);
565 }
566
567 #[test]
568 fn event_variants() {
569 let _key = Event::Key(KeyEvent::new(KeyCode::Char('a')));
571 let _mouse = Event::Mouse(MouseEvent::new(
572 MouseEventKind::Down(MouseButton::Left),
573 0,
574 0,
575 ));
576 let _resize = Event::Resize {
577 width: 80,
578 height: 24,
579 };
580 let _paste = Event::Paste(PasteEvent::bracketed("test"));
581 let _focus = Event::Focus(true);
582 let _clipboard = Event::Clipboard(ClipboardEvent::new("test", ClipboardSource::Unknown));
583 let _tick = Event::Tick;
584 }
585
586 #[test]
587 fn modifiers_default() {
588 assert_eq!(Modifiers::default(), Modifiers::NONE);
589 }
590
591 #[test]
592 fn key_event_kind_default() {
593 assert_eq!(KeyEventKind::default(), KeyEventKind::Press);
594 }
595
596 #[test]
597 fn clipboard_source_default() {
598 assert_eq!(ClipboardSource::default(), ClipboardSource::Unknown);
599 }
600
601 #[test]
602 fn function_keys() {
603 let f1 = KeyEvent::new(KeyCode::F(1));
604 let f12 = KeyEvent::new(KeyCode::F(12));
605 assert_eq!(f1.code, KeyCode::F(1));
606 assert_eq!(f12.code, KeyCode::F(12));
607 }
608
609 #[test]
610 fn event_is_clone_and_eq() {
611 let event = Event::Key(KeyEvent::new(KeyCode::Char('x')));
612 let cloned = event.clone();
613 assert_eq!(event, cloned);
614 }
615
616 #[test]
619 fn map_modifiers_ctrl() {
620 let mapped = map_modifiers(ct_event::KeyModifiers::CONTROL);
621 assert!(mapped.contains(Modifiers::CTRL));
622 assert!(!mapped.contains(Modifiers::SHIFT));
623 }
624
625 #[test]
626 fn map_modifiers_alt() {
627 let mapped = map_modifiers(ct_event::KeyModifiers::ALT);
628 assert!(mapped.contains(Modifiers::ALT));
629 }
630
631 #[test]
632 fn map_modifiers_super_variants() {
633 let super_mapped = map_modifiers(ct_event::KeyModifiers::SUPER);
634 assert!(super_mapped.contains(Modifiers::SUPER));
635
636 let hyper_mapped = map_modifiers(ct_event::KeyModifiers::HYPER);
637 assert!(hyper_mapped.contains(Modifiers::SUPER));
638
639 let meta_mapped = map_modifiers(ct_event::KeyModifiers::META);
640 assert!(meta_mapped.contains(Modifiers::SUPER));
641 }
642
643 #[test]
644 fn map_modifiers_combined() {
645 let combined = ct_event::KeyModifiers::SHIFT | ct_event::KeyModifiers::CONTROL;
646 let mapped = map_modifiers(combined);
647 assert!(mapped.contains(Modifiers::SHIFT));
648 assert!(mapped.contains(Modifiers::CTRL));
649 assert!(!mapped.contains(Modifiers::ALT));
650 }
651
652 #[test]
653 fn map_mouse_button_all() {
654 assert_eq!(
655 map_mouse_button(ct_event::MouseButton::Left),
656 MouseButton::Left
657 );
658 assert_eq!(
659 map_mouse_button(ct_event::MouseButton::Right),
660 MouseButton::Right
661 );
662 assert_eq!(
663 map_mouse_button(ct_event::MouseButton::Middle),
664 MouseButton::Middle
665 );
666 }
667
668 #[test]
669 fn map_mouse_event_down() {
670 let ct_event = ct_event::MouseEvent {
671 kind: ct_event::MouseEventKind::Down(ct_event::MouseButton::Left),
672 column: 10,
673 row: 5,
674 modifiers: ct_event::KeyModifiers::NONE,
675 };
676 let mapped = map_mouse_event(ct_event);
677 assert!(matches!(
678 mapped.kind,
679 MouseEventKind::Down(MouseButton::Left)
680 ));
681 assert_eq!(mapped.x, 10);
682 assert_eq!(mapped.y, 5);
683 }
684
685 #[test]
686 fn map_mouse_event_up() {
687 let ct_event = ct_event::MouseEvent {
688 kind: ct_event::MouseEventKind::Up(ct_event::MouseButton::Right),
689 column: 20,
690 row: 15,
691 modifiers: ct_event::KeyModifiers::NONE,
692 };
693 let mapped = map_mouse_event(ct_event);
694 assert!(matches!(
695 mapped.kind,
696 MouseEventKind::Up(MouseButton::Right)
697 ));
698 assert_eq!(mapped.x, 20);
699 assert_eq!(mapped.y, 15);
700 }
701
702 #[test]
703 fn map_mouse_event_drag() {
704 let ct_event = ct_event::MouseEvent {
705 kind: ct_event::MouseEventKind::Drag(ct_event::MouseButton::Middle),
706 column: 5,
707 row: 10,
708 modifiers: ct_event::KeyModifiers::NONE,
709 };
710 let mapped = map_mouse_event(ct_event);
711 assert!(matches!(
712 mapped.kind,
713 MouseEventKind::Drag(MouseButton::Middle)
714 ));
715 }
716
717 #[test]
718 fn map_mouse_event_moved() {
719 let ct_event = ct_event::MouseEvent {
720 kind: ct_event::MouseEventKind::Moved,
721 column: 0,
722 row: 0,
723 modifiers: ct_event::KeyModifiers::NONE,
724 };
725 let mapped = map_mouse_event(ct_event);
726 assert!(matches!(mapped.kind, MouseEventKind::Moved));
727 }
728
729 #[test]
730 fn map_mouse_event_scroll() {
731 let scroll_up = ct_event::MouseEvent {
732 kind: ct_event::MouseEventKind::ScrollUp,
733 column: 0,
734 row: 0,
735 modifiers: ct_event::KeyModifiers::NONE,
736 };
737 let scroll_down = ct_event::MouseEvent {
738 kind: ct_event::MouseEventKind::ScrollDown,
739 column: 0,
740 row: 0,
741 modifiers: ct_event::KeyModifiers::NONE,
742 };
743 let scroll_left = ct_event::MouseEvent {
744 kind: ct_event::MouseEventKind::ScrollLeft,
745 column: 0,
746 row: 0,
747 modifiers: ct_event::KeyModifiers::NONE,
748 };
749 let scroll_right = ct_event::MouseEvent {
750 kind: ct_event::MouseEventKind::ScrollRight,
751 column: 0,
752 row: 0,
753 modifiers: ct_event::KeyModifiers::NONE,
754 };
755
756 assert!(matches!(
757 map_mouse_event(scroll_up).kind,
758 MouseEventKind::ScrollUp
759 ));
760 assert!(matches!(
761 map_mouse_event(scroll_down).kind,
762 MouseEventKind::ScrollDown
763 ));
764 assert!(matches!(
765 map_mouse_event(scroll_left).kind,
766 MouseEventKind::ScrollLeft
767 ));
768 assert!(matches!(
769 map_mouse_event(scroll_right).kind,
770 MouseEventKind::ScrollRight
771 ));
772 }
773
774 #[test]
775 fn map_mouse_event_modifiers() {
776 let ct_event = ct_event::MouseEvent {
777 kind: ct_event::MouseEventKind::Down(ct_event::MouseButton::Left),
778 column: 0,
779 row: 0,
780 modifiers: ct_event::KeyModifiers::SHIFT | ct_event::KeyModifiers::ALT,
781 };
782 let mapped = map_mouse_event(ct_event);
783 assert!(mapped.modifiers.contains(Modifiers::SHIFT));
784 assert!(mapped.modifiers.contains(Modifiers::ALT));
785 }
786
787 #[test]
788 fn map_key_event_char() {
789 let ct_event = ct_event::KeyEvent {
790 code: ct_event::KeyCode::Char('x'),
791 modifiers: ct_event::KeyModifiers::CONTROL,
792 kind: ct_event::KeyEventKind::Press,
793 state: ct_event::KeyEventState::NONE,
794 };
795 let mapped = map_key_event(ct_event).expect("should map");
796 assert_eq!(mapped.code, KeyCode::Char('x'));
797 assert!(mapped.modifiers.contains(Modifiers::CTRL));
798 assert_eq!(mapped.kind, KeyEventKind::Press);
799 }
800
801 #[test]
802 fn map_key_event_function_key() {
803 let ct_event = ct_event::KeyEvent {
804 code: ct_event::KeyCode::F(5),
805 modifiers: ct_event::KeyModifiers::NONE,
806 kind: ct_event::KeyEventKind::Press,
807 state: ct_event::KeyEventState::NONE,
808 };
809 let mapped = map_key_event(ct_event).expect("should map");
810 assert_eq!(mapped.code, KeyCode::F(5));
811 }
812
813 #[test]
814 fn map_crossterm_event_key() {
815 let ct_event = ct_event::Event::Key(ct_event::KeyEvent {
816 code: ct_event::KeyCode::Enter,
817 modifiers: ct_event::KeyModifiers::NONE,
818 kind: ct_event::KeyEventKind::Press,
819 state: ct_event::KeyEventState::NONE,
820 });
821 let mapped = map_crossterm_event_internal(ct_event).expect("should map");
822 assert!(matches!(mapped, Event::Key(_)));
823 }
824
825 #[test]
826 fn map_crossterm_event_mouse() {
827 let ct_event = ct_event::Event::Mouse(ct_event::MouseEvent {
828 kind: ct_event::MouseEventKind::Down(ct_event::MouseButton::Left),
829 column: 10,
830 row: 5,
831 modifiers: ct_event::KeyModifiers::NONE,
832 });
833 let mapped = map_crossterm_event_internal(ct_event).expect("should map");
834 assert!(matches!(mapped, Event::Mouse(_)));
835 }
836
837 #[test]
838 fn map_crossterm_event_resize() {
839 let ct_event = ct_event::Event::Resize(80, 24);
840 let mapped = map_crossterm_event_internal(ct_event).expect("should map");
841 assert!(matches!(
842 mapped,
843 Event::Resize {
844 width: 80,
845 height: 24
846 }
847 ));
848 }
849
850 #[test]
851 fn map_crossterm_event_paste() {
852 let ct_event = ct_event::Event::Paste("hello world".to_string());
853 let mapped = map_crossterm_event_internal(ct_event).expect("should map");
854 match mapped {
855 Event::Paste(paste) => assert_eq!(paste.text, "hello world"),
856 _ => panic!("expected Paste event"),
857 }
858 }
859
860 #[test]
861 fn map_crossterm_event_focus() {
862 let gained = ct_event::Event::FocusGained;
863 let lost = ct_event::Event::FocusLost;
864
865 assert!(matches!(
866 map_crossterm_event_internal(gained),
867 Some(Event::Focus(true))
868 ));
869 assert!(matches!(
870 map_crossterm_event_internal(lost),
871 Some(Event::Focus(false))
872 ));
873 }
874}