From 3baccba0ac2026d3be9de9378913ad6774facb03 Mon Sep 17 00:00:00 2001 From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:38:43 -0700 Subject: [PATCH] Show loading state when @ search results are pending (#3061) ## Summary - allow selection popups to specify their empty state message - show a "loading..." placeholder in the file search popup while matches are pending - update other popup call sites to continue using a "no matches" message ## Testing - just fmt - just fix -p codex-tui - cargo test -p codex-tui ------ https://chatgpt.com/codex/tasks/task_i_68b73e956e90832caf4d04a75fcc9c46 --- codex-rs/tui/src/bottom_pane/command_popup.rs | 10 +++++++++- .../tui/src/bottom_pane/file_search_popup.rs | 19 ++++++++++++++----- .../src/bottom_pane/list_selection_view.rs | 10 +++++++++- .../src/bottom_pane/selection_popup_common.rs | 3 ++- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/command_popup.rs b/codex-rs/tui/src/bottom_pane/command_popup.rs index 4a0f3c13..a5961e31 100644 --- a/codex-rs/tui/src/bottom_pane/command_popup.rs +++ b/codex-rs/tui/src/bottom_pane/command_popup.rs @@ -195,7 +195,15 @@ impl WidgetRef for CommandPopup { }) .collect() }; - render_rows(area, buf, &rows_all, &self.state, MAX_POPUP_ROWS, false); + render_rows( + area, + buf, + &rows_all, + &self.state, + MAX_POPUP_ROWS, + false, + "no matches", + ); } } diff --git a/codex-rs/tui/src/bottom_pane/file_search_popup.rs b/codex-rs/tui/src/bottom_pane/file_search_popup.rs index f046a2f1..65dfe693 100644 --- a/codex-rs/tui/src/bottom_pane/file_search_popup.rs +++ b/codex-rs/tui/src/bottom_pane/file_search_popup.rs @@ -132,11 +132,20 @@ impl WidgetRef for &FileSearchPopup { .collect() }; - if self.waiting && rows_all.is_empty() { - // Render a minimal waiting stub using the shared renderer (no rows -> "no matches"). - render_rows(area, buf, &[], &self.state, MAX_POPUP_ROWS, false); + let empty_message = if self.waiting { + "loading..." } else { - render_rows(area, buf, &rows_all, &self.state, MAX_POPUP_ROWS, false); - } + "no matches" + }; + + render_rows( + area, + buf, + &rows_all, + &self.state, + MAX_POPUP_ROWS, + false, + empty_message, + ); } } diff --git a/codex-rs/tui/src/bottom_pane/list_selection_view.rs b/codex-rs/tui/src/bottom_pane/list_selection_view.rs index 6760b67f..35763969 100644 --- a/codex-rs/tui/src/bottom_pane/list_selection_view.rs +++ b/codex-rs/tui/src/bottom_pane/list_selection_view.rs @@ -222,7 +222,15 @@ impl BottomPaneView for ListSelectionView { }) .collect(); if rows_area.height > 0 { - render_rows(rows_area, buf, &rows, &self.state, MAX_POPUP_ROWS, true); + render_rows( + rows_area, + buf, + &rows, + &self.state, + MAX_POPUP_ROWS, + true, + "no matches", + ); } if let Some(hint) = &self.footer_hint { diff --git a/codex-rs/tui/src/bottom_pane/selection_popup_common.rs b/codex-rs/tui/src/bottom_pane/selection_popup_common.rs index c5de9510..3852d10b 100644 --- a/codex-rs/tui/src/bottom_pane/selection_popup_common.rs +++ b/codex-rs/tui/src/bottom_pane/selection_popup_common.rs @@ -36,11 +36,12 @@ pub(crate) fn render_rows( state: &ScrollState, max_results: usize, _dim_non_selected: bool, + empty_message: &str, ) { let mut rows: Vec = Vec::new(); if rows_all.is_empty() { rows.push(Row::new(vec![Cell::from(Line::from( - "no matches".dim().italic(), + empty_message.dim().italic(), ))])); } else { let max_rows_from_area = area.height as usize;