keys.rs

  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}