fix alignment in slash command popup (#3937)

This commit is contained in:
Jeremy Rose
2025-09-19 12:08:04 -07:00
committed by GitHub
parent 9b18875a42
commit ff389dc52f
5 changed files with 60 additions and 56 deletions

View File

@@ -95,32 +95,10 @@ impl CommandPopup {
/// Determine the preferred height of the popup for a given width. /// Determine the preferred height of the popup for a given width.
/// Accounts for wrapped descriptions so that long tooltips don't overflow. /// Accounts for wrapped descriptions so that long tooltips don't overflow.
pub(crate) fn calculate_required_height(&self, width: u16) -> u16 { pub(crate) fn calculate_required_height(&self, width: u16) -> u16 {
use super::selection_popup_common::GenericDisplayRow;
use super::selection_popup_common::measure_rows_height; use super::selection_popup_common::measure_rows_height;
let matches = self.filtered(); let rows = self.rows_from_matches(self.filtered());
let rows_all: Vec<GenericDisplayRow> = if matches.is_empty() {
Vec::new()
} else {
matches
.into_iter()
.map(|(item, indices, _)| match item {
CommandItem::Builtin(cmd) => GenericDisplayRow {
name: format!("/{}", cmd.command()),
match_indices: indices.map(|v| v.into_iter().map(|i| i + 1).collect()),
is_current: false,
description: Some(cmd.description().to_string()),
},
CommandItem::UserPrompt(i) => GenericDisplayRow {
name: format!("/{}", self.prompts[i].name),
match_indices: indices.map(|v| v.into_iter().map(|i| i + 1).collect()),
is_current: false,
description: Some("send saved prompt".to_string()),
},
})
.collect()
};
measure_rows_height(&rows_all, &self.state, MAX_POPUP_ROWS, width) measure_rows_height(&rows, &self.state, MAX_POPUP_ROWS, width)
} }
/// Compute fuzzy-filtered matches over built-in commands and user prompts, /// Compute fuzzy-filtered matches over built-in commands and user prompts,
@@ -172,6 +150,32 @@ impl CommandPopup {
self.filtered().into_iter().map(|(c, _, _)| c).collect() self.filtered().into_iter().map(|(c, _, _)| c).collect()
} }
fn rows_from_matches(
&self,
matches: Vec<(CommandItem, Option<Vec<usize>>, i32)>,
) -> Vec<GenericDisplayRow> {
matches
.into_iter()
.map(|(item, indices, _)| {
let (name, description) = match item {
CommandItem::Builtin(cmd) => {
(format!("/{}", cmd.command()), cmd.description().to_string())
}
CommandItem::UserPrompt(i) => (
format!("/{}", self.prompts[i].name),
"send saved prompt".to_string(),
),
};
GenericDisplayRow {
name,
match_indices: indices.map(|v| v.into_iter().map(|i| i + 1).collect()),
is_current: false,
description: Some(description),
}
})
.collect()
}
/// Move the selection cursor one step up. /// Move the selection cursor one step up.
pub(crate) fn move_up(&mut self) { pub(crate) fn move_up(&mut self) {
let len = self.filtered_items().len(); let len = self.filtered_items().len();
@@ -198,32 +202,11 @@ impl CommandPopup {
impl WidgetRef for CommandPopup { impl WidgetRef for CommandPopup {
fn render_ref(&self, area: Rect, buf: &mut Buffer) { fn render_ref(&self, area: Rect, buf: &mut Buffer) {
let matches = self.filtered(); let rows = self.rows_from_matches(self.filtered());
let rows_all: Vec<GenericDisplayRow> = if matches.is_empty() {
Vec::new()
} else {
matches
.into_iter()
.map(|(item, indices, _)| match item {
CommandItem::Builtin(cmd) => GenericDisplayRow {
name: format!("/{}", cmd.command()),
match_indices: indices.map(|v| v.into_iter().map(|i| i + 1).collect()),
is_current: false,
description: Some(cmd.description().to_string()),
},
CommandItem::UserPrompt(i) => GenericDisplayRow {
name: format!("/{}", self.prompts[i].name),
match_indices: indices.map(|v| v.into_iter().map(|i| i + 1).collect()),
is_current: false,
description: Some("send saved prompt".to_string()),
},
})
.collect()
};
render_rows( render_rows(
area, area,
buf, buf,
&rows_all, &rows,
&self.state, &self.state,
MAX_POPUP_ROWS, MAX_POPUP_ROWS,
false, false,

View File

@@ -15,6 +15,7 @@ use ratatui::widgets::Paragraph;
use ratatui::widgets::Widget; use ratatui::widgets::Widget;
use super::scroll_state::ScrollState; use super::scroll_state::ScrollState;
use crate::ui_consts::LIVE_PREFIX_COLS;
/// A generic representation of a display row for selection popups. /// A generic representation of a display row for selection popups.
pub(crate) struct GenericDisplayRow { pub(crate) struct GenericDisplayRow {
@@ -99,14 +100,34 @@ pub(crate) fn render_rows(
.border_style(Style::default().add_modifier(Modifier::DIM)); .border_style(Style::default().add_modifier(Modifier::DIM));
block.render(area, buf); block.render(area, buf);
// Content renders to the right of the border. // Content renders to the right of the border with the same live prefix
// padding used by the composer so the popup aligns with the input text.
let prefix_cols = LIVE_PREFIX_COLS;
let content_area = Rect { let content_area = Rect {
x: area.x.saturating_add(1), x: area.x.saturating_add(prefix_cols),
y: area.y, y: area.y,
width: area.width.saturating_sub(1), width: area.width.saturating_sub(prefix_cols),
height: area.height, height: area.height,
}; };
// Clear the padding column(s) so stale characters never peek between the
// border and the popup contents.
let padding_cols = prefix_cols.saturating_sub(1);
if padding_cols > 0 {
let pad_start = area.x.saturating_add(1);
let pad_end = pad_start
.saturating_add(padding_cols)
.min(area.x.saturating_add(area.width));
let pad_bottom = area.y.saturating_add(area.height);
for x in pad_start..pad_end {
for y in area.y..pad_bottom {
if let Some(cell) = buf.cell_mut((x, y)) {
cell.set_symbol(" ");
}
}
}
}
if rows_all.is_empty() { if rows_all.is_empty() {
if content_area.height > 0 { if content_area.height > 0 {
let para = Paragraph::new(Line::from(empty_message.dim().italic())); let para = Paragraph::new(Line::from(empty_message.dim().italic()));

View File

@@ -4,5 +4,5 @@ expression: terminal.backend()
--- ---
"▌ /mo " "▌ /mo "
"▌ " "▌ "
"▌/model choose what model and reasoning effort to use " "▌ /model choose what model and reasoning effort to use "
"▌/mention mention a file " "▌ /mention mention a file "

View File

@@ -5,7 +5,7 @@ expression: render_lines(&view)
▌ Select Approval Mode ▌ Select Approval Mode
▌ Switch between Codex approval presets ▌ Switch between Codex approval presets
▌> 1. Read Only (current) Codex can read files > 1. Read Only (current) Codex can read files
▌ 2. Full Access Codex can edit files 2. Full Access Codex can edit files
Press Enter to confirm or Esc to go back Press Enter to confirm or Esc to go back

View File

@@ -4,7 +4,7 @@ expression: render_lines(&view)
--- ---
▌ Select Approval Mode ▌ Select Approval Mode
▌> 1. Read Only (current) Codex can read files > 1. Read Only (current) Codex can read files
▌ 2. Full Access Codex can edit files 2. Full Access Codex can edit files
Press Enter to confirm or Esc to go back Press Enter to confirm or Esc to go back