feat(tui/history): polish explore block labeling

This commit is contained in:
James Peter
2025-09-17 13:35:52 +10:00
parent 360fbf94a3
commit d7ce134537
7 changed files with 72 additions and 20 deletions
@@ -487,7 +487,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
EventMsg::WebSearchBegin(WebSearchBeginEvent { .. }) => {}
EventMsg::WebSearchComplete(WebSearchCompleteEvent { call_id: _, query }) => {
if let Some(query) = query {
ts_println!(self, "🌐 Searched: {query}");
ts_println!(self, "🌐 Search: {query}");
}
}
EventMsg::PatchApplyBegin(PatchApplyBeginEvent {
+28 -1
View File
@@ -1394,7 +1394,7 @@ impl ChatWidget<'_> {
}
/// If a completed exec cell sits at `idx`, attempt to merge it into the
/// previous cell when they represent the same action header (e.g., Searched, Read).
/// previous cell when they represent the same action header (e.g., Search, Read).
// MCP tool call handlers now live in chatwidget::tools
@@ -2646,6 +2646,7 @@ impl ChatWidget<'_> {
// Maintain input focus when new history arrives
self.bottom_pane.ensure_input_focus();
self.app_event_tx.send(AppEvent::RequestRedraw);
self.refresh_explore_trailing_flags();
self.refresh_reasoning_collapsed_visibility();
pos
}
@@ -2695,6 +2696,7 @@ impl ChatWidget<'_> {
self.history_cells[idx] = cell;
self.invalidate_height_cache();
self.request_redraw();
self.refresh_explore_trailing_flags();
// Keep debug info for this cell index as-is.
}
}
@@ -2710,6 +2712,7 @@ impl ChatWidget<'_> {
}
self.invalidate_height_cache();
self.request_redraw();
self.refresh_explore_trailing_flags();
}
}
@@ -2818,6 +2821,30 @@ impl ChatWidget<'_> {
}
}
fn refresh_explore_trailing_flags(&mut self) {
let mut trailing_non_reasoning: Option<usize> = None;
for i in (0..self.history_cells.len()).rev() {
if self.history_cells[i]
.as_any()
.downcast_ref::<history_cell::CollapsibleReasoningCell>()
.is_some()
{
continue;
}
trailing_non_reasoning = Some(i);
break;
}
for (idx, cell) in self.history_cells.iter_mut().enumerate() {
if let Some(explore) = cell
.as_any_mut()
.downcast_mut::<history_cell::ExploreAggregationCell>()
{
explore.set_trailing(Some(idx) == trailing_non_reasoning);
}
}
}
fn submit_user_message(&mut self, user_message: UserMessage) {
// Surface a local diagnostic note and anchor it to the NEXT turn,
// placing it directly after the user prompt so ordering is stable.
+1 -1
View File
@@ -477,7 +477,7 @@ pub(super) fn handle_exec_begin_now(
},
);
if !chat.tools_state.running_web_search.is_empty() {
chat.bottom_pane.update_status_text("Searched".to_string());
chat.bottom_pane.update_status_text("Search".to_string());
} else {
let preview = chat
.exec
@@ -2,5 +2,5 @@
source: tui/src/chatwidget/tests.rs
expression: blob1
---
⠋ Exploring
└ List ls -la
⠋ Exploring...
└ List ls -la
@@ -2,6 +2,6 @@
source: tui/src/chatwidget/tests.rs
expression: blob3
---
⠋ Exploring
└ List ls -la
Read foo.txt
⠋ Exploring...
└ List ls -la
Read foo.txt
+1 -1
View File
@@ -15,7 +15,7 @@ pub(super) fn web_search_begin(chat: &mut ChatWidget<'_>, call_id: String, query
chat.tools_state
.running_web_search
.insert(super::ToolCallId(call_id), (idx, query));
chat.bottom_pane.update_status_text("Searched".to_string());
chat.bottom_pane.update_status_text("Search".to_string());
chat.mark_needs_redraw();
}
+36 -11
View File
@@ -391,7 +391,7 @@ impl ExploreEntry {
}
match self.action {
ExecAction::Read => "Read",
ExecAction::Search => "Searched",
ExecAction::Search => "Search",
ExecAction::List => "List",
ExecAction::Run => "Run",
}
@@ -660,15 +660,21 @@ fn annotation_for_range(start: u32, end: u32) -> Option<String> {
pub(crate) struct ExploreAggregationCell {
entries: Vec<ExploreEntry>,
is_trailing: bool,
}
impl ExploreAggregationCell {
pub(crate) fn new() -> Self {
Self {
entries: Vec::new(),
is_trailing: true,
}
}
pub(crate) fn set_trailing(&mut self, trailing: bool) {
self.is_trailing = trailing;
}
pub(crate) fn push_from_parsed(
&mut self,
parsed: &[ParsedCommand],
@@ -859,27 +865,46 @@ impl HistoryCell for ExploreAggregationCell {
}
fn display_lines(&self) -> Vec<Line<'static>> {
let header = if self.is_trailing {
"Exploring..."
} else {
"Explored"
};
if self.entries.is_empty() {
return vec![Line::styled(
"Exploring",
header,
Style::default().fg(crate::colors::text()),
)];
}
let mut lines: Vec<Line<'static>> = Vec::new();
lines.push(Line::styled(
"Exploring",
header,
Style::default().fg(crate::colors::text()),
));
let max_label_len = self
.entries
.iter()
.map(|e| e.label().chars().count())
.max()
.unwrap_or(0);
for (idx, entry) in self.entries.iter().enumerate() {
let prefix = if idx == 0 { "└ " } else { " " };
let mut spans: Vec<Span<'static>> = vec![Span::styled(
prefix,
Style::default().add_modifier(Modifier::DIM),
)];
let label = entry.label();
let label_len = label.chars().count();
let padding = max_label_len.saturating_sub(label_len) + 1;
let mut padded_label = String::with_capacity(label.len() + padding);
padded_label.push_str(label);
padded_label.extend(std::iter::repeat(' ').take(padding));
spans.push(Span::styled(
format!("{:<8} ", entry.label()),
padded_label,
Style::default().fg(crate::colors::text_dim()),
));
spans.extend(entry.summary_spans());
@@ -2425,7 +2450,7 @@ impl HistoryCell for MergedExecCell {
Style::default().fg(crate::colors::text()),
)),
ExecKind::Search => Some(Line::styled(
"Searched",
"Search",
Style::default().fg(crate::colors::text_dim()),
)),
ExecKind::List => Some(Line::styled(
@@ -2808,7 +2833,7 @@ fn exec_render_parts_parsed(
Style::default().fg(crate::colors::text()),
)),
ExecAction::Search => pre.push(Line::styled(
"Searched",
"Search",
Style::default().fg(crate::colors::text_dim()),
)),
ExecAction::List => pre.push(Line::styled(
@@ -2830,7 +2855,7 @@ fn exec_render_parts_parsed(
Some(o) if o.exit_code == 0 => {
let done = match action {
ExecAction::Read => "Read".to_string(),
ExecAction::Search => "Searched".to_string(),
ExecAction::Search => "Search".to_string(),
ExecAction::List => "List".to_string(),
ExecAction::Run => match &ctx_path {
Some(p) => format!("Ran in {}", p),
@@ -2857,7 +2882,7 @@ fn exec_render_parts_parsed(
Some(_) => {
let done = match action {
ExecAction::Read => "Read".to_string(),
ExecAction::Search => "Searched".to_string(),
ExecAction::Search => "Search".to_string(),
ExecAction::List => "List".to_string(),
ExecAction::Run => match &ctx_path {
Some(p) => format!("Ran in {}", p),
@@ -5924,7 +5949,7 @@ fn new_parsed_command(
};
let header = match action {
ExecAction::Read => "Read",
ExecAction::Search => "Searched",
ExecAction::Search => "Search",
ExecAction::List => "List",
ExecAction::Run => unreachable!(),
};
@@ -5942,7 +5967,7 @@ fn new_parsed_command(
lines.push(Line::styled(
match action {
ExecAction::Read => "Read",
ExecAction::Search => "Searched",
ExecAction::Search => "Search",
ExecAction::List => "List",
ExecAction::Run => unreachable!(),
},
@@ -5969,7 +5994,7 @@ fn new_parsed_command(
lines.push(Line::styled(
match action {
ExecAction::Read => "Read",
ExecAction::Search => "Searched",
ExecAction::Search => "Search",
ExecAction::List => "List",
ExecAction::Run => unreachable!(),
},