1use crossterm::event::{KeyCode, KeyModifiers};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Action {
6 Submit,
7 InsertNewline,
8 Cancel,
9 ClearLine,
10 DeleteWordBackward,
11 DeleteToEnd,
12 Insert(char),
13 Complete,
14 CursorLeft,
15 CursorRight,
16 LineStart,
17 LineEnd,
18 HistoryPrev,
19 HistoryNext,
20 Backspace,
21 DeleteForward,
22 ToggleToolOutput,
23 NoOp,
24}
25
26pub fn classify(code: KeyCode, modifiers: KeyModifiers) -> Action {
27 let ctrl = modifiers.contains(KeyModifiers::CONTROL);
28 let shift = modifiers.contains(KeyModifiers::SHIFT);
29 let alt = modifiers.contains(KeyModifiers::ALT);
30
31 match (code, ctrl) {
32 (KeyCode::Enter, ctrl) if ctrl || shift || alt => Action::InsertNewline,
33 (KeyCode::Enter, _) => Action::Submit,
34 (KeyCode::Char('j'), true) => Action::InsertNewline,
39 (KeyCode::Char('c'), true) => Action::Cancel,
40 (KeyCode::Char('u'), true) => Action::ClearLine,
41 (KeyCode::Char('w'), true) => Action::DeleteWordBackward,
42 (KeyCode::Char('k'), true) => Action::DeleteToEnd,
43 (KeyCode::Char('a'), true) => Action::LineStart,
46 (KeyCode::Char('e'), true) => Action::LineEnd,
47 (KeyCode::Char('o'), true) => Action::ToggleToolOutput,
49 (KeyCode::Char('h'), true) => Action::Backspace,
57 (KeyCode::Char('?'), true) => Action::DeleteForward,
61 (KeyCode::Char(c), false) => Action::Insert(c),
62 (KeyCode::Tab, _) => Action::Complete,
63 (KeyCode::Left, _) => Action::CursorLeft,
64 (KeyCode::Right, _) => Action::CursorRight,
65 (KeyCode::Home, _) => Action::LineStart,
66 (KeyCode::End, _) => Action::LineEnd,
67 (KeyCode::Up, _) => Action::HistoryPrev,
68 (KeyCode::Down, _) => Action::HistoryNext,
69 (KeyCode::Backspace, _) => Action::Backspace,
70 (KeyCode::Delete, _) => Action::DeleteForward,
71 _ => Action::NoOp,
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crossterm::event::{KeyCode, KeyModifiers};
79
80 fn k(code: KeyCode, modifiers: KeyModifiers) -> Action {
81 classify(code, modifiers)
82 }
83
84 #[test]
85 fn enter_submits() {
86 assert_eq!(k(KeyCode::Enter, KeyModifiers::NONE), Action::Submit);
87 }
88
89 #[test]
90 fn shift_enter_inserts_newline() {
91 assert_eq!(
92 k(KeyCode::Enter, KeyModifiers::SHIFT),
93 Action::InsertNewline
94 );
95 }
96
97 #[test]
98 fn alt_enter_inserts_newline() {
99 assert_eq!(k(KeyCode::Enter, KeyModifiers::ALT), Action::InsertNewline);
100 }
101
102 #[test]
103 fn alt_shift_enter_inserts_newline() {
104 assert_eq!(
105 k(KeyCode::Enter, KeyModifiers::ALT | KeyModifiers::SHIFT),
106 Action::InsertNewline
107 );
108 }
109
110 #[test]
111 fn ctrl_j_inserts_newline() {
112 assert_eq!(
120 k(KeyCode::Char('j'), KeyModifiers::CONTROL),
121 Action::InsertNewline
122 );
123 }
124
125 #[test]
126 fn ctrl_c_cancels() {
127 assert_eq!(k(KeyCode::Char('c'), KeyModifiers::CONTROL), Action::Cancel);
128 }
129
130 #[test]
131 fn ctrl_u_clears_line() {
132 assert_eq!(
133 k(KeyCode::Char('u'), KeyModifiers::CONTROL),
134 Action::ClearLine
135 );
136 }
137
138 #[test]
139 fn ctrl_w_deletes_word() {
140 assert_eq!(
141 k(KeyCode::Char('w'), KeyModifiers::CONTROL),
142 Action::DeleteWordBackward
143 );
144 }
145
146 #[test]
147 fn ctrl_k_deletes_to_end() {
148 assert_eq!(
149 k(KeyCode::Char('k'), KeyModifiers::CONTROL),
150 Action::DeleteToEnd
151 );
152 }
153
154 #[test]
155 fn ctrl_a_jumps_to_line_start() {
156 assert_eq!(
157 k(KeyCode::Char('a'), KeyModifiers::CONTROL),
158 Action::LineStart
159 );
160 }
161
162 #[test]
163 fn ctrl_e_jumps_to_line_end() {
164 assert_eq!(
165 k(KeyCode::Char('e'), KeyModifiers::CONTROL),
166 Action::LineEnd
167 );
168 }
169
170 #[test]
171 fn ctrl_h_acts_as_backspace() {
172 assert_eq!(
175 k(KeyCode::Char('h'), KeyModifiers::CONTROL),
176 Action::Backspace
177 );
178 }
179
180 #[test]
181 fn ctrl_questionmark_acts_as_delete_forward() {
182 assert_eq!(
183 k(KeyCode::Char('?'), KeyModifiers::CONTROL),
184 Action::DeleteForward,
185 );
186 }
187
188 #[test]
189 fn plain_letter_inserts() {
190 assert_eq!(
191 k(KeyCode::Char('a'), KeyModifiers::NONE),
192 Action::Insert('a')
193 );
194 }
195
196 #[test]
197 fn shifted_letter_inserts() {
198 assert_eq!(
199 k(KeyCode::Char('A'), KeyModifiers::SHIFT),
200 Action::Insert('A')
201 );
202 }
203
204 #[test]
205 fn tab_completes() {
206 assert_eq!(k(KeyCode::Tab, KeyModifiers::NONE), Action::Complete);
207 }
208
209 #[test]
210 fn arrow_navigation() {
211 assert_eq!(k(KeyCode::Left, KeyModifiers::NONE), Action::CursorLeft);
212 assert_eq!(k(KeyCode::Right, KeyModifiers::NONE), Action::CursorRight);
213 assert_eq!(k(KeyCode::Up, KeyModifiers::NONE), Action::HistoryPrev);
214 assert_eq!(k(KeyCode::Down, KeyModifiers::NONE), Action::HistoryNext);
215 assert_eq!(k(KeyCode::Home, KeyModifiers::NONE), Action::LineStart);
216 assert_eq!(k(KeyCode::End, KeyModifiers::NONE), Action::LineEnd);
217 }
218
219 #[test]
220 fn backspace_and_delete() {
221 assert_eq!(k(KeyCode::Backspace, KeyModifiers::NONE), Action::Backspace);
222 assert_eq!(
223 k(KeyCode::Delete, KeyModifiers::NONE),
224 Action::DeleteForward
225 );
226 }
227
228 #[test]
229 fn unknown_key_is_noop() {
230 assert_eq!(k(KeyCode::F(5), KeyModifiers::NONE), Action::NoOp);
231 }
232}