From d93155db61a38ccf5b767a54c5e21e2a6e421a5d Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 19 Aug 2026 22:17:41 +0000 Subject: [PATCH] terminal: Fix support for alt-f5 and add support for ctrl-alt-key (#62891) # Objective - Fix support for alt-f5 in terminal and add support for ctrl-alt-key ## Solution - Fix `crates/terminal/src/mappings/keys.rs`. ## Testing - Did you test these changes? If so, how? Added a unit test, and test the built zed with fish_key_reader to check that the keys are indeed recognized. - Are there any parts that need more testing? No - How can other people (reviewers) test your changes? Is there anything specific they need to know? Fish has fish_key_reader which can check the keys that it recognizes. Not sure about other shells. `cat | xxd` can be used to confirm the exact received key codes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? Tested on Linux only. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - In terminal, "alt-f5" keystroke support is fixed and "ctrl-alt-key" support is added for letter keys. --------- Signed-off-by: Gary Guo Co-authored-by: Kirill Bulatov --- crates/terminal/src/mappings/keys.rs | 50 ++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 5e453e9500..03152a4936 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -182,7 +182,7 @@ pub(crate) fn to_esc_str( "f2" => Some(format!("\x1b[1;{}Q", modifier_code)), "f3" => Some(format!("\x1b[1;{}R", modifier_code)), "f4" => Some(format!("\x1b[1;{}S", modifier_code)), - "F5" => Some(format!("\x1b[15;{}~", modifier_code)), + "f5" => Some(format!("\x1b[15;{}~", modifier_code)), "f6" => Some(format!("\x1b[17;{}~", modifier_code)), "f7" => Some(format!("\x1b[18;{}~", modifier_code)), "f8" => Some(format!("\x1b[19;{}~", modifier_code)), @@ -210,18 +210,23 @@ pub(crate) fn to_esc_str( } } - if !cfg!(target_os = "macos") || option_as_meta { - let is_alt_lowercase_ascii = - modifiers == TerminalModifiers::Alt && keystroke.key.is_ascii(); - let is_alt_uppercase_ascii = - keystroke.modifiers.alt && keystroke.modifiers.shift && keystroke.key.is_ascii(); - if is_alt_lowercase_ascii || is_alt_uppercase_ascii { - let key = if is_alt_uppercase_ascii { - &keystroke.key.to_ascii_uppercase() - } else { - &keystroke.key - }; - return Some(Cow::Owned(format!("\x1b{}", key))); + // Handling alt keys. These work for single ASCII characters. + if (!cfg!(target_os = "macos") || option_as_meta) + && keystroke.modifiers.alt + && keystroke.key.is_ascii() + && keystroke.key.len() == 1 + { + let key = keystroke.key.chars().next().unwrap(); + + if modifiers == TerminalModifiers::Alt { + return Some(Cow::Owned(format!("\x1b{key}"))); + } else if keystroke.modifiers.shift { + return Some(Cow::Owned(format!("\x1b{}", key.to_ascii_uppercase()))); + } else if keystroke.modifiers.control && key.is_ascii_lowercase() { + return Some(Cow::Owned(format!( + "\x1b{}", + (key as u8 - b'a' + 1) as char + ))); } } @@ -362,7 +367,7 @@ mod test { } let gpui_keys = [ - "up", "down", "right", "left", "f1", "f2", "f3", "f4", "F5", "f6", "f7", "f8", "f9", + "up", "down", "right", "left", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "insert", "pageup", "pagedown", "end", "home", ]; @@ -380,6 +385,23 @@ mod test { } } + #[test] + fn test_ctrl_alt_codes() { + let letters_lower = 'a'..='z'; + let esc_codes = '\x01'..='\x1a'; + for (character, esc_code) in letters_lower.zip(esc_codes) { + assert_eq!( + to_esc_str( + &Keystroke::parse(&format!("ctrl-alt-{}", character)).unwrap(), + Modes::NONE, + true + ) + .unwrap(), + format!("\x1b{}", esc_code) + ); + } + } + #[test] fn test_shift_enter_newline() { let shift_enter = Keystroke::parse("shift-enter").unwrap();