Skip to main content

ratatui_textarea/input/
crossterm.rs

1use super::{Input, Key};
2use ratatui_crossterm::crossterm::event::{
3    Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind,
4};
5
6impl From<Event> for Input {
7    /// Convert [`ratatui_crossterm::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 [`ratatui_crossterm::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 [`ratatui_crossterm::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        let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
50        let alt = key.modifiers.contains(KeyModifiers::ALT);
51
52        if key.code == KeyCode::BackTab {
53            // `crossterm` does not support Shift+Tab, but reports it as BackTab.
54            return Self {
55                key: Key::Tab,
56                shift: true,
57                ctrl,
58                alt,
59            };
60        }
61
62        let shift = key.modifiers.contains(KeyModifiers::SHIFT);
63        let key = Key::from(key.code);
64
65        Self {
66            key,
67            ctrl,
68            alt,
69            shift,
70        }
71    }
72}
73
74impl From<MouseEventKind> for Key {
75    /// Convert [`ratatui_crossterm::crossterm::event::MouseEventKind`] into [`Key`].
76    fn from(kind: MouseEventKind) -> Self {
77        match kind {
78            MouseEventKind::ScrollDown => Key::MouseScrollDown,
79            MouseEventKind::ScrollUp => Key::MouseScrollUp,
80            _ => Key::Null,
81        }
82    }
83}
84
85impl From<MouseEvent> for Input {
86    /// Convert [`ratatui_crossterm::crossterm::event::MouseEvent`] into [`Input`].
87    fn from(mouse: MouseEvent) -> Self {
88        let key = Key::from(mouse.kind);
89        let ctrl = mouse.modifiers.contains(KeyModifiers::CONTROL);
90        let alt = mouse.modifiers.contains(KeyModifiers::ALT);
91        let shift = mouse.modifiers.contains(KeyModifiers::SHIFT);
92        Self {
93            key,
94            ctrl,
95            alt,
96            shift,
97        }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::input::tests::input;
105    use ratatui_crossterm::crossterm::event::KeyEventState;
106
107    fn key_event(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
108        KeyEvent {
109            code,
110            modifiers,
111            kind: KeyEventKind::Press,
112            state: KeyEventState::empty(),
113        }
114    }
115
116    fn mouse_event(kind: MouseEventKind, modifiers: KeyModifiers) -> MouseEvent {
117        MouseEvent {
118            kind,
119            column: 1,
120            row: 1,
121            modifiers,
122        }
123    }
124
125    #[test]
126    fn key_to_input() {
127        for (from, to) in [
128            (
129                key_event(KeyCode::Char('a'), KeyModifiers::empty()),
130                input(Key::Char('a'), false, false, false),
131            ),
132            (
133                key_event(KeyCode::Enter, KeyModifiers::empty()),
134                input(Key::Enter, false, false, false),
135            ),
136            (
137                key_event(KeyCode::Left, KeyModifiers::CONTROL),
138                input(Key::Left, true, false, false),
139            ),
140            (
141                key_event(KeyCode::Right, KeyModifiers::SHIFT),
142                input(Key::Right, false, false, true),
143            ),
144            (
145                key_event(KeyCode::Home, KeyModifiers::ALT),
146                input(Key::Home, false, true, false),
147            ),
148            (
149                key_event(
150                    KeyCode::F(1),
151                    KeyModifiers::ALT | KeyModifiers::CONTROL | KeyModifiers::SHIFT,
152                ),
153                input(Key::F(1), true, true, true),
154            ),
155            (
156                key_event(KeyCode::NumLock, KeyModifiers::CONTROL),
157                input(Key::Null, true, false, false),
158            ),
159        ] {
160            assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
161        }
162    }
163
164    #[test]
165    fn mouse_to_input() {
166        for (from, to) in [
167            (
168                mouse_event(MouseEventKind::ScrollDown, KeyModifiers::empty()),
169                input(Key::MouseScrollDown, false, false, false),
170            ),
171            (
172                mouse_event(MouseEventKind::ScrollUp, KeyModifiers::CONTROL),
173                input(Key::MouseScrollUp, true, false, false),
174            ),
175            (
176                mouse_event(MouseEventKind::ScrollUp, KeyModifiers::SHIFT),
177                input(Key::MouseScrollUp, false, false, true),
178            ),
179            (
180                mouse_event(MouseEventKind::ScrollDown, KeyModifiers::ALT),
181                input(Key::MouseScrollDown, false, true, false),
182            ),
183            (
184                mouse_event(
185                    MouseEventKind::ScrollUp,
186                    KeyModifiers::CONTROL | KeyModifiers::ALT,
187                ),
188                input(Key::MouseScrollUp, true, true, false),
189            ),
190            (
191                mouse_event(MouseEventKind::Moved, KeyModifiers::CONTROL),
192                input(Key::Null, true, false, false),
193            ),
194        ] {
195            assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
196        }
197    }
198
199    #[test]
200    fn event_to_input() {
201        for (from, to) in [
202            (
203                Event::Key(key_event(KeyCode::Char('a'), KeyModifiers::empty())),
204                input(Key::Char('a'), false, false, false),
205            ),
206            (
207                Event::Mouse(mouse_event(
208                    MouseEventKind::ScrollDown,
209                    KeyModifiers::empty(),
210                )),
211                input(Key::MouseScrollDown, false, false, false),
212            ),
213            (Event::FocusGained, input(Key::Null, false, false, false)),
214        ] {
215            assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
216        }
217    }
218
219    // Regression for https://github.com/rhysd/tui-textarea/issues/14
220    #[test]
221    fn ignore_key_release_event() {
222        let mut from = key_event(KeyCode::Char('a'), KeyModifiers::empty());
223        from.kind = KeyEventKind::Release;
224        let to = input(Key::Null, false, false, false);
225        assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
226    }
227}