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