1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
10
11use super::TextArea;
12use super::cursor::CursorMove;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum Key {
17 Char(char),
18 Backspace,
19 Enter,
20 Left,
21 Right,
22 Up,
23 Down,
24 Home,
25 End,
26 PageUp,
27 PageDown,
28 Tab,
29 BackTab,
30 Delete,
31 Esc,
32 #[default]
34 None,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub struct Input {
40 pub key: Key,
41 pub ctrl: bool,
42 pub alt: bool,
43 pub shift: bool,
44}
45
46impl Input {
47 pub fn new(key: Key) -> Self {
48 Self {
49 key,
50 ..Self::default()
51 }
52 }
53}
54
55impl From<&KeyEvent> for Input {
56 fn from(event: &KeyEvent) -> Self {
57 let key = match event.code {
58 KeyCode::Char(c) => Key::Char(c),
59 KeyCode::Backspace => Key::Backspace,
60 KeyCode::Enter => Key::Enter,
61 KeyCode::Left => Key::Left,
62 KeyCode::Right => Key::Right,
63 KeyCode::Up => Key::Up,
64 KeyCode::Down => Key::Down,
65 KeyCode::Home => Key::Home,
66 KeyCode::End => Key::End,
67 KeyCode::PageUp => Key::PageUp,
68 KeyCode::PageDown => Key::PageDown,
69 KeyCode::Tab => Key::Tab,
70 KeyCode::BackTab => Key::BackTab,
71 KeyCode::Delete => Key::Delete,
72 KeyCode::Esc => Key::Esc,
73 _ => Key::None,
74 };
75 Self {
76 key,
77 ctrl: event.modifiers.contains(KeyModifiers::CONTROL),
78 alt: event.modifiers.contains(KeyModifiers::ALT),
79 shift: event.modifiers.contains(KeyModifiers::SHIFT),
80 }
81 }
82}
83
84impl From<KeyEvent> for Input {
85 fn from(event: KeyEvent) -> Self {
86 Self::from(&event)
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub(super) enum Action {
93 Insert(char),
94 InsertNewline,
95 InsertTab,
96 DeletePrevChar,
97 DeleteNextChar,
98 DeletePrevWord,
99 DeleteNextWord,
100 DeleteToLineEnd,
101 DeleteToLineStart,
102 Move(CursorMove, bool),
104 Undo,
105 Redo,
106 Copy,
107 Cut,
108 Paste,
109 Nop,
111}
112
113pub(super) fn action_for(input: Input) -> Action {
117 let Input {
118 key,
119 ctrl,
120 alt,
121 shift,
122 } = input;
123
124 if let Key::Char(c) = key
126 && !ctrl
127 && !alt
128 {
129 return Action::Insert(c);
130 }
131
132 match (key, ctrl, alt) {
133 (Key::Enter, _, _) | (Key::Char('m'), true, false) => Action::InsertNewline,
134 (Key::Tab, false, false) => Action::InsertTab,
135
136 (Key::Backspace, false, false) | (Key::Char('h'), true, false) => Action::DeletePrevChar,
137 (Key::Delete, false, false) | (Key::Char('d'), true, false) => Action::DeleteNextChar,
138 (Key::Backspace, _, true)
139 | (Key::Char('w'), true, false)
140 | (Key::Char('h'), false, true) => Action::DeletePrevWord,
141 (Key::Delete, _, true) | (Key::Char('d'), false, true) => Action::DeleteNextWord,
142 (Key::Char('k'), true, false) => Action::DeleteToLineEnd,
143 (Key::Char('u'), true, false) => Action::DeleteToLineStart,
144 (Key::Char('z'), true, false) => Action::Undo,
148 (Key::Char('r'), true, false) => Action::Redo,
149 (Key::Char('w'), false, true) => Action::Copy,
151 (Key::Char('x'), true, false) => Action::Cut,
152 (Key::Char('y'), true, false) => Action::Paste,
153
154 (Key::Right, true, false) | (Key::Char('f'), false, true) => {
155 Action::Move(CursorMove::WordForward, shift)
156 }
157 (Key::Left, true, false) | (Key::Char('b'), false, true) => {
158 Action::Move(CursorMove::WordBack, shift)
159 }
160 (Key::Right, false, false) | (Key::Char('f'), true, false) => {
161 Action::Move(CursorMove::Forward, shift)
162 }
163 (Key::Left, false, false) | (Key::Char('b'), true, false) => {
164 Action::Move(CursorMove::Back, shift)
165 }
166 (Key::Up, false, false) | (Key::Char('p'), true, false) => {
167 Action::Move(CursorMove::Up, shift)
168 }
169 (Key::Down, false, false) | (Key::Char('n'), true, false) => {
170 Action::Move(CursorMove::Down, shift)
171 }
172 (Key::Home, false, false) | (Key::Char('a'), true, false) => {
173 Action::Move(CursorMove::Head, shift)
174 }
175 (Key::End, false, false) | (Key::Char('e'), true, false) => {
176 Action::Move(CursorMove::End, shift)
177 }
178 (Key::Home, true, _) | (Key::Char('<'), false, true) => {
179 Action::Move(CursorMove::Top, shift)
180 }
181 (Key::End, true, _) | (Key::Char('>'), false, true) => {
182 Action::Move(CursorMove::Bottom, shift)
183 }
184 (Key::PageDown, false, false) | (Key::Char('v'), true, false) => {
185 Action::Move(CursorMove::DownBy(0), shift)
186 }
187 (Key::PageUp, false, false) | (Key::Char('v'), false, true) => {
188 Action::Move(CursorMove::UpBy(0), shift)
189 }
190
191 _ => Action::Nop,
192 }
193}
194
195impl TextArea {
196 pub fn input(&mut self, input: impl Into<Input>) -> bool {
199 match action_for(input.into()) {
200 Action::Insert(c) => self.insert_char(c),
201 Action::InsertNewline => self.insert_newline(),
202 Action::InsertTab => self.insert_tab(),
203 Action::DeletePrevChar => self.delete_prev_char(),
204 Action::DeleteNextChar => self.delete_next_char(),
205 Action::DeletePrevWord => self.delete_prev_word(),
206 Action::DeleteNextWord => self.delete_next_word(),
207 Action::DeleteToLineEnd => self.delete_line_by_end(),
208 Action::DeleteToLineStart => self.delete_line_by_head(),
209 Action::Move(movement, true) => self.move_cursor_selecting(movement),
210 Action::Move(movement, false) => self.move_cursor(movement),
211 Action::Undo => self.undo(),
212 Action::Redo => self.redo(),
213 Action::Copy => self.copy(),
214 Action::Cut => self.cut(),
215 Action::Paste => self.paste(),
216 Action::Nop => false,
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn key(code: KeyCode, modifiers: KeyModifiers) -> Input {
226 Input::from(KeyEvent::new(code, modifiers))
227 }
228
229 #[test]
230 fn plain_characters_insert_themselves() {
231 assert_eq!(
232 action_for(key(KeyCode::Char('a'), KeyModifiers::NONE)),
233 Action::Insert('a')
234 );
235 assert_eq!(
236 action_for(key(KeyCode::Char('A'), KeyModifiers::SHIFT)),
237 Action::Insert('A')
238 );
239 }
240
241 #[test]
242 fn readline_bindings_match_their_arrow_key_equivalents() {
243 let pairs = [
244 (
245 key(KeyCode::Char('a'), KeyModifiers::CONTROL),
246 key(KeyCode::Home, KeyModifiers::NONE),
247 ),
248 (
249 key(KeyCode::Char('e'), KeyModifiers::CONTROL),
250 key(KeyCode::End, KeyModifiers::NONE),
251 ),
252 (
253 key(KeyCode::Char('f'), KeyModifiers::CONTROL),
254 key(KeyCode::Right, KeyModifiers::NONE),
255 ),
256 (
257 key(KeyCode::Char('b'), KeyModifiers::CONTROL),
258 key(KeyCode::Left, KeyModifiers::NONE),
259 ),
260 (
261 key(KeyCode::Char('h'), KeyModifiers::CONTROL),
262 key(KeyCode::Backspace, KeyModifiers::NONE),
263 ),
264 (
265 key(KeyCode::Char('d'), KeyModifiers::CONTROL),
266 key(KeyCode::Delete, KeyModifiers::NONE),
267 ),
268 ];
269 for (emacs, arrow) in pairs {
270 assert_eq!(
271 action_for(emacs),
272 action_for(arrow),
273 "{emacs:?} vs {arrow:?}"
274 );
275 }
276 }
277
278 #[test]
279 fn ctrl_u_kills_to_line_start_and_ctrl_z_undoes() {
280 assert_eq!(
281 action_for(key(KeyCode::Char('u'), KeyModifiers::CONTROL)),
282 Action::DeleteToLineStart
283 );
284 assert_eq!(
285 action_for(key(KeyCode::Char('z'), KeyModifiers::CONTROL)),
286 Action::Undo
287 );
288 }
289
290 #[test]
291 fn ctrl_j_never_edits() {
292 assert_eq!(
293 action_for(key(KeyCode::Char('j'), KeyModifiers::CONTROL)),
294 Action::Nop
295 );
296 }
297
298 #[test]
299 fn shifted_movement_extends_the_selection() {
300 assert_eq!(
301 action_for(key(KeyCode::Right, KeyModifiers::SHIFT)),
302 Action::Move(CursorMove::Forward, true)
303 );
304 assert_eq!(
305 action_for(key(KeyCode::Right, KeyModifiers::NONE)),
306 Action::Move(CursorMove::Forward, false)
307 );
308 }
309
310 #[test]
311 fn ctrl_arrows_move_by_word() {
312 assert_eq!(
313 action_for(key(KeyCode::Right, KeyModifiers::CONTROL)),
314 Action::Move(CursorMove::WordForward, false)
315 );
316 assert_eq!(
317 action_for(key(KeyCode::Left, KeyModifiers::CONTROL)),
318 Action::Move(CursorMove::WordBack, false)
319 );
320 }
321
322 #[test]
323 fn unmapped_keys_do_nothing() {
324 assert_eq!(
325 action_for(key(KeyCode::Esc, KeyModifiers::NONE)),
326 Action::Nop
327 );
328 assert_eq!(
329 action_for(key(KeyCode::F(5), KeyModifiers::NONE)),
330 Action::Nop
331 );
332 assert_eq!(
333 action_for(key(KeyCode::BackTab, KeyModifiers::SHIFT)),
334 Action::Nop
335 );
336 }
337
338 #[test]
339 fn key_events_convert_with_their_modifiers() {
340 let input = key(
341 KeyCode::Char('k'),
342 KeyModifiers::CONTROL | KeyModifiers::ALT,
343 );
344 assert_eq!(input.key, Key::Char('k'));
345 assert!(input.ctrl);
346 assert!(input.alt);
347 assert!(!input.shift);
348 }
349}