1use alacritty_terminal::term::TermMode;
2use gpui::keymap::Keystroke;
3
4/*
5Connection events still to do:
6- Reporting mouse events correctly.
7- Reporting scrolls
8- Correctly bracketing a paste
9- Storing changed colors
10- Focus change sequence
11*/
12
13#[derive(Debug)]
14pub enum Modifiers {
15 None,
16 Alt,
17 Ctrl,
18 Shift,
19 CtrlShift,
20 Other,
21}
22
23impl Modifiers {
24 fn new(ks: &Keystroke) -> Self {
25 match (ks.alt, ks.ctrl, ks.shift, ks.cmd) {
26 (false, false, false, false) => Modifiers::None,
27 (true, false, false, false) => Modifiers::Alt,
28 (false, true, false, false) => Modifiers::Ctrl,
29 (false, false, true, false) => Modifiers::Shift,
30 (false, true, true, false) => Modifiers::CtrlShift,
31 _ => Modifiers::Other,
32 }
33 }
34
35 fn any(&self) -> bool {
36 match &self {
37 Modifiers::None => false,
38 Modifiers::Alt => true,
39 Modifiers::Ctrl => true,
40 Modifiers::Shift => true,
41 Modifiers::CtrlShift => true,
42 Modifiers::Other => true,
43 }
44 }
45}
46
47pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option<String> {
48 let modifiers = Modifiers::new(&keystroke);
49
50 // Manual Bindings including modifiers
51 let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) {
52 //Basic special keys
53 ("space", Modifiers::None) => Some(" ".to_string()),
54 ("tab", Modifiers::None) => Some("\x09".to_string()),
55 ("escape", Modifiers::None) => Some("\x1b".to_string()),
56 ("enter", Modifiers::None) => Some("\x0d".to_string()),
57 ("backspace", Modifiers::None) => Some("\x7f".to_string()),
58 //Interesting escape codes
59 ("tab", Modifiers::Shift) => Some("\x1b[Z".to_string()),
60 ("backspace", Modifiers::Alt) => Some("\x1b\x7f".to_string()),
61 ("backspace", Modifiers::Shift) => Some("\x7f".to_string()),
62 ("home", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
63 Some("\x1b[1;2H".to_string())
64 }
65 ("end", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
66 Some("\x1b[1;2F".to_string())
67 }
68 ("pageup", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
69 Some("\x1b[5;2~".to_string())
70 }
71 ("pagedown", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
72 Some("\x1b[6;2~".to_string())
73 }
74 ("home", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
75 Some("\x1bOH".to_string())
76 }
77 ("home", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
78 Some("\x1b[H".to_string())
79 }
80 ("end", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
81 Some("\x1bOF".to_string())
82 }
83 ("end", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
84 Some("\x1b[F".to_string())
85 }
86 ("up", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
87 Some("\x1bOA".to_string())
88 }
89 ("up", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
90 Some("\x1b[A".to_string())
91 }
92 ("down", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
93 Some("\x1bOB".to_string())
94 }
95 ("down", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
96 Some("\x1b[B".to_string())
97 }
98 ("right", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
99 Some("\x1bOC".to_string())
100 }
101 ("right", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
102 Some("\x1b[C".to_string())
103 }
104 ("left", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
105 Some("\x1bOD".to_string())
106 }
107 ("left", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
108 Some("\x1b[D".to_string())
109 }
110 ("back", Modifiers::None) => Some("\x7f".to_string()),
111 ("insert", Modifiers::None) => Some("\x1b[2~".to_string()),
112 ("delete", Modifiers::None) => Some("\x1b[3~".to_string()),
113 ("pageup", Modifiers::None) => Some("\x1b[5~".to_string()),
114 ("pagedown", Modifiers::None) => Some("\x1b[6~".to_string()),
115 ("f1", Modifiers::None) => Some("\x1bOP".to_string()),
116 ("f2", Modifiers::None) => Some("\x1bOQ".to_string()),
117 ("f3", Modifiers::None) => Some("\x1bOR".to_string()),
118 ("f4", Modifiers::None) => Some("\x1bOS".to_string()),
119 ("f5", Modifiers::None) => Some("\x1b[15~".to_string()),
120 ("f6", Modifiers::None) => Some("\x1b[17~".to_string()),
121 ("f7", Modifiers::None) => Some("\x1b[18~".to_string()),
122 ("f8", Modifiers::None) => Some("\x1b[19~".to_string()),
123 ("f9", Modifiers::None) => Some("\x1b[20~".to_string()),
124 ("f10", Modifiers::None) => Some("\x1b[21~".to_string()),
125 ("f11", Modifiers::None) => Some("\x1b[23~".to_string()),
126 ("f12", Modifiers::None) => Some("\x1b[24~".to_string()),
127 ("f13", Modifiers::None) => Some("\x1b[25~".to_string()),
128 ("f14", Modifiers::None) => Some("\x1b[26~".to_string()),
129 ("f15", Modifiers::None) => Some("\x1b[28~".to_string()),
130 ("f16", Modifiers::None) => Some("\x1b[29~".to_string()),
131 ("f17", Modifiers::None) => Some("\x1b[31~".to_string()),
132 ("f18", Modifiers::None) => Some("\x1b[32~".to_string()),
133 ("f19", Modifiers::None) => Some("\x1b[33~".to_string()),
134 ("f20", Modifiers::None) => Some("\x1b[34~".to_string()),
135 // NumpadEnter, Action::Esc("\n".into());
136 //Mappings for caret notation keys
137 ("a", Modifiers::Ctrl) => Some("\x01".to_string()), //1
138 ("A", Modifiers::CtrlShift) => Some("\x01".to_string()), //1
139 ("b", Modifiers::Ctrl) => Some("\x02".to_string()), //2
140 ("B", Modifiers::CtrlShift) => Some("\x02".to_string()), //2
141 ("c", Modifiers::Ctrl) => Some("\x03".to_string()), //3
142 ("C", Modifiers::CtrlShift) => Some("\x03".to_string()), //3
143 ("d", Modifiers::Ctrl) => Some("\x04".to_string()), //4
144 ("D", Modifiers::CtrlShift) => Some("\x04".to_string()), //4
145 ("e", Modifiers::Ctrl) => Some("\x05".to_string()), //5
146 ("E", Modifiers::CtrlShift) => Some("\x05".to_string()), //5
147 ("f", Modifiers::Ctrl) => Some("\x06".to_string()), //6
148 ("F", Modifiers::CtrlShift) => Some("\x06".to_string()), //6
149 ("g", Modifiers::Ctrl) => Some("\x07".to_string()), //7
150 ("G", Modifiers::CtrlShift) => Some("\x07".to_string()), //7
151 ("h", Modifiers::Ctrl) => Some("\x08".to_string()), //8
152 ("H", Modifiers::CtrlShift) => Some("\x08".to_string()), //8
153 ("i", Modifiers::Ctrl) => Some("\x09".to_string()), //9
154 ("I", Modifiers::CtrlShift) => Some("\x09".to_string()), //9
155 ("j", Modifiers::Ctrl) => Some("\x0a".to_string()), //10
156 ("J", Modifiers::CtrlShift) => Some("\x0a".to_string()), //10
157 ("k", Modifiers::Ctrl) => Some("\x0b".to_string()), //11
158 ("K", Modifiers::CtrlShift) => Some("\x0b".to_string()), //11
159 ("l", Modifiers::Ctrl) => Some("\x0c".to_string()), //12
160 ("L", Modifiers::CtrlShift) => Some("\x0c".to_string()), //12
161 ("m", Modifiers::Ctrl) => Some("\x0d".to_string()), //13
162 ("M", Modifiers::CtrlShift) => Some("\x0d".to_string()), //13
163 ("n", Modifiers::Ctrl) => Some("\x0e".to_string()), //14
164 ("N", Modifiers::CtrlShift) => Some("\x0e".to_string()), //14
165 ("o", Modifiers::Ctrl) => Some("\x0f".to_string()), //15
166 ("O", Modifiers::CtrlShift) => Some("\x0f".to_string()), //15
167 ("p", Modifiers::Ctrl) => Some("\x10".to_string()), //16
168 ("P", Modifiers::CtrlShift) => Some("\x10".to_string()), //16
169 ("q", Modifiers::Ctrl) => Some("\x11".to_string()), //17
170 ("Q", Modifiers::CtrlShift) => Some("\x11".to_string()), //17
171 ("r", Modifiers::Ctrl) => Some("\x12".to_string()), //18
172 ("R", Modifiers::CtrlShift) => Some("\x12".to_string()), //18
173 ("s", Modifiers::Ctrl) => Some("\x13".to_string()), //19
174 ("S", Modifiers::CtrlShift) => Some("\x13".to_string()), //19
175 ("t", Modifiers::Ctrl) => Some("\x14".to_string()), //20
176 ("T", Modifiers::CtrlShift) => Some("\x14".to_string()), //20
177 ("u", Modifiers::Ctrl) => Some("\x15".to_string()), //21
178 ("U", Modifiers::CtrlShift) => Some("\x15".to_string()), //21
179 ("v", Modifiers::Ctrl) => Some("\x16".to_string()), //22
180 ("V", Modifiers::CtrlShift) => Some("\x16".to_string()), //22
181 ("w", Modifiers::Ctrl) => Some("\x17".to_string()), //23
182 ("W", Modifiers::CtrlShift) => Some("\x17".to_string()), //23
183 ("x", Modifiers::Ctrl) => Some("\x18".to_string()), //24
184 ("X", Modifiers::CtrlShift) => Some("\x18".to_string()), //24
185 ("y", Modifiers::Ctrl) => Some("\x19".to_string()), //25
186 ("Y", Modifiers::CtrlShift) => Some("\x19".to_string()), //25
187 ("z", Modifiers::Ctrl) => Some("\x1a".to_string()), //26
188 ("Z", Modifiers::CtrlShift) => Some("\x1a".to_string()), //26
189 ("@", Modifiers::Ctrl) => Some("\x00".to_string()), //0
190 ("[", Modifiers::Ctrl) => Some("\x1b".to_string()), //27
191 ("\\", Modifiers::Ctrl) => Some("\x1c".to_string()), //28
192 ("]", Modifiers::Ctrl) => Some("\x1d".to_string()), //29
193 ("^", Modifiers::Ctrl) => Some("\x1e".to_string()), //30
194 ("_", Modifiers::Ctrl) => Some("\x1f".to_string()), //31
195 ("?", Modifiers::Ctrl) => Some("\x7f".to_string()), //127
196 _ => None,
197 };
198 if manual_esc_str.is_some() {
199 return manual_esc_str;
200 }
201
202 // Automated bindings applying modifiers
203 if modifiers.any() {
204 let modifier_code = modifier_code(&keystroke);
205 let modified_esc_str = match keystroke.key.as_ref() {
206 "up" => Some(format!("\x1b[1;{}A", modifier_code)),
207 "down" => Some(format!("\x1b[1;{}B", modifier_code)),
208 "right" => Some(format!("\x1b[1;{}C", modifier_code)),
209 "left" => Some(format!("\x1b[1;{}D", modifier_code)),
210 "f1" => Some(format!("\x1b[1;{}P", modifier_code)),
211 "f2" => Some(format!("\x1b[1;{}Q", modifier_code)),
212 "f3" => Some(format!("\x1b[1;{}R", modifier_code)),
213 "f4" => Some(format!("\x1b[1;{}S", modifier_code)),
214 "F5" => Some(format!("\x1b[15;{}~", modifier_code)),
215 "f6" => Some(format!("\x1b[17;{}~", modifier_code)),
216 "f7" => Some(format!("\x1b[18;{}~", modifier_code)),
217 "f8" => Some(format!("\x1b[19;{}~", modifier_code)),
218 "f9" => Some(format!("\x1b[20;{}~", modifier_code)),
219 "f10" => Some(format!("\x1b[21;{}~", modifier_code)),
220 "f11" => Some(format!("\x1b[23;{}~", modifier_code)),
221 "f12" => Some(format!("\x1b[24;{}~", modifier_code)),
222 "f13" => Some(format!("\x1b[25;{}~", modifier_code)),
223 "f14" => Some(format!("\x1b[26;{}~", modifier_code)),
224 "f15" => Some(format!("\x1b[28;{}~", modifier_code)),
225 "f16" => Some(format!("\x1b[29;{}~", modifier_code)),
226 "f17" => Some(format!("\x1b[31;{}~", modifier_code)),
227 "f18" => Some(format!("\x1b[32;{}~", modifier_code)),
228 "f19" => Some(format!("\x1b[33;{}~", modifier_code)),
229 "f20" => Some(format!("\x1b[34;{}~", modifier_code)),
230 _ if modifier_code == 2 => None,
231 "insert" => Some(format!("\x1b[2;{}~", modifier_code)),
232 "pageup" => Some(format!("\x1b[5;{}~", modifier_code)),
233 "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)),
234 "end" => Some(format!("\x1b[1;{}F", modifier_code)),
235 "home" => Some(format!("\x1b[1;{}H", modifier_code)),
236 _ => None,
237 };
238 if modified_esc_str.is_some() {
239 return modified_esc_str;
240 }
241 }
242
243 //Fallback to sending the keystroke input directly
244 //Skin colors in utf8 are implemented as a seperate, invisible character
245 //that modifies the associated emoji. Some languages may have similarly
246 //implemented modifiers, e.g. certain diacritics that can be typed as a single character.
247 //This means that we need to assume some user input can result in multi-byte,
248 //multi-char strings. This is somewhat difficult, as GPUI normalizes all
249 //keys into a string representation. Hence, the check here to filter out GPUI
250 //keys that weren't captured above.
251 if !matches_gpui_key_str(&keystroke.key) {
252 return Some(keystroke.key.clone());
253 } else {
254 None
255 }
256}
257
258///Checks if the given string matches a GPUI key string.
259///Table made from reading the source at gpui/src/platform/mac/event.rs
260fn matches_gpui_key_str(str: &str) -> bool {
261 match str {
262 "backspace" => true,
263 "up" => true,
264 "down" => true,
265 "left" => true,
266 "right" => true,
267 "pageup" => true,
268 "pagedown" => true,
269 "home" => true,
270 "end" => true,
271 "delete" => true,
272 "enter" => true,
273 "escape" => true,
274 "tab" => true,
275 "f1" => true,
276 "f2" => true,
277 "f3" => true,
278 "f4" => true,
279 "f5" => true,
280 "f6" => true,
281 "f7" => true,
282 "f8" => true,
283 "f9" => true,
284 "f10" => true,
285 "f11" => true,
286 "f12" => true,
287 "space" => true,
288 _ => false,
289 }
290}
291
292/// Code Modifiers
293/// ---------+---------------------------
294/// 2 | Shift
295/// 3 | Alt
296/// 4 | Shift + Alt
297/// 5 | Control
298/// 6 | Shift + Control
299/// 7 | Alt + Control
300/// 8 | Shift + Alt + Control
301/// ---------+---------------------------
302/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
303fn modifier_code(keystroke: &Keystroke) -> u32 {
304 let mut modifier_code = 0;
305 if keystroke.shift {
306 modifier_code |= 1;
307 }
308 if keystroke.alt {
309 modifier_code |= 1 << 1;
310 }
311 if keystroke.ctrl {
312 modifier_code |= 1 << 2;
313 }
314 modifier_code + 1
315}
316
317#[cfg(test)]
318mod test {
319 use super::*;
320
321 #[test]
322 fn test_scroll_keys() {
323 //These keys should be handled by the scrolling element directly
324 //Need to signify this by returning 'None'
325 let shift_pageup = Keystroke::parse("shift-pageup").unwrap();
326 let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap();
327 let shift_home = Keystroke::parse("shift-home").unwrap();
328 let shift_end = Keystroke::parse("shift-end").unwrap();
329
330 let none = TermMode::NONE;
331 assert_eq!(to_esc_str(&shift_pageup, &none), None);
332 assert_eq!(to_esc_str(&shift_pagedown, &none), None);
333 assert_eq!(to_esc_str(&shift_home, &none), None);
334 assert_eq!(to_esc_str(&shift_end, &none), None);
335
336 let alt_screen = TermMode::ALT_SCREEN;
337 assert_eq!(
338 to_esc_str(&shift_pageup, &alt_screen),
339 Some("\x1b[5;2~".to_string())
340 );
341 assert_eq!(
342 to_esc_str(&shift_pagedown, &alt_screen),
343 Some("\x1b[6;2~".to_string())
344 );
345 assert_eq!(
346 to_esc_str(&shift_home, &alt_screen),
347 Some("\x1b[1;2H".to_string())
348 );
349 assert_eq!(
350 to_esc_str(&shift_end, &alt_screen),
351 Some("\x1b[1;2F".to_string())
352 );
353
354 let pageup = Keystroke::parse("pageup").unwrap();
355 let pagedown = Keystroke::parse("pagedown").unwrap();
356 let any = TermMode::ANY;
357
358 assert_eq!(to_esc_str(&pageup, &any), Some("\x1b[5~".to_string()));
359 assert_eq!(to_esc_str(&pagedown, &any), Some("\x1b[6~".to_string()));
360 }
361
362 #[test]
363 fn test_multi_char_fallthrough() {
364 let ks = Keystroke {
365 ctrl: false,
366 alt: false,
367 shift: false,
368 cmd: false,
369
370 key: "🖖🏻".to_string(), //2 char string
371 };
372
373 assert_eq!(to_esc_str(&ks, &TermMode::NONE), Some("🖖🏻".to_string()));
374 }
375
376 #[test]
377 fn test_application_mode() {
378 let app_cursor = TermMode::APP_CURSOR;
379 let none = TermMode::NONE;
380
381 let up = Keystroke::parse("up").unwrap();
382 let down = Keystroke::parse("down").unwrap();
383 let left = Keystroke::parse("left").unwrap();
384 let right = Keystroke::parse("right").unwrap();
385
386 assert_eq!(to_esc_str(&up, &none), Some("\x1b[A".to_string()));
387 assert_eq!(to_esc_str(&down, &none), Some("\x1b[B".to_string()));
388 assert_eq!(to_esc_str(&right, &none), Some("\x1b[C".to_string()));
389 assert_eq!(to_esc_str(&left, &none), Some("\x1b[D".to_string()));
390
391 assert_eq!(to_esc_str(&up, &app_cursor), Some("\x1bOA".to_string()));
392 assert_eq!(to_esc_str(&down, &app_cursor), Some("\x1bOB".to_string()));
393 assert_eq!(to_esc_str(&right, &app_cursor), Some("\x1bOC".to_string()));
394 assert_eq!(to_esc_str(&left, &app_cursor), Some("\x1bOD".to_string()));
395 }
396
397 #[test]
398 fn test_ctrl_codes() {
399 let letters_lower = 'a'..='z';
400 let letters_upper = 'A'..='Z';
401 let mode = TermMode::ANY;
402
403 for (lower, upper) in letters_lower.zip(letters_upper) {
404 assert_eq!(
405 to_esc_str(
406 &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(),
407 &mode
408 ),
409 to_esc_str(
410 &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(),
411 &mode
412 ),
413 "On letter: {}/{}",
414 lower,
415 upper
416 )
417 }
418 }
419
420 #[test]
421 fn test_modifier_code_calc() {
422 // Code Modifiers
423 // ---------+---------------------------
424 // 2 | Shift
425 // 3 | Alt
426 // 4 | Shift + Alt
427 // 5 | Control
428 // 6 | Shift + Control
429 // 7 | Alt + Control
430 // 8 | Shift + Alt + Control
431 // ---------+---------------------------
432 // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
433 assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap()));
434 assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap()));
435 assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap()));
436 assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap()));
437 assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap()));
438 assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap()));
439 assert_eq!(
440 8,
441 modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap())
442 );
443 }
444}