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