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