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