mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
refactor(tui/history): consolidate merges + introduce ExecAction enum
- Add ExecAction enum and action_enum_from_parsed() - Switch exec_tools to use enum instead of string tags - Add ChatWidget::history_maybe_merge_tool_with_previous() - Adopt merge helper in web_search_complete - Keep behavior identical; centralize merge pathways Validated with ./build-fast.sh (no warnings).
This commit is contained in:
@@ -1605,6 +1605,44 @@ impl ChatWidget<'_> {
|
|||||||
crate::chatwidget::exec_tools::try_merge_completed_exec_at(self, idx);
|
crate::chatwidget::exec_tools::try_merge_completed_exec_at(self, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge adjacent tool cells with the same header (e.g., successive Web Search blocks)
|
||||||
|
fn history_maybe_merge_tool_with_previous(&mut self, idx: usize) {
|
||||||
|
if idx == 0 || idx >= self.history_cells.len() { return; }
|
||||||
|
let new_lines = self.history_cells[idx].display_lines();
|
||||||
|
let new_header = new_lines
|
||||||
|
.first()
|
||||||
|
.and_then(|l| l.spans.get(0))
|
||||||
|
.map(|s| s.content.clone().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if new_header.is_empty() { return; }
|
||||||
|
let prev_lines = self.history_cells[idx - 1].display_lines();
|
||||||
|
let prev_header = prev_lines
|
||||||
|
.first()
|
||||||
|
.and_then(|l| l.spans.get(0))
|
||||||
|
.map(|s| s.content.clone().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if new_header != prev_header { return; }
|
||||||
|
let mut combined = prev_lines.clone();
|
||||||
|
while combined
|
||||||
|
.last()
|
||||||
|
.map(|l| crate::render::line_utils::is_blank_line_trim(l))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{ combined.pop(); }
|
||||||
|
let mut body: Vec<ratatui::text::Line<'static>> = new_lines.into_iter().skip(1).collect();
|
||||||
|
while body.first().map(|l| crate::render::line_utils::is_blank_line_trim(l)).unwrap_or(false) { body.remove(0); }
|
||||||
|
while body.last().map(|l| crate::render::line_utils::is_blank_line_trim(l)).unwrap_or(false) { body.pop(); }
|
||||||
|
if let Some(first_line) = body.first_mut() {
|
||||||
|
if let Some(first_span) = first_line.spans.get_mut(0) {
|
||||||
|
if first_span.content == " └ " || first_span.content == "└ " {
|
||||||
|
first_span.content = " ".into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combined.extend(body);
|
||||||
|
self.history_replace_at(idx - 1, Box::new(crate::history_cell::PlainHistoryCell { lines: combined, kind: crate::history_cell::HistoryCellType::Plain }));
|
||||||
|
self.history_remove_at(idx);
|
||||||
|
}
|
||||||
|
|
||||||
/// Clean up faded-out animation cells
|
/// Clean up faded-out animation cells
|
||||||
fn process_animation_cleanup(&mut self) {
|
fn process_animation_cleanup(&mut self) {
|
||||||
// With trait-based cells, we can't easily detect and clean up specific cell types
|
// With trait-based cells, we can't easily detect and clean up specific cell types
|
||||||
|
|||||||
@@ -301,10 +301,10 @@ pub(super) fn handle_exec_begin_now(chat: &mut ChatWidget<'_>, ev: ExecCommandBe
|
|||||||
if chat.ended_call_ids.contains(&ev.call_id) { return; }
|
if chat.ended_call_ids.contains(&ev.call_id) { return; }
|
||||||
for cell in &chat.history_cells { cell.trigger_fade(); }
|
for cell in &chat.history_cells { cell.trigger_fade(); }
|
||||||
let parsed_command = ev.parsed_cmd.clone();
|
let parsed_command = ev.parsed_cmd.clone();
|
||||||
let action = history_cell::action_from_parsed(&parsed_command);
|
let action = history_cell::action_enum_from_parsed(&parsed_command);
|
||||||
chat.height_manager.borrow_mut().record_event(HeightEvent::RunBegin);
|
chat.height_manager.borrow_mut().record_event(HeightEvent::RunBegin);
|
||||||
|
|
||||||
if action == "read" {
|
if matches!(action, history_cell::ExecAction::Read) {
|
||||||
chat.exec.running_commands.insert(
|
chat.exec.running_commands.insert(
|
||||||
ev.call_id.clone(),
|
ev.call_id.clone(),
|
||||||
super::RunningCommand { command: ev.command.clone(), parsed: parsed_command.clone(), history_index: None },
|
super::RunningCommand { command: ev.command.clone(), parsed: parsed_command.clone(), history_index: None },
|
||||||
@@ -379,13 +379,13 @@ pub(super) fn handle_exec_end_now(chat: &mut ChatWidget<'_>, ev: ExecCommandEndE
|
|||||||
.map(|cmd| (cmd.command, cmd.parsed, cmd.history_index))
|
.map(|cmd| (cmd.command, cmd.parsed, cmd.history_index))
|
||||||
.unwrap_or_else(|| (vec![call_id.clone()], vec![], None));
|
.unwrap_or_else(|| (vec![call_id.clone()], vec![], None));
|
||||||
|
|
||||||
let action = history_cell::action_from_parsed(&parsed);
|
let action = history_cell::action_enum_from_parsed(&parsed);
|
||||||
if action == "read" {
|
if matches!(action, history_cell::ExecAction::Read) {
|
||||||
let any_read_running = chat
|
let any_read_running = chat
|
||||||
.exec
|
.exec
|
||||||
.running_commands
|
.running_commands
|
||||||
.values()
|
.values()
|
||||||
.any(|rc| history_cell::action_from_parsed(&rc.parsed) == "read");
|
.any(|rc| matches!(history_cell::action_enum_from_parsed(&rc.parsed), history_cell::ExecAction::Read));
|
||||||
if !any_read_running {
|
if !any_read_running {
|
||||||
if let Some(idx) = chat.exec.running_read_agg_index.take() {
|
if let Some(idx) = chat.exec.running_read_agg_index.take() {
|
||||||
if idx < chat.history_cells.len() {
|
if idx < chat.history_cells.len() {
|
||||||
|
|||||||
@@ -49,32 +49,7 @@ pub(super) fn web_search_complete(chat: &mut ChatWidget<'_>, call_id: String, qu
|
|||||||
let final_query = query.or(maybe_query);
|
let final_query = query.or(maybe_query);
|
||||||
let completed = rt.finalize_web_search(true, final_query);
|
let completed = rt.finalize_web_search(true, final_query);
|
||||||
chat.history_replace_at(i, Box::new(completed));
|
chat.history_replace_at(i, Box::new(completed));
|
||||||
|
chat.history_maybe_merge_tool_with_previous(i);
|
||||||
// Merge adjacent Web Search blocks with same header
|
|
||||||
if i > 0 {
|
|
||||||
let new_lines = chat.history_cells[i].display_lines();
|
|
||||||
let new_header = new_lines.first().and_then(|l| l.spans.get(0)).map(|s| s.content.clone().to_string()).unwrap_or_default();
|
|
||||||
let prev_lines = chat.history_cells[i - 1].display_lines();
|
|
||||||
let prev_header = prev_lines.first().and_then(|l| l.spans.get(0)).map(|s| s.content.clone().to_string()).unwrap_or_default();
|
|
||||||
if !new_header.is_empty() && new_header == prev_header {
|
|
||||||
let mut combined = prev_lines.clone();
|
|
||||||
while combined.last().map(|l| crate::render::line_utils::is_blank_line_trim(l)).unwrap_or(false) { combined.pop(); }
|
|
||||||
let mut body: Vec<ratatui::text::Line<'static>> = new_lines.into_iter().skip(1).collect();
|
|
||||||
while body.first().map(|l| crate::render::line_utils::is_blank_line_trim(l)).unwrap_or(false) { body.remove(0); }
|
|
||||||
while body.last().map(|l| crate::render::line_utils::is_blank_line_trim(l)).unwrap_or(false) { body.pop(); }
|
|
||||||
if let Some(first_line) = body.first_mut() {
|
|
||||||
if let Some(first_span) = first_line.spans.get_mut(0) {
|
|
||||||
if first_span.content == " └ " || first_span.content == "└ " {
|
|
||||||
first_span.content = " ".into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
combined.extend(body);
|
|
||||||
chat.history_replace_at(i - 1, Box::new(history_cell::PlainHistoryCell { lines: combined, kind: history_cell::HistoryCellType::Plain }));
|
|
||||||
chat.history_remove_at(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// history_replace_at already invalidates and redraws
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,28 @@ pub(crate) enum ExecKind {
|
|||||||
Run,
|
Run,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unified action classification for exec commands
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) enum ExecAction {
|
||||||
|
Read,
|
||||||
|
Search,
|
||||||
|
List,
|
||||||
|
Run,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn action_enum_from_parsed(parsed: &Vec<codex_core::parse_command::ParsedCommand>) -> ExecAction {
|
||||||
|
use codex_core::parse_command::ParsedCommand;
|
||||||
|
for p in parsed {
|
||||||
|
match p {
|
||||||
|
ParsedCommand::Read { .. } => return ExecAction::Read,
|
||||||
|
ParsedCommand::Search { .. } => return ExecAction::Search,
|
||||||
|
ParsedCommand::ListFiles { .. } => return ExecAction::List,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExecAction::Run
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub(crate) enum ExecStatus {
|
pub(crate) enum ExecStatus {
|
||||||
Running,
|
Running,
|
||||||
|
|||||||
Reference in New Issue
Block a user