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        ("backspace", AlacModifiers::None) => Some("\x7f".to_string()),
 55        //Interesting escape codes
 56        ("tab", AlacModifiers::Shift) => Some("\x1b[Z".to_string()),
 57        ("backspace", AlacModifiers::Ctrl) => Some("\x08".to_string()),
 58        ("backspace", AlacModifiers::Alt) => Some("\x1b\x7f".to_string()),
 59        ("backspace", AlacModifiers::Shift) => Some("\x7f".to_string()),
 60        ("space", AlacModifiers::Ctrl) => Some("\x00".to_string()),
 61        ("home", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 62            Some("\x1b[1;2H".to_string())
 63        }
 64        ("end", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 65            Some("\x1b[1;2F".to_string())
 66        }
 67        ("pageup", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 68            Some("\x1b[5;2~".to_string())
 69        }
 70        ("pagedown", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 71            Some("\x1b[6;2~".to_string())
 72        }
 73        ("home", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 74            Some("\x1bOH".to_string())
 75        }
 76        ("home", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 77            Some("\x1b[H".to_string())
 78        }
 79        ("end", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 80            Some("\x1bOF".to_string())
 81        }
 82        ("end", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 83            Some("\x1b[F".to_string())
 84        }
 85        ("up", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 86            Some("\x1bOA".to_string())
 87        }
 88        ("up", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 89            Some("\x1b[A".to_string())
 90        }
 91        ("down", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 92            Some("\x1bOB".to_string())
 93        }
 94        ("down", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 95            Some("\x1b[B".to_string())
 96        }
 97        ("right", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 98            Some("\x1bOC".to_string())
 99        }
100        ("right", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
101            Some("\x1b[C".to_string())
102        }
103        ("left", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
104            Some("\x1bOD".to_string())
105        }
106        ("left", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
107            Some("\x1b[D".to_string())
108        }
109        ("back", AlacModifiers::None) => Some("\x7f".to_string()),
110        ("insert", AlacModifiers::None) => Some("\x1b[2~".to_string()),
111        ("delete", AlacModifiers::None) => Some("\x1b[3~".to_string()),
112        ("pageup", AlacModifiers::None) => Some("\x1b[5~".to_string()),
113        ("pagedown", AlacModifiers::None) => Some("\x1b[6~".to_string()),
114        ("f1", AlacModifiers::None) => Some("\x1bOP".to_string()),
115        ("f2", AlacModifiers::None) => Some("\x1bOQ".to_string()),
116        ("f3", AlacModifiers::None) => Some("\x1bOR".to_string()),
117        ("f4", AlacModifiers::None) => Some("\x1bOS".to_string()),
118        ("f5", AlacModifiers::None) => Some("\x1b[15~".to_string()),
119        ("f6", AlacModifiers::None) => Some("\x1b[17~".to_string()),
120        ("f7", AlacModifiers::None) => Some("\x1b[18~".to_string()),
121        ("f8", AlacModifiers::None) => Some("\x1b[19~".to_string()),
122        ("f9", AlacModifiers::None) => Some("\x1b[20~".to_string()),
123        ("f10", AlacModifiers::None) => Some("\x1b[21~".to_string()),
124        ("f11", AlacModifiers::None) => Some("\x1b[23~".to_string()),
125        ("f12", AlacModifiers::None) => Some("\x1b[24~".to_string()),
126        ("f13", AlacModifiers::None) => Some("\x1b[25~".to_string()),
127        ("f14", AlacModifiers::None) => Some("\x1b[26~".to_string()),
128        ("f15", AlacModifiers::None) => Some("\x1b[28~".to_string()),
129        ("f16", AlacModifiers::None) => Some("\x1b[29~".to_string()),
130        ("f17", AlacModifiers::None) => Some("\x1b[31~".to_string()),
131        ("f18", AlacModifiers::None) => Some("\x1b[32~".to_string()),
132        ("f19", AlacModifiers::None) => Some("\x1b[33~".to_string()),
133        ("f20", AlacModifiers::None) => Some("\x1b[34~".to_string()),
134        // NumpadEnter, Action::Esc("\n".into());
135        //Mappings for caret notation keys
136        ("a", AlacModifiers::Ctrl) => Some("\x01".to_string()), //1
137        ("A", AlacModifiers::CtrlShift) => Some("\x01".to_string()), //1
138        ("b", AlacModifiers::Ctrl) => Some("\x02".to_string()), //2
139        ("B", AlacModifiers::CtrlShift) => Some("\x02".to_string()), //2
140        ("c", AlacModifiers::Ctrl) => Some("\x03".to_string()), //3
141        ("C", AlacModifiers::CtrlShift) => Some("\x03".to_string()), //3
142        ("d", AlacModifiers::Ctrl) => Some("\x04".to_string()), //4
143        ("D", AlacModifiers::CtrlShift) => Some("\x04".to_string()), //4
144        ("e", AlacModifiers::Ctrl) => Some("\x05".to_string()), //5
145        ("E", AlacModifiers::CtrlShift) => Some("\x05".to_string()), //5
146        ("f", AlacModifiers::Ctrl) => Some("\x06".to_string()), //6
147        ("F", AlacModifiers::CtrlShift) => Some("\x06".to_string()), //6
148        ("g", AlacModifiers::Ctrl) => Some("\x07".to_string()), //7
149        ("G", AlacModifiers::CtrlShift) => Some("\x07".to_string()), //7
150        ("h", AlacModifiers::Ctrl) => Some("\x08".to_string()), //8
151        ("H", AlacModifiers::CtrlShift) => Some("\x08".to_string()), //8
152        ("i", AlacModifiers::Ctrl) => Some("\x09".to_string()), //9
153        ("I", AlacModifiers::CtrlShift) => Some("\x09".to_string()), //9
154        ("j", AlacModifiers::Ctrl) => Some("\x0a".to_string()), //10
155        ("J", AlacModifiers::CtrlShift) => Some("\x0a".to_string()), //10
156        ("k", AlacModifiers::Ctrl) => Some("\x0b".to_string()), //11
157        ("K", AlacModifiers::CtrlShift) => Some("\x0b".to_string()), //11
158        ("l", AlacModifiers::Ctrl) => Some("\x0c".to_string()), //12
159        ("L", AlacModifiers::CtrlShift) => Some("\x0c".to_string()), //12
160        ("m", AlacModifiers::Ctrl) => Some("\x0d".to_string()), //13
161        ("M", AlacModifiers::CtrlShift) => Some("\x0d".to_string()), //13
162        ("n", AlacModifiers::Ctrl) => Some("\x0e".to_string()), //14
163        ("N", AlacModifiers::CtrlShift) => Some("\x0e".to_string()), //14
164        ("o", AlacModifiers::Ctrl) => Some("\x0f".to_string()), //15
165        ("O", AlacModifiers::CtrlShift) => Some("\x0f".to_string()), //15
166        ("p", AlacModifiers::Ctrl) => Some("\x10".to_string()), //16
167        ("P", AlacModifiers::CtrlShift) => Some("\x10".to_string()), //16
168        ("q", AlacModifiers::Ctrl) => Some("\x11".to_string()), //17
169        ("Q", AlacModifiers::CtrlShift) => Some("\x11".to_string()), //17
170        ("r", AlacModifiers::Ctrl) => Some("\x12".to_string()), //18
171        ("R", AlacModifiers::CtrlShift) => Some("\x12".to_string()), //18
172        ("s", AlacModifiers::Ctrl) => Some("\x13".to_string()), //19
173        ("S", AlacModifiers::CtrlShift) => Some("\x13".to_string()), //19
174        ("t", AlacModifiers::Ctrl) => Some("\x14".to_string()), //20
175        ("T", AlacModifiers::CtrlShift) => Some("\x14".to_string()), //20
176        ("u", AlacModifiers::Ctrl) => Some("\x15".to_string()), //21
177        ("U", AlacModifiers::CtrlShift) => Some("\x15".to_string()), //21
178        ("v", AlacModifiers::Ctrl) => Some("\x16".to_string()), //22
179        ("V", AlacModifiers::CtrlShift) => Some("\x16".to_string()), //22
180        ("w", AlacModifiers::Ctrl) => Some("\x17".to_string()), //23
181        ("W", AlacModifiers::CtrlShift) => Some("\x17".to_string()), //23
182        ("x", AlacModifiers::Ctrl) => Some("\x18".to_string()), //24
183        ("X", AlacModifiers::CtrlShift) => Some("\x18".to_string()), //24
184        ("y", AlacModifiers::Ctrl) => Some("\x19".to_string()), //25
185        ("Y", AlacModifiers::CtrlShift) => Some("\x19".to_string()), //25
186        ("z", AlacModifiers::Ctrl) => Some("\x1a".to_string()), //26
187        ("Z", AlacModifiers::CtrlShift) => Some("\x1a".to_string()), //26
188        ("@", AlacModifiers::Ctrl) => Some("\x00".to_string()), //0
189        ("[", AlacModifiers::Ctrl) => Some("\x1b".to_string()), //27
190        ("\\", AlacModifiers::Ctrl) => Some("\x1c".to_string()), //28
191        ("]", AlacModifiers::Ctrl) => Some("\x1d".to_string()), //29
192        ("^", AlacModifiers::Ctrl) => Some("\x1e".to_string()), //30
193        ("_", AlacModifiers::Ctrl) => Some("\x1f".to_string()), //31
194        ("?", AlacModifiers::Ctrl) => Some("\x7f".to_string()), //127
195        _ => None,
196    };
197    if manual_esc_str.is_some() {
198        return manual_esc_str;
199    }
200
201    // Automated bindings applying modifiers
202    if modifiers.any() {
203        let modifier_code = modifier_code(keystroke);
204        let modified_esc_str = match keystroke.key.as_ref() {
205            "up" => Some(format!("\x1b[1;{}A", modifier_code)),
206            "down" => Some(format!("\x1b[1;{}B", modifier_code)),
207            "right" => Some(format!("\x1b[1;{}C", modifier_code)),
208            "left" => Some(format!("\x1b[1;{}D", modifier_code)),
209            "f1" => Some(format!("\x1b[1;{}P", modifier_code)),
210            "f2" => Some(format!("\x1b[1;{}Q", modifier_code)),
211            "f3" => Some(format!("\x1b[1;{}R", modifier_code)),
212            "f4" => Some(format!("\x1b[1;{}S", modifier_code)),
213            "F5" => Some(format!("\x1b[15;{}~", modifier_code)),
214            "f6" => Some(format!("\x1b[17;{}~", modifier_code)),
215            "f7" => Some(format!("\x1b[18;{}~", modifier_code)),
216            "f8" => Some(format!("\x1b[19;{}~", modifier_code)),
217            "f9" => Some(format!("\x1b[20;{}~", modifier_code)),
218            "f10" => Some(format!("\x1b[21;{}~", modifier_code)),
219            "f11" => Some(format!("\x1b[23;{}~", modifier_code)),
220            "f12" => Some(format!("\x1b[24;{}~", modifier_code)),
221            "f13" => Some(format!("\x1b[25;{}~", modifier_code)),
222            "f14" => Some(format!("\x1b[26;{}~", modifier_code)),
223            "f15" => Some(format!("\x1b[28;{}~", modifier_code)),
224            "f16" => Some(format!("\x1b[29;{}~", modifier_code)),
225            "f17" => Some(format!("\x1b[31;{}~", modifier_code)),
226            "f18" => Some(format!("\x1b[32;{}~", modifier_code)),
227            "f19" => Some(format!("\x1b[33;{}~", modifier_code)),
228            "f20" => Some(format!("\x1b[34;{}~", modifier_code)),
229            _ if modifier_code == 2 => None,
230            "insert" => Some(format!("\x1b[2;{}~", modifier_code)),
231            "pageup" => Some(format!("\x1b[5;{}~", modifier_code)),
232            "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)),
233            "end" => Some(format!("\x1b[1;{}F", modifier_code)),
234            "home" => Some(format!("\x1b[1;{}H", modifier_code)),
235            _ => None,
236        };
237        if modified_esc_str.is_some() {
238            return modified_esc_str;
239        }
240    }
241
242    let alt_meta_binding =
243        if alt_is_meta && modifiers == AlacModifiers::Alt && keystroke.key.is_ascii() {
244            Some(format!("\x1b{}", keystroke.key))
245        } else {
246            None
247        };
248
249    if alt_meta_binding.is_some() {
250        return alt_meta_binding;
251    }
252
253    None
254}
255
256///   Code     Modifiers
257/// ---------+---------------------------
258///    2     | Shift
259///    3     | Alt
260///    4     | Shift + Alt
261///    5     | Control
262///    6     | Shift + Control
263///    7     | Alt + Control
264///    8     | Shift + Alt + Control
265/// ---------+---------------------------
266/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
267fn modifier_code(keystroke: &Keystroke) -> u32 {
268    let mut modifier_code = 0;
269    if keystroke.modifiers.shift {
270        modifier_code |= 1;
271    }
272    if keystroke.modifiers.alt {
273        modifier_code |= 1 << 1;
274    }
275    if keystroke.modifiers.control {
276        modifier_code |= 1 << 2;
277    }
278    modifier_code + 1
279}
280
281#[cfg(test)]
282mod test {
283    use gpui::Modifiers;
284
285    use super::*;
286
287    #[test]
288    fn test_scroll_keys() {
289        //These keys should be handled by the scrolling element directly
290        //Need to signify this by returning 'None'
291        let shift_pageup = Keystroke::parse("shift-pageup").unwrap();
292        let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap();
293        let shift_home = Keystroke::parse("shift-home").unwrap();
294        let shift_end = Keystroke::parse("shift-end").unwrap();
295
296        let none = TermMode::NONE;
297        assert_eq!(to_esc_str(&shift_pageup, &none, false), None);
298        assert_eq!(to_esc_str(&shift_pagedown, &none, false), None);
299        assert_eq!(to_esc_str(&shift_home, &none, false), None);
300        assert_eq!(to_esc_str(&shift_end, &none, false), None);
301
302        let alt_screen = TermMode::ALT_SCREEN;
303        assert_eq!(
304            to_esc_str(&shift_pageup, &alt_screen, false),
305            Some("\x1b[5;2~".to_string())
306        );
307        assert_eq!(
308            to_esc_str(&shift_pagedown, &alt_screen, false),
309            Some("\x1b[6;2~".to_string())
310        );
311        assert_eq!(
312            to_esc_str(&shift_home, &alt_screen, false),
313            Some("\x1b[1;2H".to_string())
314        );
315        assert_eq!(
316            to_esc_str(&shift_end, &alt_screen, false),
317            Some("\x1b[1;2F".to_string())
318        );
319
320        let pageup = Keystroke::parse("pageup").unwrap();
321        let pagedown = Keystroke::parse("pagedown").unwrap();
322        let any = TermMode::ANY;
323
324        assert_eq!(
325            to_esc_str(&pageup, &any, false),
326            Some("\x1b[5~".to_string())
327        );
328        assert_eq!(
329            to_esc_str(&pagedown, &any, false),
330            Some("\x1b[6~".to_string())
331        );
332    }
333
334    #[test]
335    fn test_plain_inputs() {
336        let ks = Keystroke {
337            modifiers: Modifiers {
338                control: false,
339                alt: false,
340                shift: false,
341                platform: false,
342                function: false,
343            },
344            key: "🖖🏻".to_string(), //2 char string
345            ime_key: None,
346        };
347        assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None);
348    }
349
350    #[test]
351    fn test_application_mode() {
352        let app_cursor = TermMode::APP_CURSOR;
353        let none = TermMode::NONE;
354
355        let up = Keystroke::parse("up").unwrap();
356        let down = Keystroke::parse("down").unwrap();
357        let left = Keystroke::parse("left").unwrap();
358        let right = Keystroke::parse("right").unwrap();
359
360        assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".to_string()));
361        assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".to_string()));
362        assert_eq!(to_esc_str(&right, &none, false), Some("\x1b[C".to_string()));
363        assert_eq!(to_esc_str(&left, &none, false), Some("\x1b[D".to_string()));
364
365        assert_eq!(
366            to_esc_str(&up, &app_cursor, false),
367            Some("\x1bOA".to_string())
368        );
369        assert_eq!(
370            to_esc_str(&down, &app_cursor, false),
371            Some("\x1bOB".to_string())
372        );
373        assert_eq!(
374            to_esc_str(&right, &app_cursor, false),
375            Some("\x1bOC".to_string())
376        );
377        assert_eq!(
378            to_esc_str(&left, &app_cursor, false),
379            Some("\x1bOD".to_string())
380        );
381    }
382
383    #[test]
384    fn test_ctrl_codes() {
385        let letters_lower = 'a'..='z';
386        let letters_upper = 'A'..='Z';
387        let mode = TermMode::ANY;
388
389        for (lower, upper) in letters_lower.zip(letters_upper) {
390            assert_eq!(
391                to_esc_str(
392                    &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(),
393                    &mode,
394                    false
395                ),
396                to_esc_str(
397                    &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(),
398                    &mode,
399                    false
400                ),
401                "On letter: {}/{}",
402                lower,
403                upper
404            )
405        }
406    }
407
408    #[test]
409    fn alt_is_meta() {
410        let ascii_printable = ' '..='~';
411        for character in ascii_printable {
412            assert_eq!(
413                to_esc_str(
414                    &Keystroke::parse(&format!("alt-{}", character)).unwrap(),
415                    &TermMode::NONE,
416                    true
417                )
418                .unwrap(),
419                format!("\x1b{}", character)
420            );
421        }
422
423        let gpui_keys = [
424            "up", "down", "right", "left", "f1", "f2", "f3", "f4", "F5", "f6", "f7", "f8", "f9",
425            "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "insert",
426            "pageup", "pagedown", "end", "home",
427        ];
428
429        for key in gpui_keys {
430            assert_ne!(
431                to_esc_str(
432                    &Keystroke::parse(&format!("alt-{}", key)).unwrap(),
433                    &TermMode::NONE,
434                    true
435                )
436                .unwrap(),
437                format!("\x1b{}", key)
438            );
439        }
440    }
441
442    #[test]
443    fn test_modifier_code_calc() {
444        //   Code     Modifiers
445        // ---------+---------------------------
446        //    2     | Shift
447        //    3     | Alt
448        //    4     | Shift + Alt
449        //    5     | Control
450        //    6     | Shift + Control
451        //    7     | Alt + Control
452        //    8     | Shift + Alt + Control
453        // ---------+---------------------------
454        // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
455        assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap()));
456        assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap()));
457        assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap()));
458        assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap()));
459        assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap()));
460        assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap()));
461        assert_eq!(
462            8,
463            modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap())
464        );
465    }
466}