keys.rs

  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}