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