Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:20:32.784Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

keys.rs

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