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 ime_key: None,
337 };
338 assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None);
339 }
340
341 #[test]
342 fn test_application_mode() {
343 let app_cursor = TermMode::APP_CURSOR;
344 let none = TermMode::NONE;
345
346 let up = Keystroke::parse("up").unwrap();
347 let down = Keystroke::parse("down").unwrap();
348 let left = Keystroke::parse("left").unwrap();
349 let right = Keystroke::parse("right").unwrap();
350
351 assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".to_string()));
352 assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".to_string()));
353 assert_eq!(to_esc_str(&right, &none, false), Some("\x1b[C".to_string()));
354 assert_eq!(to_esc_str(&left, &none, false), Some("\x1b[D".to_string()));
355
356 assert_eq!(
357 to_esc_str(&up, &app_cursor, false),
358 Some("\x1bOA".to_string())
359 );
360 assert_eq!(
361 to_esc_str(&down, &app_cursor, false),
362 Some("\x1bOB".to_string())
363 );
364 assert_eq!(
365 to_esc_str(&right, &app_cursor, false),
366 Some("\x1bOC".to_string())
367 );
368 assert_eq!(
369 to_esc_str(&left, &app_cursor, false),
370 Some("\x1bOD".to_string())
371 );
372 }
373
374 #[test]
375 fn test_ctrl_codes() {
376 let letters_lower = 'a'..='z';
377 let letters_upper = 'A'..='Z';
378 let mode = TermMode::ANY;
379
380 for (lower, upper) in letters_lower.zip(letters_upper) {
381 assert_eq!(
382 to_esc_str(
383 &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(),
384 &mode,
385 false
386 ),
387 to_esc_str(
388 &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(),
389 &mode,
390 false
391 ),
392 "On letter: {}/{}",
393 lower,
394 upper
395 )
396 }
397 }
398
399 #[test]
400 fn alt_is_meta() {
401 let ascii_printable = ' '..='~';
402 for character in ascii_printable {
403 assert_eq!(
404 to_esc_str(
405 &Keystroke::parse(&format!("alt-{}", character)).unwrap(),
406 &TermMode::NONE,
407 true
408 )
409 .unwrap(),
410 format!("\x1b{}", character)
411 );
412 }
413
414 let gpui_keys = [
415 "up", "down", "right", "left", "f1", "f2", "f3", "f4", "F5", "f6", "f7", "f8", "f9",
416 "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "insert",
417 "pageup", "pagedown", "end", "home",
418 ];
419
420 for key in gpui_keys {
421 assert_ne!(
422 to_esc_str(
423 &Keystroke::parse(&format!("alt-{}", key)).unwrap(),
424 &TermMode::NONE,
425 true
426 )
427 .unwrap(),
428 format!("\x1b{}", key)
429 );
430 }
431 }
432
433 #[test]
434 fn test_modifier_code_calc() {
435 // Code Modifiers
436 // ---------+---------------------------
437 // 2 | Shift
438 // 3 | Alt
439 // 4 | Shift + Alt
440 // 5 | Control
441 // 6 | Shift + Control
442 // 7 | Alt + Control
443 // 8 | Shift + Alt + Control
444 // ---------+---------------------------
445 // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
446 assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap()));
447 assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap()));
448 assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap()));
449 assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap()));
450 assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap()));
451 assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap()));
452 assert_eq!(
453 8,
454 modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap())
455 );
456 }
457}