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.
/// Accounts for wrapped descriptions so that long tooltips don't overflow.
pub(crate) fn calculate_required_height(&self, width: u16) -> u16 {
use super::selection_popup_common::GenericDisplayRow;
use super::selection_popup_common::measure_rows_height;
let 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()
};
let rows = self.rows_from_matches(self.filtered());
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,
@@ -172,6 +150,32 @@ impl CommandPopup {
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.
pub(crate) fn move_up(&mut self) {
let len = self.filtered_items().len();
@@ -198,32 +202,11 @@ impl CommandPopup {
impl WidgetRef for CommandPopup {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
let 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()
};
let rows = self.rows_from_matches(self.filtered());
render_rows(
area,
buf,
&rows_all,
&rows,
&self.state,
MAX_POPUP_ROWS,
false,

View File

@@ -15,6 +15,7 @@ use ratatui::widgets::Paragraph;
use ratatui::widgets::Widget;
use super::scroll_state::ScrollState;
use crate::ui_consts::LIVE_PREFIX_COLS;
/// A generic representation of a display row for selection popups.
pub(crate) struct GenericDisplayRow {
@@ -99,14 +100,34 @@ pub(crate) fn render_rows(
.border_style(Style::default().add_modifier(Modifier::DIM));
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 {
x: area.x.saturating_add(1),
x: area.x.saturating_add(prefix_cols),
y: area.y,
width: area.width.saturating_sub(1),
width: area.width.saturating_sub(prefix_cols),
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 content_area.height > 0 {
let para = Paragraph::new(Line::from(empty_message.dim().italic()));

View File

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

View File

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

View File

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