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 #[cfg(test)]
48 pub fn new(key: Key) -> Self {
49 Self {
50 key,
51 ..Self::default()
52 }
53 }
54}
55
56impl From<&KeyEvent> for Input {
57 fn from(event: &KeyEvent) -> Self {
58 let key = match event.code {
59 KeyCode::Char(c) => Key::Char(c),
60 KeyCode::Backspace => Key::Backspace,
61 KeyCode::Enter => Key::Enter,
62 KeyCode::Left => Key::Left,
63 KeyCode::Right => Key::Right,
64 KeyCode::Up => Key::Up,
65 KeyCode::Down => Key::Down,
66 KeyCode::Home => Key::Home,
67 KeyCode::End => Key::End,
68 KeyCode::PageUp => Key::PageUp,
69 KeyCode::PageDown => Key::PageDown,
70 KeyCode::Tab => Key::Tab,
71 KeyCode::BackTab => Key::BackTab,
72 KeyCode::Delete => Key::Delete,
73 KeyCode::Esc => Key::Esc,
74 _ => Key::None,
75 };
76 Self {
77 key,
78 ctrl: event.modifiers.contains(KeyModifiers::CONTROL),
79 alt: event.modifiers.contains(KeyModifiers::ALT),
80 shift: event.modifiers.contains(KeyModifiers::SHIFT),
81 }
82 }
83}
84
85impl From<KeyEvent> for Input {
86 fn from(event: KeyEvent) -> Self {
87 Self::from(&event)
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub(super) enum Action {
94 Insert(char),
95 InsertNewline,
96 InsertTab,
97 DeletePrevChar,
98 DeleteNextChar,
99 DeletePrevWord,
100 DeleteNextWord,
101 DeleteToLineEnd,
102 DeleteToLineStart,
103 Move(CursorMove, bool),
105 Undo,
106 Redo,
107 Copy,
108 Cut,
109 Paste,
110 Nop,
112}
113
114pub(super) fn action_for(input: Input) -> Action {
118 let Input {
119 key,
120 ctrl,
121 alt,
122 shift,
123 } = input;
124
125 if let Key::Char(c) = key
127 && !ctrl
128 && !alt
129 {
130 return Action::Insert(c);
131 }
132
133 match (key, ctrl, alt) {
134 (Key::Enter, _, _) | (Key::Char('m'), true, false) => Action::InsertNewline,
135 (Key::Tab, false, false) => Action::InsertTab,
136
137 (Key::Backspace, false, false) | (Key::Char('h'), true, false) => Action::DeletePrevChar,
138 (Key::Delete, false, false) | (Key::Char('d'), true, false) => Action::DeleteNextChar,
139 (Key::Backspace, _, true)
140 | (Key::Char('w'), true, false)
141 | (Key::Char('h'), false, true) => Action::DeletePrevWord,
142 (Key::Delete, _, true) | (Key::Char('d'), false, true) => Action::DeleteNextWord,
143 (Key::Char('k'), true, false) => Action::DeleteToLineEnd,
144 (Key::Char('u'), true, false) => Action::DeleteToLineStart,
145 (Key::Char('z'), true, false) => Action::Undo,
149 (Key::Char('r'), true, false) => Action::Redo,
150 (Key::Char('w'), false, true) => Action::Copy,
152 (Key::Char('x'), true, false) => Action::Cut,
153 (Key::Char('y'), true, false) => Action::Paste,
154
155 (Key::Right, true, false) | (Key::Char('f'), false, true) => {
156 Action::Move(CursorMove::WordForward, shift)
157 }
158 (Key::Left, true, false) | (Key::Char('b'), false, true) => {
159 Action::Move(CursorMove::WordBack, shift)
160 }
161 (Key::Right, false, false) | (Key::Char('f'), true, false) => {
162 Action::Move(CursorMove::Forward, shift)
163 }
164 (Key::Left, false, false) | (Key::Char('b'), true, false) => {
165 Action::Move(CursorMove::Back, shift)
166 }
167 (Key::Up, false, false) | (Key::Char('p'), true, false) => {
168 Action::Move(CursorMove::Up, shift)
169 }
170 (Key::Down, false, false) | (Key::Char('n'), true, false) => {
171 Action::Move(CursorMove::Down, shift)
172 }
173 (Key::Home, false, false) | (Key::Char('a'), true, false) => {
174 Action::Move(CursorMove::Head, shift)
175 }
176 (Key::End, false, false) | (Key::Char('e'), true, false) => {
177 Action::Move(CursorMove::End, shift)
178 }
179 (Key::Home, true, _) | (Key::Char('<'), false, true) => {
180 Action::Move(CursorMove::Top, shift)
181 }
182 (Key::End, true, _) | (Key::Char('>'), false, true) => {
183 Action::Move(CursorMove::Bottom, shift)
184 }
185 (Key::PageDown, false, false) | (Key::Char('v'), true, false) => {
186 Action::Move(CursorMove::DownBy(0), shift)
187 }
188 (Key::PageUp, false, false) | (Key::Char('v'), false, true) => {
189 Action::Move(CursorMove::UpBy(0), shift)
190 }
191
192 _ => Action::Nop,
193 }
194}
195
196impl TextArea {
197 pub fn input(&mut self, input: impl Into<Input>) -> bool {
200 match action_for(input.into()) {
201 Action::Insert(c) => self.insert_char(c),
202 Action::InsertNewline => self.insert_newline(),
203 Action::InsertTab => self.insert_tab(),
204 Action::DeletePrevChar => self.delete_prev_char(),
205 Action::DeleteNextChar => self.delete_next_char(),
206 Action::DeletePrevWord => self.delete_prev_word(),
207 Action::DeleteNextWord => self.delete_next_word(),
208 Action::DeleteToLineEnd => self.delete_line_by_end(),
209 Action::DeleteToLineStart => self.delete_line_by_head(),
210 Action::Move(movement, true) => self.move_cursor_selecting(movement),
211 Action::Move(movement, false) => self.move_cursor(movement),
212 Action::Undo => self.undo(),
213 Action::Redo => self.redo(),
214 Action::Copy => self.copy(),
215 Action::Cut => self.cut(),
216 Action::Paste => self.paste(),
217 Action::Nop => false,
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests;