feat(tui): allow function keys through f24 in keymaps (#25329)

## Why

Closes #25006.

`tui.keymap` currently rejects `F13` even though Codex's terminal event
layer can report higher function keys. This prevents users from using
common remappings such as Caps Lock to `F13`.

## What Changed

- Define a shared portable upper bound of `F24` for stored TUI
keybindings.
- Accept `f13` through `f24` in config normalization and runtime
parsing.
- Allow `/keymap` capture to persist `F13` through `F24`.
- Update the unsupported-function-key error and add boundary tests for
`F13`, `F24`, and `F25`.

## How to Test

1. Add a binding such as:

   ```toml
   [tui.keymap.global]
   open_transcript = "f13"
   ```

2. Start Codex and press the remapped `F13` key.
3. Confirm Codex loads the config without the previous `F1 through F12`
error and the action runs.
4. Open `/keymap`, capture `F13` for an action, and confirm the saved
binding is `f13`.
5. As a regression check, try to capture `F25` and confirm Codex reports
that only `F1` through `F24` can be stored.

Targeted tests:

- `just test -p codex-config`
- `just test -p codex-tui function_keys`

Full `just test -p codex-tui` completed with 2,752 passing tests, 4
skipped tests, and two unrelated guardian feature-flag failures:

-
`app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
-
`app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
This commit is contained in:
Felipe Coury
2026-05-31 15:42:39 -03:00
committed by GitHub
Unverified
parent cdde711fac
commit 2f0726ad6d
4 changed files with 41 additions and 8 deletions
+12 -2
View File
@@ -23,6 +23,9 @@ use serde::Serialize;
use serde::de::Error as SerdeError;
use std::collections::BTreeMap;
/// Highest function key supported by portable TUI keymap configuration.
pub const MAX_FUNCTION_KEY: u8 = 24;
/// Normalized string representation of a single key event (for example `ctrl-a`).
///
/// The parser accepts a small alias set (for example `escape` -> `esc`,
@@ -552,14 +555,14 @@ fn normalize_key_name(key: &str, original: &str) -> Result<String, String> {
if let Some(number) = alias.strip_prefix('f')
&& let Ok(number) = number.parse::<u8>()
&& (1..=12).contains(&number)
&& (1..=MAX_FUNCTION_KEY).contains(&number)
{
return Ok(alias.to_string());
}
Err(format!(
"unknown key `{key}` in keybinding `{original}`. \
Use a printable character (for example `a`), function keys (`f1`-`f12`), \
Use a printable character (for example `a`), function keys (`f1`-`f{MAX_FUNCTION_KEY}`), \
or one of: enter, tab, backspace, esc, delete, arrows, home/end, page-up/page-down, space, minus.\n\
See the Codex keymap documentation for supported actions and examples."
))
@@ -674,4 +677,11 @@ mod tests {
assert_eq!(keymap, expected_keymap);
}
}
#[test]
fn function_keys_through_f24_are_accepted() {
assert_eq!(normalize_keybinding_spec("F13"), Ok("f13".to_string()));
assert_eq!(normalize_keybinding_spec("f24"), Ok("f24".to_string()));
assert!(normalize_keybinding_spec("f25").is_err());
}
}
+1
View File
@@ -31,6 +31,7 @@ use serde::Serialize;
pub use crate::tui_keymap::KeybindingSpec;
pub use crate::tui_keymap::KeybindingsSpec;
pub use crate::tui_keymap::MAX_FUNCTION_KEY;
pub use crate::tui_keymap::TuiApprovalKeymap;
pub use crate::tui_keymap::TuiChatKeymap;
pub use crate::tui_keymap::TuiComposerKeymap;