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