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 <gary@garyguo.net>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
Gary GuoandKirill Bulatov authored and GitHub committed 2026-08-19 22:17:41 +00:00
1 parent 611301feb3
commit d93155db61
1 file changed
+36 -14
+36 -14
View File
@@ -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();