keys.rs

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