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}