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