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