use crate::interactive::tui::types::App;
use crate::interactive::tui::wrap::{cell_width, truncate_cells};
pub(super) const CANCEL_HINT: &str = " (Esc to cancel) ";
pub(super) const SEPARATOR: &str = "· ";
const MIN_DETAIL_CELLS: usize = 8;
fn bare_text(activity: Option<&str>) -> String {
match activity {
Some(tool) => format!("running {tool} "),
None => "thinking ".to_string(),
}
}
pub(super) fn bare_action_width(activity: Option<&str>) -> usize {
cell_width(&bare_text(activity))
}
pub(super) struct BusyRowPlan {
pub lead_painted: bool,
pub action_room: usize,
pub status_segments: usize,
}
pub(super) fn busy_row_plan(
frame_width: usize,
lead_width: usize,
spinner_width: usize,
elapsed_width: usize,
segment_widths: &[usize],
full_action_width: usize,
bare_action_width: usize,
) -> BusyRowPlan {
let chrome = spinner_width + elapsed_width + cell_width(SEPARATOR) + cell_width(CANCEL_HINT);
let base = frame_width.saturating_sub(chrome);
if lead_width + full_action_width + segment_widths.iter().sum::<usize>() <= base {
return BusyRowPlan {
lead_painted: lead_width > 0,
action_room: full_action_width,
status_segments: segment_widths.len(),
};
}
let mut kept = 0;
let mut used = lead_width;
for (index, width) in segment_widths.iter().enumerate() {
if used + full_action_width + *width > base {
break;
}
used += *width;
kept = index + 1;
}
if used + full_action_width <= base {
return BusyRowPlan {
lead_painted: lead_width > 0,
action_room: full_action_width,
status_segments: kept,
};
}
let lead_painted = lead_width > 0 && lead_width + bare_action_width <= base;
let lead = if lead_painted { lead_width } else { 0 };
BusyRowPlan {
lead_painted,
action_room: base.saturating_sub(lead),
status_segments: 0,
}
}
pub(super) fn action_text(
activity: Option<&str>,
running: Option<(String, serde_json::Value)>,
room: usize,
) -> String {
let bare = bare_text(activity);
let Some(tool) = activity.map(str::to_string) else {
return bare;
};
let detail = running.as_ref().and_then(|(name, arguments)| {
(name == &tool)
.then(|| crate::interactive::tui::stream_events::tool_call_detail(name, arguments))
.flatten()
});
let Some(detail) = detail else { return bare };
let detail = one_line(&detail);
let full = format!("running {tool}: {detail} ");
if cell_width(&full) <= room.max(cell_width(&bare)) {
return full;
}
let head = format!("running {tool}: ");
let budget = room.saturating_sub(cell_width(&head) + 2);
if budget < MIN_DETAIL_CELLS {
return bare;
}
format!("{head}{}… ", truncate_cells(&detail, budget))
}
pub(super) fn running_call(app: &App) -> Option<(String, serde_json::Value)> {
app.transcript
.newest_open_tool()
.map(|(name, arguments)| (name.to_string(), arguments.clone()))
}
fn one_line(detail: &str) -> String {
detail.split_whitespace().collect::<Vec<_>>().join(" ")
}