TUI: improve unified mention selection visibility (#28959)

## Summary

[@milanglacier reported in
#28653](https://github.com/openai/codex/issues/28653) that the active
mention candidate is hard to distinguish. I suspect [@binbjz’s #28500
report](https://github.com/openai/codex/issues/28500) _(where arrow-key
navigation appeared not to work)_ may describe the same presentation
problem: the selection may have been changing, but the UI was not
showing the active row clearly in their terminal. This PR makes two
small changes to the selection indication behavior:

- Reserve a two-character gutter and mark the active candidate with `> `
for color-agnostic indicator coverage.
- Apply the shared theme-aware accent to the entire selected row for
extra emphasis.
- Update the existing popup snapshot.

Reverse-video styling was considered, but avoided it because it is
overly dependent on the user’s terminal palette.

<img width="2046" height="482" alt="image"
src="https://github.com/user-attachments/assets/b5eb62c3-fd24-4c09-906e-7bd66913b5c6"
/>

## Testing

- `just test -p codex-tui default_unified_mention_popup_snapshot`
- `just clippy -p codex-tui`
- `just fmt`
- Compiled `codex-cli` and tested the unified mentions picker in the
terminal.
This commit is contained in:
canvrno-oai
2026-06-18 13:37:04 -07:00
committed by GitHub
Unverified
parent 29eb434bc5
commit 9bcc09f9f7
2 changed files with 20 additions and 26 deletions
@@ -9,8 +9,7 @@ use ratatui::text::Span;
use ratatui::widgets::Widget;
use crate::line_truncation::truncate_line_with_ellipsis_if_overflow;
use crate::render::Insets;
use crate::render::RectExt;
use crate::style::accent_style;
use super::candidate::MentionType;
use super::candidate::SearchResult;
@@ -46,15 +45,7 @@ pub(super) fn render_popup(
(area, None)
};
render_rows(
list_area.inset(Insets::tlbr(
/*top*/ 0, /*left*/ 2, /*bottom*/ 0, /*right*/ 0,
)),
buf,
rows,
state,
empty_message,
);
render_rows(list_area, buf, rows, state, empty_message);
if let Some(hint_area) = hint_area {
let hint_area = Rect {
@@ -78,7 +69,7 @@ fn render_rows(
return;
}
if rows.is_empty() {
Line::from(empty_message.italic()).render(area, buf);
Line::from(vec![" ".into(), empty_message.italic()]).render(area, buf);
return;
}
@@ -131,31 +122,34 @@ fn build_line(
width: usize,
primary_column_width: usize,
) -> Line<'static> {
let base_style = if selected {
Style::default().bold()
} else {
Style::default()
};
let dim_style = if selected {
Style::default().bold()
} else {
Style::default().dim()
};
let base_style = Style::default();
let dim_style = Style::default().dim();
let tag = row.mention_type.span(base_style);
let tag_width = tag.width();
let content_width = width.saturating_sub(tag_width.saturating_add(2));
let gutter = if selected { "> " } else { " " };
let gutter_width = gutter.len();
let content_width =
width.saturating_sub(gutter_width.saturating_add(tag_width).saturating_add(2));
let content = truncate_line_with_ellipsis_if_overflow(
content_line(row, base_style, dim_style, primary_column_width),
content_width,
);
let rendered_content_width = content.width();
let mut spans = Vec::new();
let mut spans = vec![gutter.into()];
spans.extend(content.spans);
let padding = width.saturating_sub(rendered_content_width.saturating_add(tag_width));
let padding = width.saturating_sub(
gutter_width
.saturating_add(rendered_content_width)
.saturating_add(tag_width),
);
if padding > 0 {
spans.push(" ".repeat(padding).set_style(dim_style));
}
spans.push(tag);
if selected {
let style = accent_style();
spans.iter_mut().for_each(|span| span.style = style);
}
Line::from(spans)
}
@@ -5,7 +5,7 @@ expression: terminal.backend()
" "
" @sa "
" "
" Sample Plugin Plugin with skills and an MCP server Plugin"
"> Sample Plugin Plugin with skills and an MCP server Plugin"
" "
" "
" "