From 2fe4a2b4d8bac510708c189fa15c527ed2d3d148 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 21 Sep 2026 18:09:28 +0000 Subject: [PATCH] Show the voice toggle in the TUI shortcut overlay (#47082) ## What changed Add a Voice entry using the configured `toggle_voice` binding (`F8` by default). Show it only when voice commands are enabled and no side conversation is active. ## Testing Update footer and shortcut overlay snapshots to include the Voice entry. GitOrigin-RevId: ba92f5af1ce1a06d9700d31dcc90e5acb1613803 --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 50 +++-------------- .../bottom_pane/chat_composer/footer_state.rs | 1 + .../chat_composer/snapshot_tests.rs | 54 ++++++++++++++++++- codex-rs/tui/src/bottom_pane/footer.rs | 2 + .../tui/src/bottom_pane/shortcut_overlay.rs | 1 + ...shortcuts_collaboration_modes_enabled.snap | 9 ++-- ...oter__tests__footer_shortcuts_running.snap | 11 ++-- ...tests__footer_shortcuts_shift_and_esc.snap | 11 ++-- ...ests__shortcut_overlay_customized_wsl.snap | 9 ++-- 9 files changed, 85 insertions(+), 63 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 42937b1945..f5fc9ee1a2 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -761,6 +761,7 @@ impl ChatComposer { .primary_hint(KeymapContext::Chat, "decrease_reasoning_effort"), reasoning_up_key: default_keymap .primary_hint(KeymapContext::Chat, "increase_reasoning_effort"), + toggle_voice_key: default_keymap.primary_hint(KeymapContext::Chat, "toggle_voice"), }, has_focus: has_input_focus, frame_requester: None, @@ -1048,6 +1049,7 @@ impl ChatComposer { keymap.primary_hint(KeymapContext::Chat, "decrease_reasoning_effort"); self.footer.reasoning_up_key = keymap.primary_hint(KeymapContext::Chat, "increase_reasoning_effort"); + self.footer.toggle_voice_key = keymap.primary_hint(KeymapContext::Chat, "toggle_voice"); } /// Return the contexts whose handlers can consume the next composer key. @@ -3865,6 +3867,10 @@ impl ChatComposer { history_search: self.footer.history_search_key, reasoning_down: self.footer.reasoning_down_key, reasoning_up: self.footer.reasoning_up_key, + toggle_voice: self + .footer + .toggle_voice_key + .filter(|_| self.voice_command_enabled && !self.side_conversation_active), }, active_agent_label: self.footer.active_agent_label.clone(), } @@ -5088,50 +5094,6 @@ mod tests { ) } - #[test] - fn shortcut_footer_displays_configured_chords() { - use codex_config::types::KeybindingSpec; - use codex_config::types::KeybindingsSpec; - use codex_config::types::TuiKeymap; - - let mut config = TuiKeymap::default(); - config.global.open_external_editor = Some(KeybindingsSpec::One(KeybindingSpec( - "ctrl-g ctrl-g".to_string(), - ))); - config.editor.insert_newline = Some(KeybindingsSpec::One(KeybindingSpec( - "ctrl-x enter".to_string(), - ))); - let keymap = RuntimeKeymap::from_config(&config).expect("valid composer chords"); - let (mut composer, _rx) = new_test_composer(); - composer.set_keymap_bindings(&keymap); - composer.footer.mode = FooterMode::ShortcutOverlay; - - let hints = composer.footer_props().key_hints; - assert_eq!( - hints.external_editor, - Some(ShortcutHint::Chord { - prefix: key_hint::ctrl(KeyCode::Char('g')), - completion: key_hint::ctrl(KeyCode::Char('g')), - }) - ); - assert_eq!( - hints.insert_newline, - Some(ShortcutHint::Chord { - prefix: key_hint::ctrl(KeyCode::Char('x')), - completion: key_hint::plain(KeyCode::Enter), - }) - ); - - snapshot_composer_state( - "footer_mode_configured_key_chords", - /*enhanced_keys_supported*/ false, - |composer| { - composer.set_keymap_bindings(&keymap); - composer.footer.mode = FooterMode::ShortcutOverlay; - }, - ); - } - #[test] fn parent_owned_thread_placeholder_snapshot() { snapshot_composer_state( diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs b/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs index f13aaa7f76..18580a30b1 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs @@ -226,6 +226,7 @@ pub(super) struct FooterState { pub(super) history_search_key: Option, pub(super) reasoning_down_key: Option, pub(super) reasoning_up_key: Option, + pub(super) toggle_voice_key: Option, } #[derive(Clone, Debug)] diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/snapshot_tests.rs b/codex-rs/tui/src/bottom_pane/chat_composer/snapshot_tests.rs index c50c922f26..2a22f909a2 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer/snapshot_tests.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer/snapshot_tests.rs @@ -1,10 +1,62 @@ -//! Snapshot coverage for draft text and live voice controls in the composer. +//! Snapshot coverage for draft text, shortcuts, and live voice controls in the composer. use super::tests::new_test_composer; +use super::tests::snapshot_composer_state_with_width; +use crate::bottom_pane::footer::FooterMode; +use crate::key_hint; +use crate::key_hint::ShortcutHint; +use crate::keymap::RuntimeKeymap; use crate::render::renderable::Renderable; +use crossterm::event::KeyCode; +use pretty_assertions::assert_eq; use ratatui::buffer::Buffer; use ratatui::layout::Rect; +#[test] +fn shortcut_footer_displays_configured_chords() { + use codex_config::types::KeybindingSpec; + use codex_config::types::KeybindingsSpec; + use codex_config::types::TuiKeymap; + + let mut config = TuiKeymap::default(); + config.global.open_external_editor = Some(KeybindingsSpec::One(KeybindingSpec( + "ctrl-g ctrl-g".to_string(), + ))); + config.editor.insert_newline = Some(KeybindingsSpec::One(KeybindingSpec( + "ctrl-x enter".to_string(), + ))); + let keymap = RuntimeKeymap::from_config(&config).expect("valid composer chords"); + let (mut composer, _rx) = new_test_composer(); + composer.set_keymap_bindings(&keymap); + composer.footer.mode = FooterMode::ShortcutOverlay; + + let hints = composer.footer_props().key_hints; + assert_eq!( + hints.external_editor, + Some(ShortcutHint::Chord { + prefix: key_hint::ctrl(KeyCode::Char('g')), + completion: key_hint::ctrl(KeyCode::Char('g')), + }) + ); + assert_eq!( + hints.insert_newline, + Some(ShortcutHint::Chord { + prefix: key_hint::ctrl(KeyCode::Char('x')), + completion: key_hint::plain(KeyCode::Enter), + }) + ); + + snapshot_composer_state_with_width( + "footer_mode_configured_key_chords", + /*width*/ 100, + /*enhanced_keys_supported*/ false, + |composer| { + composer.set_keymap_bindings(&keymap); + composer.footer.mode = FooterMode::ShortcutOverlay; + }, + ); +} + #[test] fn draft_and_voice_composer_snapshots() { use crate::bottom_pane::voice_strip::VoiceStripPhase; diff --git a/codex-rs/tui/src/bottom_pane/footer.rs b/codex-rs/tui/src/bottom_pane/footer.rs index 76a13b744c..e05190d795 100644 --- a/codex-rs/tui/src/bottom_pane/footer.rs +++ b/codex-rs/tui/src/bottom_pane/footer.rs @@ -123,6 +123,7 @@ pub(crate) struct FooterKeyHints { pub(crate) history_search: Option, pub(crate) reasoning_down: Option, pub(crate) reasoning_up: Option, + pub(crate) toggle_voice: Option, } impl FooterKeyHints { @@ -140,6 +141,7 @@ impl FooterKeyHints { history_search: Some(key_hint::ctrl(KeyCode::Char('r')).into()), reasoning_down: Some(key_hint::alt(KeyCode::Char(',')).into()), reasoning_up: Some(key_hint::alt(KeyCode::Char('.')).into()), + toggle_voice: Some(key_hint::plain(KeyCode::F(8)).into()), } } } diff --git a/codex-rs/tui/src/bottom_pane/shortcut_overlay.rs b/codex-rs/tui/src/bottom_pane/shortcut_overlay.rs index fecf2cc926..ac6cd657c1 100644 --- a/codex-rs/tui/src/bottom_pane/shortcut_overlay.rs +++ b/codex-rs/tui/src/bottom_pane/shortcut_overlay.rs @@ -73,6 +73,7 @@ pub(super) fn lines(props: &FooterProps, width: u16) -> Vec> { } session.push(hints.reasoning_down, "Less reasoning"); session.push(hints.reasoning_up, "More reasoning"); + session.push(hints.toggle_voice, "Voice"); session.push(hints.agents, "Agents (empty prompt)"); session.entries.push(Shortcut::new( key_hint::ctrl(KeyCode::Char('c')), diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_collaboration_modes_enabled.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_collaboration_modes_enabled.snap index 6a46157528..0eb963406f 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_collaboration_modes_enabled.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_collaboration_modes_enabled.snap @@ -10,10 +10,11 @@ expression: terminal.backend() " @ Mention files shift+tab Change mode " " ! Shell command ⌥+, Less reasoning " " ctrl+j New line ⌥+. More reasoning " -" ctrl+v Paste image ctrl+c Quit " -" ctrl+g External editor " -" ctrl+r Search history Transcript (open first) " -" esc esc Edit last message ctrl+t Open transcript " +" ctrl+v Paste image f8 Voice " +" ctrl+g External editor ctrl+c Quit " +" ctrl+r Search history " +" esc esc Edit last message Transcript (open first) " +" ctrl+t Open transcript " " f3 Find text " " pgup / pgdn Scroll " " ctrl+space Start selection " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_running.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_running.snap index 1747fbdb55..cb2a7ce0a2 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_running.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_running.snap @@ -9,11 +9,12 @@ expression: terminal.backend() " / Commands tab Queue message " " @ Mention files ⌥+, Less reasoning " " ! Shell command ⌥+. More reasoning " -" ctrl+j New line ctrl+c Interrupt " -" ctrl+v Paste image " -" ctrl+g External editor Transcript (open first) " -" ctrl+r Search history ctrl+t Open transcript " -" esc esc Edit last message f3 Find text " +" ctrl+j New line f8 Voice " +" ctrl+v Paste image ctrl+c Interrupt " +" ctrl+g External editor " +" ctrl+r Search history Transcript (open first) " +" esc esc Edit last message ctrl+t Open transcript " +" f3 Find text " " pgup / pgdn Scroll " " ctrl+space Start selection " " ctrl+home / ctrl+end Top / latest " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_shift_and_esc.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_shift_and_esc.snap index 748fefa2fc..fd8b6b6943 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_shift_and_esc.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_shift_and_esc.snap @@ -9,11 +9,12 @@ expression: terminal.backend() " / Commands tab Send message " " @ Mention files ⌥+, Less reasoning " " ! Shell command ⌥+. More reasoning " -" shift+enter New line ctrl+c Quit " -" ctrl+v Paste image " -" ctrl+g External editor Transcript (open first) " -" ctrl+r Search history ctrl+t Open transcript " -" esc Edit last message f3 Find text " +" shift+enter New line f8 Voice " +" ctrl+v Paste image ctrl+c Quit " +" ctrl+g External editor " +" ctrl+r Search history Transcript (open first) " +" esc Edit last message ctrl+t Open transcript " +" f3 Find text " " pgup / pgdn Scroll " " ctrl+space Start selection " " ctrl+home / ctrl+end Top / latest " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__shortcut_overlay__tests__shortcut_overlay_customized_wsl.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__shortcut_overlay__tests__shortcut_overlay_customized_wsl.snap index 48b0d80fc3..ac2fea009e 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__shortcut_overlay__tests__shortcut_overlay_customized_wsl.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__shortcut_overlay__tests__shortcut_overlay_customized_wsl.snap @@ -8,10 +8,11 @@ expression: terminal.backend() " / Commands tab Send message " " @ Mention files shift+tab Change mode " " ! Shell command ⌥+. More reasoning " -" shift+enter New line ← Agents (empty prompt) " -" ctrl+⌥+v Paste image ctrl+c Quit " -" ctrl+x ctrl+e External editor " -" esc Edit last message Transcript (open first) " +" shift+enter New line f8 Voice " +" ctrl+⌥+v Paste image ← Agents (empty prompt) " +" ctrl+x ctrl+e External editor ctrl+c Quit " +" esc Edit last message " +" Transcript (open first) " " ctrl+t Open transcript " " f3 Find text " " pgup / pgdn Scroll "