supercode_frontend_tui/terminal/
event_stream.rs1use crossterm::event::Event;
10use crossterm::event::KeyEvent;
11
12#[derive(Clone, Debug, Eq, PartialEq)]
14pub enum TerminalEvent {
15 Key(KeyEvent),
16 Paste(String),
17 Resize { width: u16, height: u16 },
18 FocusChanged(bool),
19}
20
21pub fn map_crossterm_event(event: Event) -> Option<TerminalEvent> {
23 match event {
24 Event::Key(key) => Some(TerminalEvent::Key(key)),
25 Event::Paste(text) => Some(TerminalEvent::Paste(text)),
26 Event::Resize(width, height) => Some(TerminalEvent::Resize { width, height }),
27 Event::FocusGained => Some(TerminalEvent::FocusChanged(true)),
28 Event::FocusLost => Some(TerminalEvent::FocusChanged(false)),
29 Event::Mouse(_) => None,
30 }
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub struct TerminalState {
36 pub width: u16,
37 pub height: u16,
38 pub focused: bool,
39}
40
41impl TerminalState {
42 pub const fn new(width: u16, height: u16) -> Self {
43 Self {
44 width,
45 height,
46 focused: true,
47 }
48 }
49
50 pub fn apply(&mut self, event: &TerminalEvent) -> bool {
52 match event {
53 TerminalEvent::Resize { width, height } => {
54 let changed = self.width != *width || self.height != *height;
55 self.width = *width;
56 self.height = *height;
57 changed
58 }
59 TerminalEvent::FocusChanged(focused) => {
60 let changed = self.focused != *focused;
61 self.focused = *focused;
62 changed
63 }
64 TerminalEvent::Key(_) | TerminalEvent::Paste(_) => false,
65 }
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use crossterm::event::KeyCode;
72 use crossterm::event::KeyEvent;
73 use crossterm::event::KeyModifiers;
74
75 use super::*;
76
77 #[test]
78 fn maps_focus_resize_paste_and_key_without_app_events() {
79 assert_eq!(
80 map_crossterm_event(Event::FocusLost),
81 Some(TerminalEvent::FocusChanged(false))
82 );
83 assert_eq!(
84 map_crossterm_event(Event::FocusGained),
85 Some(TerminalEvent::FocusChanged(true))
86 );
87 assert_eq!(
88 map_crossterm_event(Event::Resize(80, 24)),
89 Some(TerminalEvent::Resize {
90 width: 80,
91 height: 24
92 })
93 );
94 assert_eq!(
95 map_crossterm_event(Event::Paste("hello".into())),
96 Some(TerminalEvent::Paste("hello".into()))
97 );
98 let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
99 assert_eq!(
100 map_crossterm_event(Event::Key(key)),
101 Some(TerminalEvent::Key(key))
102 );
103 }
104
105 #[test]
106 fn focus_and_resize_change_state_and_require_redraw() {
107 let mut state = TerminalState::new(80, 24);
108 assert!(state.apply(&TerminalEvent::FocusChanged(false)));
109 assert!(!state.focused);
110 assert!(!state.apply(&TerminalEvent::FocusChanged(false)));
111 assert!(state.apply(&TerminalEvent::Resize {
112 width: 160,
113 height: 48,
114 }));
115 assert_eq!((state.width, state.height), (160, 48));
116 assert!(!state.apply(&TerminalEvent::Resize {
117 width: 160,
118 height: 48,
119 }));
120 }
121}