From 075e385969831e52ea07c9b8035b7fad8e5f7bb8 Mon Sep 17 00:00:00 2001 From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:55:50 -0700 Subject: [PATCH] =?UTF-8?q?Use=20=E2=8C=A5=E2=87=A7=E2=8C=83=20glyphs=20fo?= =?UTF-8?q?r=20key=20hints=20on=20mac=20(#3143)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### Summary - render the edit queued message shortcut with the ⌥ modifier on macOS builds - add a helper for status indicator snapshot suffixes - record macOS-specific snapshots for the status indicator widget --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 28 +++++------ ...mposer__tests__backspace_after_pastes.snap | 2 +- ...tom_pane__chat_composer__tests__empty.snap | 2 +- ...tom_pane__chat_composer__tests__large.snap | 2 +- ...chat_composer__tests__multiple_pastes.snap | 2 +- ...tom_pane__chat_composer__tests__small.snap | 2 +- ...chatwidget__tests__chat_small_idle_h2.snap | 2 +- ...exec_and_status_layout_vt100_snapshot.snap | 2 +- ...atwidget__tests__status_widget_active.snap | 2 +- codex-rs/tui/src/chatwidget/tests.rs | 4 +- codex-rs/tui/src/key_hint.rs | 49 +++++++++++++++++++ codex-rs/tui/src/lib.rs | 1 + ...__tests__renders_with_queued_messages.snap | 2 +- codex-rs/tui/src/status_indicator_widget.rs | 11 +++-- 14 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 codex-rs/tui/src/key_hint.rs diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index c873ac8d..dd784f92 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -11,7 +11,6 @@ use ratatui::layout::Rect; use ratatui::style::Color; use ratatui::style::Modifier; use ratatui::style::Style; -use ratatui::style::Styled; use ratatui::style::Stylize; use ratatui::text::Line; use ratatui::text::Span; @@ -37,6 +36,7 @@ use crate::bottom_pane::textarea::TextArea; use crate::bottom_pane::textarea::TextAreaState; use crate::clipboard_paste::normalize_pasted_path; use crate::clipboard_paste::pasted_image_format; +use crate::key_hint; use codex_file_search::FileMatch; use std::cell::RefCell; use std::collections::HashMap; @@ -1259,35 +1259,35 @@ impl WidgetRef for ChatComposer { } ActivePopup::None => { let bottom_line_rect = popup_rect; - let key_hint_style = Style::default().fg(Color::Cyan); - let mut hint = if self.ctrl_c_quit_hint { + let mut hint: Vec> = if self.ctrl_c_quit_hint { vec![ " ".into(), - "Ctrl+C again".set_style(key_hint_style), + key_hint::ctrl('C'), + " again".into(), " to quit".into(), ] } else { let newline_hint_key = if self.use_shift_enter_hint { - "Shift+⏎" + key_hint::shift('⏎') } else { - "Ctrl+J" + key_hint::ctrl('J') }; vec![ " ".into(), - "⏎".set_style(key_hint_style), + key_hint::plain('⏎'), " send ".into(), - newline_hint_key.set_style(key_hint_style), + newline_hint_key, " newline ".into(), - "Ctrl+T".set_style(key_hint_style), + key_hint::ctrl('T'), " transcript ".into(), - "Ctrl+C".set_style(key_hint_style), + key_hint::ctrl('C'), " quit".into(), ] }; if !self.ctrl_c_quit_hint && self.esc_backtrack_hint { hint.push(" ".into()); - hint.push("Esc".set_style(key_hint_style)); + hint.push(key_hint::plain("Esc")); hint.push(" edit prev".into()); } @@ -1635,7 +1635,6 @@ mod tests { use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyModifiers; - use insta::assert_snapshot; use ratatui::Terminal; use ratatui::backend::TestBackend; @@ -1687,13 +1686,12 @@ mod tests { .draw(|f| f.render_widget_ref(composer, f.area())) .unwrap_or_else(|e| panic!("Failed to draw {name} composer: {e}")); - assert_snapshot!(name, terminal.backend()); + insta::assert_snapshot!(name, terminal.backend()); } } #[test] fn slash_popup_model_first_for_mo_ui() { - use insta::assert_snapshot; use ratatui::Terminal; use ratatui::backend::TestBackend; @@ -1720,7 +1718,7 @@ mod tests { .unwrap_or_else(|e| panic!("Failed to draw composer: {e}")); // Visual snapshot should show the slash popup with /model as the first entry. - assert_snapshot!("slash_popup_mo", terminal.backend()); + insta::assert_snapshot!("slash_popup_mo", terminal.backend()); } #[test] diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__backspace_after_pastes.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__backspace_after_pastes.snap index 6933fa67..0c278fd7 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__backspace_after_pastes.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__backspace_after_pastes.snap @@ -11,4 +11,4 @@ expression: terminal.backend() "▌ " "▌ " "▌ " -" ⏎ send Ctrl+J newline Ctrl+T transcript Ctrl+C quit " +" ⏎ send ⌃J newline ⌃T transcript ⌃C quit " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__empty.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__empty.snap index 6d6d6b1a..c6192485 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__empty.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__empty.snap @@ -11,4 +11,4 @@ expression: terminal.backend() "▌ " "▌ " "▌ " -" ⏎ send Ctrl+J newline Ctrl+T transcript Ctrl+C quit " +" ⏎ send ⌃J newline ⌃T transcript ⌃C quit " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__large.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__large.snap index 3539b5aa..1660bb49 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__large.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__large.snap @@ -11,4 +11,4 @@ expression: terminal.backend() "▌ " "▌ " "▌ " -" ⏎ send Ctrl+J newline Ctrl+T transcript Ctrl+C quit " +" ⏎ send ⌃J newline ⌃T transcript ⌃C quit " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__multiple_pastes.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__multiple_pastes.snap index a2827ff9..21869a07 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__multiple_pastes.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__multiple_pastes.snap @@ -11,4 +11,4 @@ expression: terminal.backend() "▌ " "▌ " "▌ " -" ⏎ send Ctrl+J newline Ctrl+T transcript Ctrl+C quit " +" ⏎ send ⌃J newline ⌃T transcript ⌃C quit " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__small.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__small.snap index b5e97f8b..1f257317 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__small.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__small.snap @@ -11,4 +11,4 @@ expression: terminal.backend() "▌ " "▌ " "▌ " -" ⏎ send Ctrl+J newline Ctrl+T transcript Ctrl+C quit " +" ⏎ send ⌃J newline ⌃T transcript ⌃C quit " diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chat_small_idle_h2.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chat_small_idle_h2.snap index aeb69e84..41f43bab 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chat_small_idle_h2.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chat_small_idle_h2.snap @@ -3,4 +3,4 @@ source: tui/src/chatwidget/tests.rs expression: terminal.backend() --- "▌ Ask Codex to do anything " -" ⏎ send Ctrl+J newline Ctrl+T transc" +" ⏎ send ⌃J newline ⌃T transcript ⌃" diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chatwidget_exec_and_status_layout_vt100_snapshot.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chatwidget_exec_and_status_layout_vt100_snapshot.snap index 77087030..c6d5b002 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chatwidget_exec_and_status_layout_vt100_snapshot.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__chatwidget_exec_and_status_layout_vt100_snapshot.snap @@ -12,4 +12,4 @@ expression: visual Investigating rendering code (0s • Esc to interrupt) ▌Summarize recent commits - ⏎ send Ctrl+J newline Ctrl+T transcript Ctrl+C quit + ⏎ send ⌃J newline ⌃T transcript ⌃C quit diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_active.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_active.snap index 72bb3c34..088b43a7 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_active.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_active.snap @@ -6,5 +6,5 @@ expression: terminal.backend() " Analyzing (0s • Esc to interrupt) " " " "▌ Ask Codex to do anything " -" ⏎ send Ctrl+J newline Ctrl+T transcript Ctrl+C quit " +" ⏎ send ⌃J newline ⌃T transcript ⌃C quit " " " diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 1b1a7832..287ce706 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1075,8 +1075,6 @@ fn apply_patch_manual_approval_adjusts_header() { #[test] fn apply_patch_manual_flow_snapshot() { - use insta::assert_snapshot; - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(); let mut proposed_changes = HashMap::new(); @@ -1647,5 +1645,5 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() { } let visual = vt_lines.join("\n"); - insta::assert_snapshot!(visual); + assert_snapshot!(visual); } diff --git a/codex-rs/tui/src/key_hint.rs b/codex-rs/tui/src/key_hint.rs new file mode 100644 index 00000000..deeee6ae --- /dev/null +++ b/codex-rs/tui/src/key_hint.rs @@ -0,0 +1,49 @@ +use ratatui::style::Color; +use ratatui::style::Style; +use ratatui::text::Span; +use std::fmt::Display; + +#[cfg(test)] +const ALT_PREFIX: &str = "⌥"; +#[cfg(all(not(test), target_os = "macos"))] +const ALT_PREFIX: &str = "⌥"; +#[cfg(all(not(test), not(target_os = "macos")))] +const ALT_PREFIX: &str = "Alt+"; + +#[cfg(test)] +const CTRL_PREFIX: &str = "⌃"; +#[cfg(all(not(test), target_os = "macos"))] +const CTRL_PREFIX: &str = "⌃"; +#[cfg(all(not(test), not(target_os = "macos")))] +const CTRL_PREFIX: &str = "Ctrl+"; + +#[cfg(test)] +const SHIFT_PREFIX: &str = "⇧"; +#[cfg(all(not(test), target_os = "macos"))] +const SHIFT_PREFIX: &str = "⇧"; +#[cfg(all(not(test), not(target_os = "macos")))] +const SHIFT_PREFIX: &str = "Shift+"; + +fn key_hint_style() -> Style { + Style::default().fg(Color::Cyan) +} + +fn modifier_span(prefix: &str, key: impl Display) -> Span<'static> { + Span::styled(format!("{prefix}{key}"), key_hint_style()) +} + +pub(crate) fn ctrl(key: impl Display) -> Span<'static> { + modifier_span(CTRL_PREFIX, key) +} + +pub(crate) fn alt(key: impl Display) -> Span<'static> { + modifier_span(ALT_PREFIX, key) +} + +pub(crate) fn shift(key: impl Display) -> Span<'static> { + modifier_span(SHIFT_PREFIX, key) +} + +pub(crate) fn plain(key: impl Display) -> Span<'static> { + Span::styled(format!("{key}"), key_hint_style()) +} diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index c05efb67..c52f48e6 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -42,6 +42,7 @@ mod file_search; mod get_git_diff; mod history_cell; pub mod insert_history; +mod key_hint; pub mod live_wrap; mod markdown; mod markdown_stream; diff --git a/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__tests__renders_with_queued_messages.snap b/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__tests__renders_with_queued_messages.snap index cb517549..0290eb69 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__tests__renders_with_queued_messages.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__tests__renders_with_queued_messages.snap @@ -5,7 +5,7 @@ expression: terminal.backend() " Working (0s • Esc to interrupt) " " ↳ first " " ↳ second " -" Alt+↑ edit " +" ⌥↑ edit " " " " " " " diff --git a/codex-rs/tui/src/status_indicator_widget.rs b/codex-rs/tui/src/status_indicator_widget.rs index a41b7c35..126f3d12 100644 --- a/codex-rs/tui/src/status_indicator_widget.rs +++ b/codex-rs/tui/src/status_indicator_widget.rs @@ -14,6 +14,7 @@ use ratatui::widgets::WidgetRef; use crate::app_event::AppEvent; use crate::app_event_sender::AppEventSender; +use crate::key_hint; use crate::shimmer::shimmer_spans; use crate::tui::FrameRequester; use textwrap::Options as TwOptions; @@ -130,7 +131,8 @@ impl WidgetRef for StatusIndicatorWidget { } } if !self.queued_messages.is_empty() { - lines.push(Line::from(vec![" ".into(), "Alt+↑".cyan(), " edit".into()]).dim()); + let shortcut = key_hint::alt("↑"); + lines.push(Line::from(vec![" ".into(), shortcut, " edit".into()]).dim()); } let paragraph = Paragraph::new(lines); @@ -143,7 +145,6 @@ mod tests { use super::*; use crate::app_event::AppEvent; use crate::app_event_sender::AppEventSender; - use insta::assert_snapshot; use ratatui::Terminal; use ratatui::backend::TestBackend; use tokio::sync::mpsc::unbounded_channel; @@ -159,7 +160,7 @@ mod tests { terminal .draw(|f| w.render_ref(f.area(), f.buffer_mut())) .expect("draw"); - assert_snapshot!(terminal.backend()); + insta::assert_snapshot!(terminal.backend()); } #[test] @@ -173,7 +174,7 @@ mod tests { terminal .draw(|f| w.render_ref(f.area(), f.buffer_mut())) .expect("draw"); - assert_snapshot!(terminal.backend()); + insta::assert_snapshot!(terminal.backend()); } #[test] @@ -188,6 +189,6 @@ mod tests { terminal .draw(|f| w.render_ref(f.area(), f.buffer_mut())) .expect("draw"); - assert_snapshot!(terminal.backend()); + insta::assert_snapshot!(terminal.backend()); } }