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        };
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}