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