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