keys.rs

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