diff --git a/codex-rs/tui/src/tui/keyboard_modes.rs b/codex-rs/tui/src/tui/keyboard_modes.rs index 0b907caf30f6..4e0702bcbdcc 100644 --- a/codex-rs/tui/src/tui/keyboard_modes.rs +++ b/codex-rs/tui/src/tui/keyboard_modes.rs @@ -125,16 +125,81 @@ pub(super) fn enable_keyboard_enhancement() { let _ = execute!( stdout(), + DisableModifyOtherKeys, PushKeyboardEnhancementFlags( KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES | KeyboardEnhancementFlags::REPORT_EVENT_TYPES | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS ) ); + + if tmux_should_enable_modify_other_keys() { + let _ = execute!(stdout(), EnableModifyOtherKeys); + } +} + +fn running_in_tmux_session() -> bool { + tmux_session_detected( + std::env::var("TMUX").ok().as_deref(), + std::env::var("TMUX_PANE").ok().as_deref(), + ) +} + +fn tmux_session_detected(tmux: Option<&str>, tmux_pane: Option<&str>) -> bool { + tmux.is_some() || tmux_pane.is_some() +} + +fn tmux_should_enable_modify_other_keys() -> bool { + tmux_should_enable_modify_other_keys_for( + running_in_tmux_session(), + read_tmux_extended_keys_format().as_deref(), + ) +} + +fn tmux_should_enable_modify_other_keys_for( + running_in_tmux_session: bool, + extended_keys_format: Option<&str>, +) -> bool { + // If tmux cannot be queried, still request mode 2; otherwise csi-u panes + // can stay in VT10x. Explicit xterm format is avoided because crossterm + // does not parse tmux's xterm-style extended-key sequences as Enter. + running_in_tmux_session && matches!(extended_keys_format, Some("csi-u") | None) +} + +fn read_tmux_extended_keys_format() -> Option { + for args in [ + ["display-message", "-p", "#{extended-keys-format}"], + ["show-options", "-gqv", "extended-keys-format"], + ] { + let output = std::process::Command::new("tmux") + .args(args) + .stdin(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .output() + .ok()?; + + if !output.status.success() { + continue; + } + + if let Some(value) = String::from_utf8(output.stdout) + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + { + return Some(value); + } + } + + None } pub(super) fn restore_keyboard_enhancement_stack() { - let _ = execute!(stdout(), PopKeyboardEnhancementFlags); + let _ = execute!( + stdout(), + PopKeyboardEnhancementFlags, + DisableModifyOtherKeys + ); } pub(super) fn reset_keyboard_reporting_after_exit() { @@ -168,6 +233,28 @@ impl Command for ResetKeyboardEnhancementFlags { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct EnableModifyOtherKeys; + +impl Command for EnableModifyOtherKeys { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str("\x1b[>4;2m") + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "modifyOtherKeys enable is not implemented for the legacy Windows API", + )) + } + + #[cfg(windows)] + fn is_ansi_code_supported(&self) -> bool { + false + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct DisableModifyOtherKeys; @@ -193,9 +280,12 @@ impl Command for DisableModifyOtherKeys { #[cfg(test)] mod tests { use super::DisableModifyOtherKeys; + use super::EnableModifyOtherKeys; use super::ResetKeyboardEnhancementFlags; use super::keyboard_enhancement_disabled_for; use super::parse_bool_env; + use super::tmux_session_detected; + use super::tmux_should_enable_modify_other_keys_for; use super::vscode_terminal_detected; use crossterm::Command; use pretty_assertions::assert_eq; @@ -268,11 +358,51 @@ mod tests { )); } + #[test] + fn tmux_session_detection_accepts_tmux_or_tmux_pane() { + assert!(tmux_session_detected( + Some("/tmp/tmux-501/default,1,0"), + /*tmux_pane*/ None + )); + assert!(tmux_session_detected(/*tmux*/ None, Some("%0"))); + assert!(!tmux_session_detected( + /*tmux*/ None, /*tmux_pane*/ None + )); + } + + #[test] + fn tmux_modify_other_keys_requests_csi_u_or_unknown_format() { + assert!(tmux_should_enable_modify_other_keys_for( + /*running_in_tmux_session*/ true, + Some("csi-u") + )); + assert!(tmux_should_enable_modify_other_keys_for( + /*running_in_tmux_session*/ true, /*extended_keys_format*/ None + )); + assert!(!tmux_should_enable_modify_other_keys_for( + /*running_in_tmux_session*/ true, + Some("xterm") + )); + assert!(!tmux_should_enable_modify_other_keys_for( + /*running_in_tmux_session*/ true, + Some("") + )); + assert!(!tmux_should_enable_modify_other_keys_for( + /*running_in_tmux_session*/ false, + Some("csi-u") + )); + } + #[test] fn reset_keyboard_enhancement_flags_clears_all_pushed_levels() { assert_eq!(ansi_for(ResetKeyboardEnhancementFlags), "\x1b[4;2m"); + } + #[test] fn disable_modify_other_keys_resets_xterm_keyboard_reporting() { assert_eq!(ansi_for(DisableModifyOtherKeys), "\x1b[>4;0m");