feat(tui): Display keyboard shortcuts inline for approval options (#5889)
Shows single-key shortcuts (y, a, n) next to approval options to make them more discoverable. Previously these shortcuts worked but were hidden, making the feature hard to discover. Changes: - "Yes, proceed" now shows "y" shortcut - "Yes, and don't ask again" now shows "a" shortcut - "No, and tell Codex..." continues to show "esc" shortcut This improves UX by surfacing the quick keyboard shortcuts that were already functional but undiscoverable in the UI. --- Update: added parentheses for better visual clarity <img width="1540" height="486" alt="CleanShot 2025-11-05 at 11 47 07@2x" src="https://github.com/user-attachments/assets/f951c34a-9ec8-4b81-b151-7b2ccba94658" /> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Eric Traut <etraut@openai.com>
This commit is contained in:
@@ -117,7 +117,9 @@ impl ApprovalOverlay {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|opt| SelectionItem {
|
.map(|opt| SelectionItem {
|
||||||
name: opt.label.clone(),
|
name: opt.label.clone(),
|
||||||
display_shortcut: opt.display_shortcut,
|
display_shortcut: opt
|
||||||
|
.display_shortcut
|
||||||
|
.or_else(|| opt.additional_shortcuts.first().copied()),
|
||||||
dismiss_on_select: false,
|
dismiss_on_select: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use ratatui::layout::Rect;
|
|||||||
// Note: Table-based layout previously used Constraint; the manual renderer
|
// Note: Table-based layout previously used Constraint; the manual renderer
|
||||||
// below no longer requires it.
|
// below no longer requires it.
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
|
use ratatui::style::Style;
|
||||||
use ratatui::style::Stylize;
|
use ratatui::style::Stylize;
|
||||||
use ratatui::text::Line;
|
use ratatui::text::Line;
|
||||||
use ratatui::text::Span;
|
use ratatui::text::Span;
|
||||||
@@ -96,8 +97,9 @@ fn build_full_line(row: &GenericDisplayRow, desc_col: usize) -> Line<'static> {
|
|||||||
let this_name_width = Line::from(name_spans.clone()).width();
|
let this_name_width = Line::from(name_spans.clone()).width();
|
||||||
let mut full_spans: Vec<Span> = name_spans;
|
let mut full_spans: Vec<Span> = name_spans;
|
||||||
if let Some(display_shortcut) = row.display_shortcut {
|
if let Some(display_shortcut) = row.display_shortcut {
|
||||||
full_spans.push(" ".into());
|
full_spans.push(" (".into());
|
||||||
full_spans.push(display_shortcut.into());
|
full_spans.push(display_shortcut.into());
|
||||||
|
full_spans.push(")".into());
|
||||||
}
|
}
|
||||||
if let Some(desc) = row.description.as_ref() {
|
if let Some(desc) = row.description.as_ref() {
|
||||||
let gap = desc_col.saturating_sub(this_name_width);
|
let gap = desc_col.saturating_sub(this_name_width);
|
||||||
@@ -179,8 +181,9 @@ pub(crate) fn render_rows(
|
|||||||
);
|
);
|
||||||
if Some(i) == state.selected_idx {
|
if Some(i) == state.selected_idx {
|
||||||
// Match previous behavior: cyan + bold for the selected row.
|
// Match previous behavior: cyan + bold for the selected row.
|
||||||
|
// Reset the style first to avoid inheriting dim from keyboard shortcuts.
|
||||||
full_line.spans.iter_mut().for_each(|span| {
|
full_line.spans.iter_mut().for_each(|span| {
|
||||||
span.style = span.style.fg(Color::Cyan).bold();
|
span.style = Style::default().fg(Color::Cyan).bold();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ expression: terminal.backend().vt100().screen().contents()
|
|||||||
|
|
||||||
$ echo hello world
|
$ echo hello world
|
||||||
|
|
||||||
› 1. Yes, proceed
|
› 1. Yes, proceed (y)
|
||||||
2. Yes, and don't ask again for this command
|
2. Yes, and don't ask again for this command (a)
|
||||||
3. No, and tell Codex what to do differently esc
|
3. No, and tell Codex what to do differently (esc)
|
||||||
|
|
||||||
Press enter to confirm or esc to cancel
|
Press enter to confirm or esc to cancel
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ expression: terminal.backend().vt100().screen().contents()
|
|||||||
|
|
||||||
$ echo hello world
|
$ echo hello world
|
||||||
|
|
||||||
› 1. Yes, proceed
|
› 1. Yes, proceed (y)
|
||||||
2. Yes, and don't ask again for this command
|
2. Yes, and don't ask again for this command (a)
|
||||||
3. No, and tell Codex what to do differently esc
|
3. No, and tell Codex what to do differently (esc)
|
||||||
|
|
||||||
Press enter to confirm or esc to cancel
|
Press enter to confirm or esc to cancel
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ expression: terminal.backend().vt100().screen().contents()
|
|||||||
1 +hello
|
1 +hello
|
||||||
2 +world
|
2 +world
|
||||||
|
|
||||||
› 1. Yes, proceed
|
› 1. Yes, proceed (y)
|
||||||
2. No, and tell Codex what to do differently esc
|
2. No, and tell Codex what to do differently (esc)
|
||||||
|
|
||||||
Press enter to confirm or esc to cancel
|
Press enter to confirm or esc to cancel
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
assertion_line: 409
|
|
||||||
expression: "format!(\"{buf:?}\")"
|
expression: "format!(\"{buf:?}\")"
|
||||||
---
|
---
|
||||||
Buffer {
|
Buffer {
|
||||||
@@ -15,9 +14,9 @@ Buffer {
|
|||||||
" ",
|
" ",
|
||||||
" $ echo hello world ",
|
" $ echo hello world ",
|
||||||
" ",
|
" ",
|
||||||
"› 1. Yes, proceed ",
|
"› 1. Yes, proceed (y) ",
|
||||||
" 2. Yes, and don't ask again for this command ",
|
" 2. Yes, and don't ask again for this command (a) ",
|
||||||
" 3. No, and tell Codex what to do differently esc ",
|
" 3. No, and tell Codex what to do differently (esc) ",
|
||||||
" ",
|
" ",
|
||||||
" Press enter to confirm or esc to cancel ",
|
" Press enter to confirm or esc to cancel ",
|
||||||
],
|
],
|
||||||
@@ -30,9 +29,11 @@ Buffer {
|
|||||||
x: 2, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: ITALIC,
|
x: 2, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: ITALIC,
|
||||||
x: 7, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
x: 7, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||||
x: 0, y: 9, fg: Cyan, bg: Reset, underline: Reset, modifier: BOLD,
|
x: 0, y: 9, fg: Cyan, bg: Reset, underline: Reset, modifier: BOLD,
|
||||||
x: 17, y: 9, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
x: 21, y: 9, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||||
x: 47, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
x: 48, y: 10, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||||
x: 50, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
x: 49, y: 10, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||||
|
x: 48, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||||
|
x: 51, y: 11, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||||
x: 2, y: 13, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
x: 2, y: 13, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ expression: terminal.backend()
|
|||||||
" "
|
" "
|
||||||
" $ echo 'hello world' "
|
" $ echo 'hello world' "
|
||||||
" "
|
" "
|
||||||
"› 1. Yes, proceed "
|
"› 1. Yes, proceed (y) "
|
||||||
" 2. Yes, and don't ask again for this command "
|
" 2. Yes, and don't ask again for this command (a) "
|
||||||
" 3. No, and tell Codex what to do differently esc "
|
" 3. No, and tell Codex what to do differently (esc) "
|
||||||
" "
|
" "
|
||||||
" Press enter to confirm or esc to cancel "
|
" Press enter to confirm or esc to cancel "
|
||||||
|
|||||||
Reference in New Issue
Block a user