tui_textarea/input/
crossterm.rs

1use super::{Input, Key};
2use crate::crossterm::event::{
3    Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind,
4};
5
6impl From<Event> for Input {
7    /// Convert [`crossterm::event::Event`] into [`Input`].
8    fn from(event: Event) -> Self {
9        match event {
10            Event::Key(key) => Self::from(key),
11            Event::Mouse(mouse) => Self::from(mouse),
12            _ => Self::default(),
13        }
14    }
15}
16
17impl From<KeyCode> for Key {
18    /// Convert [`crossterm::event::KeyCode`] into [`Key`].
19    fn from(code: KeyCode) -> Self {
20        match code {
21            KeyCode::Char(c) => Key::Char(c),
22            KeyCode::Backspace => Key::Backspace,
23            KeyCode::Enter => Key::Enter,
24            KeyCode::Left => Key::Left,
25            KeyCode::Right => Key::Right,
26            KeyCode::Up => Key::Up,
27            KeyCode::Down => Key::Down,
28            KeyCode::Tab => Key::Tab,
29            KeyCode::Delete => Key::Delete,
30            KeyCode::Home => Key::Home,
31            KeyCode::End => Key::End,
32            KeyCode::PageUp => Key::PageUp,
33            KeyCode::PageDown => Key::PageDown,
34            KeyCode::Esc => Key::Esc,
35            KeyCode::F(x) => Key::F(x),
36            _ => Key::Null,
37        }
38    }
39}
40
41impl From<KeyEvent> for Input {
42    /// Convert [`crossterm::event::KeyEvent`] into [`Input`].
43    fn from(key: KeyEvent) -> Self {
44        if key.kind == KeyEventKind::Release {
45            // On Windows or when `crossterm::event::PushKeyboardEnhancementFlags` is set,
46            // key release event can be reported. Ignore it. (#14)
47            return Self::default();
48        }
49
50        let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
51        let alt = key.modifiers.contains(KeyModifiers::ALT);
52        let shift = key.modifiers.contains(KeyModifiers::SHIFT);
53        let key = Key::from(key.code);
54
55        Self {
56            key,
57            ctrl,
58            alt,
59            shift,
60        }
61    }
62}
63
64impl From<MouseEventKind> for Key {
65    /// Convert [`crossterm::event::MouseEventKind`] into [`Key`].
66    fn from(kind: MouseEventKind) -> Self {
67        match kind {
68            MouseEventKind::ScrollDown => Key::MouseScrollDown,
69            MouseEventKind::ScrollUp => Key::MouseScrollUp,
70            _ => Key::Null,
71        }
72    }
73}
74
75impl From<MouseEvent> for Input {
76    /// Convert [`crossterm::event::MouseEvent`] into [`Input`].
77    fn from(mouse: MouseEvent) -> Self {
78        let key = Key::from(mouse.kind);
79        let ctrl = mouse.modifiers.contains(KeyModifiers::CONTROL);
80        let alt = mouse.modifiers.contains(KeyModifiers::ALT);
81        let shift = mouse.modifiers.contains(KeyModifiers::SHIFT);
82        Self {
83            key,
84            ctrl,
85            alt,
86            shift,
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::crossterm::event::KeyEventState;
95    use crate::input::tests::input;
96
97    fn key_event(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
98        KeyEvent {
99            code,
100            modifiers,
101            kind: KeyEventKind::Press,
102            state: KeyEventState::empty(),
103        }
104    }
105
106    fn mouse_event(kind: MouseEventKind, modifiers: KeyModifiers) -> MouseEvent {
107        MouseEvent {
108            kind,
109            column: 1,
110            row: 1,
111            modifiers,
112        }
113    }
114
115    #[test]
116    fn key_to_input() {
117        for (from, to) in [
118            (
119                key_event(KeyCode::Char('a'), KeyModifiers::empty()),
120                input(Key::Char('a'), false, false, false),
121            ),
122            (
123                key_event(KeyCode::Enter, KeyModifiers::empty()),
124                input(Key::Enter, false, false, false),
125            ),
126            (
127                key_event(KeyCode::Left, KeyModifiers::CONTROL),
128                input(Key::Left, true, false, false),
129            ),
130            (
131                key_event(KeyCode::Right, KeyModifiers::SHIFT),
132                input(Key::Right, false, false, true),
133            ),
134            (
135                key_event(KeyCode::Home, KeyModifiers::ALT),
136                input(Key::Home, false, true, false),
137            ),
138            (
139                key_event(
140                    KeyCode::F(1),
141                    KeyModifiers::ALT | KeyModifiers::CONTROL | KeyModifiers::SHIFT,
142                ),
143                input(Key::F(1), true, true, true),
144            ),
145            (
146                key_event(KeyCode::NumLock, KeyModifiers::CONTROL),
147                input(Key::Null, true, false, false),
148            ),
149        ] {
150            assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
151        }
152    }
153
154    #[test]
155    fn mouse_to_input() {
156        for (from, to) in [
157            (
158                mouse_event(MouseEventKind::ScrollDown, KeyModifiers::empty()),
159                input(Key::MouseScrollDown, false, false, false),
160            ),
161            (
162                mouse_event(MouseEventKind::ScrollUp, KeyModifiers::CONTROL),
163                input(Key::MouseScrollUp, true, false, false),
164            ),
165            (
166                mouse_event(MouseEventKind::ScrollUp, KeyModifiers::SHIFT),
167                input(Key::MouseScrollUp, false, false, true),
168            ),
169            (
170                mouse_event(MouseEventKind::ScrollDown, KeyModifiers::ALT),
171                input(Key::MouseScrollDown, false, true, false),
172            ),
173            (
174                mouse_event(
175                    MouseEventKind::ScrollUp,
176                    KeyModifiers::CONTROL | KeyModifiers::ALT,
177                ),
178                input(Key::MouseScrollUp, true, true, false),
179            ),
180            (
181                mouse_event(MouseEventKind::Moved, KeyModifiers::CONTROL),
182                input(Key::Null, true, false, false),
183            ),
184        ] {
185            assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
186        }
187    }
188
189    #[test]
190    fn event_to_input() {
191        for (from, to) in [
192            (
193                Event::Key(key_event(KeyCode::Char('a'), KeyModifiers::empty())),
194                input(Key::Char('a'), false, false, false),
195            ),
196            (
197                Event::Mouse(mouse_event(
198                    MouseEventKind::ScrollDown,
199                    KeyModifiers::empty(),
200                )),
201                input(Key::MouseScrollDown, false, false, false),
202            ),
203            (Event::FocusGained, input(Key::Null, false, false, false)),
204        ] {
205            assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
206        }
207    }
208
209    // Regression for https://github.com/rhysd/tui-textarea/issues/14
210    #[test]
211    fn ignore_key_release_event() {
212        let mut from = key_event(KeyCode::Char('a'), KeyModifiers::empty());
213        from.kind = KeyEventKind::Release;
214        let to = input(Key::Null, false, false, false);
215        assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
216    }
217}