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