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 if alt_is_meta {
244 let is_alt_lowercase_ascii = modifiers == AlacModifiers::Alt && keystroke.key.is_ascii();
245 let is_alt_uppercase_ascii =
246 keystroke.modifiers.alt && keystroke.modifiers.shift && keystroke.key.is_ascii();
247 if is_alt_lowercase_ascii || is_alt_uppercase_ascii {
248 let key = if is_alt_uppercase_ascii {
249 &keystroke.key.to_ascii_uppercase()
250 } else {
251 &keystroke.key
252 };
253 return Some(format!("\x1b{}", key));
254 }
255 }
256
257 None
258}
259
260/// Code Modifiers
261/// ---------+---------------------------
262/// 2 | Shift
263/// 3 | Alt
264/// 4 | Shift + Alt
265/// 5 | Control
266/// 6 | Shift + Control
267/// 7 | Alt + Control
268/// 8 | Shift + Alt + Control
269/// ---------+---------------------------
270/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
271fn modifier_code(keystroke: &Keystroke) -> u32 {
272 let mut modifier_code = 0;
273 if keystroke.modifiers.shift {
274 modifier_code |= 1;
275 }
276 if keystroke.modifiers.alt {
277 modifier_code |= 1 << 1;
278 }
279 if keystroke.modifiers.control {
280 modifier_code |= 1 << 2;
281 }
282 modifier_code + 1
283}
284
285#[cfg(test)]
286mod test {
287 use gpui::Modifiers;
288
289 use super::*;
290
291 #[test]
292 fn test_scroll_keys() {
293 //These keys should be handled by the scrolling element directly
294 //Need to signify this by returning 'None'
295 let shift_pageup = Keystroke::parse("shift-pageup").unwrap();
296 let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap();
297 let shift_home = Keystroke::parse("shift-home").unwrap();
298 let shift_end = Keystroke::parse("shift-end").unwrap();
299
300 let none = TermMode::NONE;
301 assert_eq!(to_esc_str(&shift_pageup, &none, false), None);
302 assert_eq!(to_esc_str(&shift_pagedown, &none, false), None);
303 assert_eq!(to_esc_str(&shift_home, &none, false), None);
304 assert_eq!(to_esc_str(&shift_end, &none, false), None);
305
306 let alt_screen = TermMode::ALT_SCREEN;
307 assert_eq!(
308 to_esc_str(&shift_pageup, &alt_screen, false),
309 Some("\x1b[5;2~".to_string())
310 );
311 assert_eq!(
312 to_esc_str(&shift_pagedown, &alt_screen, false),
313 Some("\x1b[6;2~".to_string())
314 );
315 assert_eq!(
316 to_esc_str(&shift_home, &alt_screen, false),
317 Some("\x1b[1;2H".to_string())
318 );
319 assert_eq!(
320 to_esc_str(&shift_end, &alt_screen, false),
321 Some("\x1b[1;2F".to_string())
322 );
323
324 let pageup = Keystroke::parse("pageup").unwrap();
325 let pagedown = Keystroke::parse("pagedown").unwrap();
326 let any = TermMode::ANY;
327
328 assert_eq!(
329 to_esc_str(&pageup, &any, false),
330 Some("\x1b[5~".to_string())
331 );
332 assert_eq!(
333 to_esc_str(&pagedown, &any, false),
334 Some("\x1b[6~".to_string())
335 );
336 }
337
338 #[test]
339 fn test_plain_inputs() {
340 let ks = Keystroke {
341 modifiers: Modifiers {
342 control: false,
343 alt: false,
344 shift: false,
345 platform: false,
346 function: false,
347 },
348 key: "🖖🏻".to_string(), //2 char string
349 key_char: None,
350 };
351 assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None);
352 }
353
354 #[test]
355 fn test_application_mode() {
356 let app_cursor = TermMode::APP_CURSOR;
357 let none = TermMode::NONE;
358
359 let up = Keystroke::parse("up").unwrap();
360 let down = Keystroke::parse("down").unwrap();
361 let left = Keystroke::parse("left").unwrap();
362 let right = Keystroke::parse("right").unwrap();
363
364 assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".to_string()));
365 assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".to_string()));
366 assert_eq!(to_esc_str(&right, &none, false), Some("\x1b[C".to_string()));
367 assert_eq!(to_esc_str(&left, &none, false), Some("\x1b[D".to_string()));
368
369 assert_eq!(
370 to_esc_str(&up, &app_cursor, false),
371 Some("\x1bOA".to_string())
372 );
373 assert_eq!(
374 to_esc_str(&down, &app_cursor, false),
375 Some("\x1bOB".to_string())
376 );
377 assert_eq!(
378 to_esc_str(&right, &app_cursor, false),
379 Some("\x1bOC".to_string())
380 );
381 assert_eq!(
382 to_esc_str(&left, &app_cursor, false),
383 Some("\x1bOD".to_string())
384 );
385 }
386
387 #[test]
388 fn test_ctrl_codes() {
389 let letters_lower = 'a'..='z';
390 let letters_upper = 'A'..='Z';
391 let mode = TermMode::ANY;
392
393 for (lower, upper) in letters_lower.zip(letters_upper) {
394 assert_eq!(
395 to_esc_str(
396 &Keystroke::parse(&format!("ctrl-shift-{}", lower)).unwrap(),
397 &mode,
398 false
399 ),
400 to_esc_str(
401 &Keystroke::parse(&format!("ctrl-{}", upper)).unwrap(),
402 &mode,
403 false
404 ),
405 "On letter: {}/{}",
406 lower,
407 upper
408 )
409 }
410 }
411
412 #[test]
413 fn alt_is_meta() {
414 let ascii_printable = ' '..='~';
415 for character in ascii_printable {
416 assert_eq!(
417 to_esc_str(
418 &Keystroke::parse(&format!("alt-{}", character)).unwrap(),
419 &TermMode::NONE,
420 true
421 )
422 .unwrap(),
423 format!("\x1b{}", character)
424 );
425 }
426
427 let gpui_keys = [
428 "up", "down", "right", "left", "f1", "f2", "f3", "f4", "F5", "f6", "f7", "f8", "f9",
429 "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "insert",
430 "pageup", "pagedown", "end", "home",
431 ];
432
433 for key in gpui_keys {
434 assert_ne!(
435 to_esc_str(
436 &Keystroke::parse(&format!("alt-{}", key)).unwrap(),
437 &TermMode::NONE,
438 true
439 )
440 .unwrap(),
441 format!("\x1b{}", key)
442 );
443 }
444 }
445
446 #[test]
447 fn test_modifier_code_calc() {
448 // Code Modifiers
449 // ---------+---------------------------
450 // 2 | Shift
451 // 3 | Alt
452 // 4 | Shift + Alt
453 // 5 | Control
454 // 6 | Shift + Control
455 // 7 | Alt + Control
456 // 8 | Shift + Alt + Control
457 // ---------+---------------------------
458 // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
459 assert_eq!(2, modifier_code(&Keystroke::parse("shift-a").unwrap()));
460 assert_eq!(3, modifier_code(&Keystroke::parse("alt-a").unwrap()));
461 assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-a").unwrap()));
462 assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-a").unwrap()));
463 assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-a").unwrap()));
464 assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-a").unwrap()));
465 assert_eq!(
466 8,
467 modifier_code(&Keystroke::parse("shift-ctrl-alt-a").unwrap())
468 );
469 }
470}