Skip to repository content421 lines · 18.5 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:20:32.784Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
keys.rs
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(®ular_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