keys.rs

  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}