keys.rs

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