keymappings.rs

  1use alacritty_terminal::term::TermMode;
  2use gpui::keymap::Keystroke;
  3
  4/*
  5Connection events still to do:
  6- Reporting mouse events correctly.
  7- Reporting scrolls
  8- Correctly bracketing a paste
  9- Storing changed colors
 10- Focus change sequence
 11*/
 12
 13#[derive(Debug)]
 14pub enum Modifiers {
 15    None,
 16    Alt,
 17    Ctrl,
 18    Shift,
 19    CtrlShift,
 20    Other,
 21}
 22
 23impl Modifiers {
 24    fn new(ks: &Keystroke) -> Self {
 25        match (ks.alt, ks.ctrl, ks.shift, ks.cmd) {
 26            (false, false, false, false) => Modifiers::None,
 27            (true, false, false, false) => Modifiers::Alt,
 28            (false, true, false, false) => Modifiers::Ctrl,
 29            (false, false, true, false) => Modifiers::Shift,
 30            (false, true, true, false) => Modifiers::CtrlShift,
 31            _ => Modifiers::Other,
 32        }
 33    }
 34
 35    fn any(&self) -> bool {
 36        match &self {
 37            Modifiers::None => false,
 38            Modifiers::Alt => true,
 39            Modifiers::Ctrl => true,
 40            Modifiers::Shift => true,
 41            Modifiers::CtrlShift => true,
 42            Modifiers::Other => true,
 43        }
 44    }
 45}
 46
 47pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option<String> {
 48    let modifiers = Modifiers::new(&keystroke);
 49
 50    // Manual Bindings including modifiers
 51    let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) {
 52        //Basic special keys
 53        ("space", Modifiers::None) => Some(" ".to_string()),
 54        ("tab", Modifiers::None) => Some("\x09".to_string()),
 55        ("escape", Modifiers::None) => Some("\x1b".to_string()),
 56        ("enter", Modifiers::None) => Some("\x0d".to_string()),
 57        ("backspace", Modifiers::None) => Some("\x7f".to_string()),
 58        //Interesting escape codes
 59        ("tab", Modifiers::Shift) => Some("\x1b[Z".to_string()),
 60        ("backspace", Modifiers::Alt) => Some("\x1b\x7f".to_string()),
 61        ("backspace", Modifiers::Shift) => Some("\x7f".to_string()),
 62        ("home", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 63            Some("\x1b[1;2H".to_string())
 64        }
 65        ("end", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 66            Some("\x1b[1;2F".to_string())
 67        }
 68        ("pageup", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 69            Some("\x1b[5;2~".to_string())
 70        }
 71        ("pagedown", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
 72            Some("\x1b[6;2~".to_string())
 73        }
 74        ("home", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 75            Some("\x1bOH".to_string())
 76        }
 77        ("home", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 78            Some("\x1b[H".to_string())
 79        }
 80        ("end", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 81            Some("\x1bOF".to_string())
 82        }
 83        ("end", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 84            Some("\x1b[F".to_string())
 85        }
 86        ("up", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 87            Some("\x1bOA".to_string())
 88        }
 89        ("up", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 90            Some("\x1b[A".to_string())
 91        }
 92        ("down", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 93            Some("\x1bOB".to_string())
 94        }
 95        ("down", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
 96            Some("\x1b[B".to_string())
 97        }
 98        ("right", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
 99            Some("\x1bOC".to_string())
100        }
101        ("right", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
102            Some("\x1b[C".to_string())
103        }
104        ("left", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
105            Some("\x1bOD".to_string())
106        }
107        ("left", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
108            Some("\x1b[D".to_string())
109        }
110        ("back", Modifiers::None) => Some("\x7f".to_string()),
111        ("insert", Modifiers::None) => Some("\x1b[2~".to_string()),
112        ("delete", Modifiers::None) => Some("\x1b[3~".to_string()),
113        ("pageup", Modifiers::None) => Some("\x1b[5~".to_string()),
114        ("pagedown", Modifiers::None) => Some("\x1b[6~".to_string()),
115        ("f1", Modifiers::None) => Some("\x1bOP".to_string()),
116        ("f2", Modifiers::None) => Some("\x1bOQ".to_string()),
117        ("f3", Modifiers::None) => Some("\x1bOR".to_string()),
118        ("f4", Modifiers::None) => Some("\x1bOS".to_string()),
119        ("f5", Modifiers::None) => Some("\x1b[15~".to_string()),
120        ("f6", Modifiers::None) => Some("\x1b[17~".to_string()),
121        ("f7", Modifiers::None) => Some("\x1b[18~".to_string()),
122        ("f8", Modifiers::None) => Some("\x1b[19~".to_string()),
123        ("f9", Modifiers::None) => Some("\x1b[20~".to_string()),
124        ("f10", Modifiers::None) => Some("\x1b[21~".to_string()),
125        ("f11", Modifiers::None) => Some("\x1b[23~".to_string()),
126        ("f12", Modifiers::None) => Some("\x1b[24~".to_string()),
127        ("f13", Modifiers::None) => Some("\x1b[25~".to_string()),
128        ("f14", Modifiers::None) => Some("\x1b[26~".to_string()),
129        ("f15", Modifiers::None) => Some("\x1b[28~".to_string()),
130        ("f16", Modifiers::None) => Some("\x1b[29~".to_string()),
131        ("f17", Modifiers::None) => Some("\x1b[31~".to_string()),
132        ("f18", Modifiers::None) => Some("\x1b[32~".to_string()),
133        ("f19", Modifiers::None) => Some("\x1b[33~".to_string()),
134        ("f20", Modifiers::None) => Some("\x1b[34~".to_string()),
135        // NumpadEnter, Action::Esc("\n".into());
136        //Mappings for caret notation keys
137        ("a", Modifiers::Ctrl) => Some("\x01".to_string()), //1
138        ("A", Modifiers::CtrlShift) => Some("\x01".to_string()), //1
139        ("b", Modifiers::Ctrl) => Some("\x02".to_string()), //2
140        ("B", Modifiers::CtrlShift) => Some("\x02".to_string()), //2
141        ("c", Modifiers::Ctrl) => Some("\x03".to_string()), //3
142        ("C", Modifiers::CtrlShift) => Some("\x03".to_string()), //3
143        ("d", Modifiers::Ctrl) => Some("\x04".to_string()), //4
144        ("D", Modifiers::CtrlShift) => Some("\x04".to_string()), //4
145        ("e", Modifiers::Ctrl) => Some("\x05".to_string()), //5
146        ("E", Modifiers::CtrlShift) => Some("\x05".to_string()), //5
147        ("f", Modifiers::Ctrl) => Some("\x06".to_string()), //6
148        ("F", Modifiers::CtrlShift) => Some("\x06".to_string()), //6
149        ("g", Modifiers::Ctrl) => Some("\x07".to_string()), //7
150        ("G", Modifiers::CtrlShift) => Some("\x07".to_string()), //7
151        ("h", Modifiers::Ctrl) => Some("\x08".to_string()), //8
152        ("H", Modifiers::CtrlShift) => Some("\x08".to_string()), //8
153        ("i", Modifiers::Ctrl) => Some("\x09".to_string()), //9
154        ("I", Modifiers::CtrlShift) => Some("\x09".to_string()), //9
155        ("j", Modifiers::Ctrl) => Some("\x0a".to_string()), //10
156        ("J", Modifiers::CtrlShift) => Some("\x0a".to_string()), //10
157        ("k", Modifiers::Ctrl) => Some("\x0b".to_string()), //11
158        ("K", Modifiers::CtrlShift) => Some("\x0b".to_string()), //11
159        ("l", Modifiers::Ctrl) => Some("\x0c".to_string()), //12
160        ("L", Modifiers::CtrlShift) => Some("\x0c".to_string()), //12
161        ("m", Modifiers::Ctrl) => Some("\x0d".to_string()), //13
162        ("M", Modifiers::CtrlShift) => Some("\x0d".to_string()), //13
163        ("n", Modifiers::Ctrl) => Some("\x0e".to_string()), //14
164        ("N", Modifiers::CtrlShift) => Some("\x0e".to_string()), //14
165        ("o", Modifiers::Ctrl) => Some("\x0f".to_string()), //15
166        ("O", Modifiers::CtrlShift) => Some("\x0f".to_string()), //15
167        ("p", Modifiers::Ctrl) => Some("\x10".to_string()), //16
168        ("P", Modifiers::CtrlShift) => Some("\x10".to_string()), //16
169        ("q", Modifiers::Ctrl) => Some("\x11".to_string()), //17
170        ("Q", Modifiers::CtrlShift) => Some("\x11".to_string()), //17
171        ("r", Modifiers::Ctrl) => Some("\x12".to_string()), //18
172        ("R", Modifiers::CtrlShift) => Some("\x12".to_string()), //18
173        ("s", Modifiers::Ctrl) => Some("\x13".to_string()), //19
174        ("S", Modifiers::CtrlShift) => Some("\x13".to_string()), //19
175        ("t", Modifiers::Ctrl) => Some("\x14".to_string()), //20
176        ("T", Modifiers::CtrlShift) => Some("\x14".to_string()), //20
177        ("u", Modifiers::Ctrl) => Some("\x15".to_string()), //21
178        ("U", Modifiers::CtrlShift) => Some("\x15".to_string()), //21
179        ("v", Modifiers::Ctrl) => Some("\x16".to_string()), //22
180        ("V", Modifiers::CtrlShift) => Some("\x16".to_string()), //22
181        ("w", Modifiers::Ctrl) => Some("\x17".to_string()), //23
182        ("W", Modifiers::CtrlShift) => Some("\x17".to_string()), //23
183        ("x", Modifiers::Ctrl) => Some("\x18".to_string()), //24
184        ("X", Modifiers::CtrlShift) => Some("\x18".to_string()), //24
185        ("y", Modifiers::Ctrl) => Some("\x19".to_string()), //25
186        ("Y", Modifiers::CtrlShift) => Some("\x19".to_string()), //25
187        ("z", Modifiers::Ctrl) => Some("\x1a".to_string()), //26
188        ("Z", Modifiers::CtrlShift) => Some("\x1a".to_string()), //26
189        ("@", Modifiers::Ctrl) => Some("\x00".to_string()), //0
190        ("[", Modifiers::Ctrl) => Some("\x1b".to_string()), //27
191        ("\\", Modifiers::Ctrl) => Some("\x1c".to_string()), //28
192        ("]", Modifiers::Ctrl) => Some("\x1d".to_string()), //29
193        ("^", Modifiers::Ctrl) => Some("\x1e".to_string()), //30
194        ("_", Modifiers::Ctrl) => Some("\x1f".to_string()), //31
195        ("?", Modifiers::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    //Fallback to sending the keystroke input directly
244    //Skin colors in utf8 are implemented as a seperate, invisible character
245    //that modifies the associated emoji. Some languages may have similarly
246    //implemented modifiers, e.g. certain diacritics that can be typed as a single character.
247    //This means that we need to assume some user input can result in multi-byte,
248    //multi-char strings. This is somewhat difficult, as GPUI normalizes all
249    //keys into a string representation. Hence, the check here to filter out GPUI
250    //keys that weren't captured above.
251    if !matches_gpui_key_str(&keystroke.key) {
252        return Some(keystroke.key.clone());
253    } else {
254        None
255    }
256}
257
258///Checks if the given string matches a GPUI key string.
259///Table made from reading the source at gpui/src/platform/mac/event.rs
260fn matches_gpui_key_str(str: &str) -> bool {
261    match str {
262        "backspace" => true,
263        "up" => true,
264        "down" => true,
265        "left" => true,
266        "right" => true,
267        "pageup" => true,
268        "pagedown" => true,
269        "home" => true,
270        "end" => true,
271        "delete" => true,
272        "enter" => true,
273        "escape" => true,
274        "tab" => true,
275        "f1" => true,
276        "f2" => true,
277        "f3" => true,
278        "f4" => true,
279        "f5" => true,
280        "f6" => true,
281        "f7" => true,
282        "f8" => true,
283        "f9" => true,
284        "f10" => true,
285        "f11" => true,
286        "f12" => true,
287        "space" => true,
288        _ => false,
289    }
290}
291
292///   Code     Modifiers
293/// ---------+---------------------------
294///    2     | Shift
295///    3     | Alt
296///    4     | Shift + Alt
297///    5     | Control
298///    6     | Shift + Control
299///    7     | Alt + Control
300///    8     | Shift + Alt + Control
301/// ---------+---------------------------
302/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
303fn modifier_code(keystroke: &Keystroke) -> u32 {
304    let mut modifier_code = 0;
305    if keystroke.shift {
306        modifier_code |= 1;
307    }
308    if keystroke.alt {
309        modifier_code |= 1 << 1;
310    }
311    if keystroke.ctrl {
312        modifier_code |= 1 << 2;
313    }
314    modifier_code + 1
315}
316
317#[cfg(test)]
318mod test {
319    use super::*;
320
321    #[test]
322    fn test_scroll_keys() {
323        //These keys should be handled by the scrolling element directly
324        //Need to signify this by returning 'None'
325        let shift_pageup = Keystroke::parse("shift-pageup").unwrap();
326        let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap();
327        let shift_home = Keystroke::parse("shift-home").unwrap();
328        let shift_end = Keystroke::parse("shift-end").unwrap();
329
330        let none = TermMode::NONE;
331        assert_eq!(to_esc_str(&shift_pageup, &none), None);
332        assert_eq!(to_esc_str(&shift_pagedown, &none), None);
333        assert_eq!(to_esc_str(&shift_home, &none), None);
334        assert_eq!(to_esc_str(&shift_end, &none), None);
335
336        let alt_screen = TermMode::ALT_SCREEN;
337        assert_eq!(
338            to_esc_str(&shift_pageup, &alt_screen),
339            Some("\x1b[5;2~".to_string())
340        );
341        assert_eq!(
342            to_esc_str(&shift_pagedown, &alt_screen),
343            Some("\x1b[6;2~".to_string())
344        );
345        assert_eq!(
346            to_esc_str(&shift_home, &alt_screen),
347            Some("\x1b[1;2H".to_string())
348        );
349        assert_eq!(
350            to_esc_str(&shift_end, &alt_screen),
351            Some("\x1b[1;2F".to_string())
352        );
353
354        let pageup = Keystroke::parse("pageup").unwrap();
355        let pagedown = Keystroke::parse("pagedown").unwrap();
356        let any = TermMode::ANY;
357
358        assert_eq!(to_esc_str(&pageup, &any), Some("\x1b[5~".to_string()));
359        assert_eq!(to_esc_str(&pagedown, &any), Some("\x1b[6~".to_string()));
360    }
361
362    #[test]
363    fn test_multi_char_fallthrough() {
364        let ks = Keystroke {
365            ctrl: false,
366            alt: false,
367            shift: false,
368            cmd: false,
369
370            key: "🖖🏻".to_string(), //2 char string
371        };
372
373        assert_eq!(to_esc_str(&ks, &TermMode::NONE), Some("🖖🏻".to_string()));
374    }
375
376    #[test]
377    fn test_application_mode() {
378        let app_cursor = TermMode::APP_CURSOR;
379        let none = TermMode::NONE;
380
381        let up = Keystroke::parse("up").unwrap();
382        let down = Keystroke::parse("down").unwrap();
383        let left = Keystroke::parse("left").unwrap();
384        let right = Keystroke::parse("right").unwrap();
385
386        assert_eq!(to_esc_str(&up, &none), Some("\x1b[A".to_string()));
387        assert_eq!(to_esc_str(&down, &none), Some("\x1b[B".to_string()));
388        assert_eq!(to_esc_str(&right, &none), Some("\x1b[C".to_string()));
389        assert_eq!(to_esc_str(&left, &none), Some("\x1b[D".to_string()));
390
391        assert_eq!(to_esc_str(&up, &app_cursor), Some("\x1bOA".to_string()));
392        assert_eq!(to_esc_str(&down, &app_cursor), Some("\x1bOB".to_string()));
393        assert_eq!(to_esc_str(&right, &app_cursor), Some("\x1bOC".to_string()));
394        assert_eq!(to_esc_str(&left, &app_cursor), Some("\x1bOD".to_string()));
395    }
396
397    #[test]
398    fn test_ctrl_codes() {
399        let letters_lower = 'a'..='z';
400        let letters_upper = 'A'..='Z';
401        let mode = TermMode::ANY;
402
403        for (lower, upper) in letters_lower.zip(letters_upper) {
404            assert_eq!(
405                to_esc_str(
406                    &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(),
407                    &mode
408                ),
409                to_esc_str(
410                    &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(),
411                    &mode
412                ),
413                "On letter: {}/{}",
414                lower,
415                upper
416            )
417        }
418    }
419
420    #[test]
421    fn test_modifier_code_calc() {
422        //   Code     Modifiers
423        // ---------+---------------------------
424        //    2     | Shift
425        //    3     | Alt
426        //    4     | Shift + Alt
427        //    5     | Control
428        //    6     | Shift + Control
429        //    7     | Alt + Control
430        //    8     | Shift + Alt + Control
431        // ---------+---------------------------
432        // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
433        assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap()));
434        assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap()));
435        assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap()));
436        assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap()));
437        assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap()));
438        assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap()));
439        assert_eq!(
440            8,
441            modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap())
442        );
443    }
444}