diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 446563a19..f64a824f4 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -1,3 +1,4 @@ +use crate::key_hint::has_ctrl_or_alt; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyEventKind; @@ -1103,8 +1104,7 @@ impl ChatComposer { .. } = input { - let has_ctrl_or_alt = - modifiers.contains(KeyModifiers::CONTROL) || modifiers.contains(KeyModifiers::ALT); + let has_ctrl_or_alt = has_ctrl_or_alt(modifiers); if !has_ctrl_or_alt { // Non-ASCII characters (e.g., from IMEs) can arrive in quick bursts and be // misclassified by paste heuristics. Flush any active burst buffer and insert @@ -1174,8 +1174,7 @@ impl ChatComposer { } = input; match code { KeyCode::Char(_) => { - let has_ctrl_or_alt = modifiers.contains(KeyModifiers::CONTROL) - || modifiers.contains(KeyModifiers::ALT); + let has_ctrl_or_alt = has_ctrl_or_alt(modifiers); if has_ctrl_or_alt { self.paste_burst.clear_window_after_non_char(); } diff --git a/codex-rs/tui/src/bottom_pane/textarea.rs b/codex-rs/tui/src/bottom_pane/textarea.rs index 0a7faa4fc..03de33681 100644 --- a/codex-rs/tui/src/bottom_pane/textarea.rs +++ b/codex-rs/tui/src/bottom_pane/textarea.rs @@ -1,3 +1,4 @@ +use crate::key_hint::is_altgr; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyModifiers; @@ -247,16 +248,13 @@ impl TextArea { } if modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT) => { self.delete_backward_word() }, + // Windows AltGr generates ALT|CONTROL; treat as a plain character input unless + // we match a specific Control+Alt binding above. KeyEvent { code: KeyCode::Char(c), modifiers, .. - } if modifiers.contains(KeyModifiers::ALT) - && modifiers.contains(KeyModifiers::CONTROL) => - { - // AltGr on many keyboards reports as Ctrl+Alt; treat it as a literal char. - self.insert_str(&c.to_string()); - }, + } if is_altgr(modifiers) => self.insert_str(&c.to_string()), KeyEvent { code: KeyCode::Backspace, modifiers: KeyModifiers::ALT, @@ -1464,6 +1462,7 @@ mod tests { assert_eq!(t.cursor(), 3); } + #[cfg_attr(not(windows), ignore = "AltGr modifier only applies on Windows")] #[test] fn altgr_ctrl_alt_char_inserts_literal() { let mut t = ta_with(""); diff --git a/codex-rs/tui/src/key_hint.rs b/codex-rs/tui/src/key_hint.rs index 1126b8023..6272ab0df 100644 --- a/codex-rs/tui/src/key_hint.rs +++ b/codex-rs/tui/src/key_hint.rs @@ -89,3 +89,19 @@ impl From<&KeyBinding> for Span<'static> { fn key_hint_style() -> Style { Style::default().dim() } + +pub(crate) fn has_ctrl_or_alt(mods: KeyModifiers) -> bool { + (mods.contains(KeyModifiers::CONTROL) || mods.contains(KeyModifiers::ALT)) && !is_altgr(mods) +} + +#[cfg(windows)] +#[inline] +pub(crate) fn is_altgr(mods: KeyModifiers) -> bool { + mods.contains(KeyModifiers::ALT) && mods.contains(KeyModifiers::CONTROL) +} + +#[cfg(not(windows))] +#[inline] +pub(crate) fn is_altgr(_mods: KeyModifiers) -> bool { + false +}