use std::collections::HashMap;
use ratatui::style::Color;
use super::path_shortener::PathShortener;
use super::tool_entries::lookup_tool;
fn extract_arg_from_keys(
keys: &[&str],
args: &HashMap<String, serde_json::Value>,
) -> Option<String> {
if args.is_empty() {
return None;
}
for key in keys {
if let Some(val) = args.get(*key)
&& let Some(s) = val.as_str()
{
return Some(s.replace('\n', " "));
}
}
None
}
pub fn format_tool_call_display(
tool_name: &str,
args: &HashMap<String, serde_json::Value>,
) -> String {
let (verb, arg) = format_tool_call_parts(tool_name, args);
if arg.is_empty() {
verb
} else {
format!("{verb} {arg}")
}
}
pub fn format_tool_call_parts(
tool_name: &str,
args: &HashMap<String, serde_json::Value>,
) -> (String, String) {
let shortener = PathShortener::default();
format_tool_call_parts_short(tool_name, args, &shortener)
}
pub fn format_tool_call_parts_with_wd(
tool_name: &str,
args: &HashMap<String, serde_json::Value>,
working_dir: Option<&str>,
) -> (String, String) {
let shortener = PathShortener::new(working_dir);
format_tool_call_parts_short(tool_name, args, &shortener)
}
pub fn format_tool_call_parts_short(
tool_name: &str,
args: &HashMap<String, serde_json::Value>,
shortener: &PathShortener,
) -> (String, String) {
let (verb, arg) = format_parts_inner(tool_name, args, shortener);
let shortened = shortener.shorten_text(&arg);
let truncated = if shortened.len() > 80 {
format!("{}...", &shortened[..77])
} else {
shortened
};
(verb, truncated)
}
fn format_parts_inner(
tool_name: &str,
args: &HashMap<String, serde_json::Value>,
shortener: &PathShortener,
) -> (String, String) {
let entry = lookup_tool(tool_name);
if tool_name == "spawn_subagent" {
let verb = args
.get("agent_type")
.and_then(|v| v.as_str())
.map(|s| {
match s {
"Explore" | "Code-Explorer" | "code_explorer" => "Explore".to_string(),
"Planner" | "planner" => "Plan".to_string(),
"ask-user" | "ask_user" => "AskUser".to_string(),
other => other.to_string(),
}
})
.unwrap_or_else(|| "Agent".to_string());
let task = extract_arg_from_keys(&["description", "task"], args)
.unwrap_or_else(|| "working...".to_string());
return (verb, task);
}
if tool_name == "past_sessions" {
let action = args
.get("action")
.and_then(|v| v.as_str())
.unwrap_or("list");
return match action {
"list" => ("List Sessions".to_string(), String::new()),
"read" => {
let id = args
.get("session_id")
.and_then(|v| v.as_str())
.unwrap_or("...");
("Read Session".to_string(), id.to_string())
}
"search" => {
let q = args.get("query").and_then(|v| v.as_str()).unwrap_or("...");
("Search Sessions".to_string(), format!("\"{q}\""))
}
"info" => {
let id = args
.get("session_id")
.and_then(|v| v.as_str())
.unwrap_or("...");
("Session Info".to_string(), id.to_string())
}
other => ("Sessions".to_string(), other.to_string()),
};
}
if matches!(tool_name, "grep" | "search" | "Grep") {
let pattern = args
.get("pattern")
.or_else(|| args.get("query"))
.and_then(|v| v.as_str())
.unwrap_or("...");
let pattern_display = if pattern.len() > 40 {
format!("\"{}...\"", &pattern[..37])
} else {
format!("\"{pattern}\"")
};
if let Some(path) = args.get("path").and_then(|v| v.as_str()) {
let rel = shortener.shorten(path);
return ("Grep".to_string(), format!("{pattern_display} in {rel}"));
}
return ("Grep".to_string(), pattern_display);
}
if matches!(tool_name, "ast_grep" | "AstGrep") {
let pattern = args
.get("pattern")
.and_then(|v| v.as_str())
.unwrap_or("...");
let pattern_display = if pattern.len() > 40 {
format!("\"{}...\"", &pattern[..37])
} else {
format!("\"{pattern}\"")
};
if let Some(lang) = args.get("lang").and_then(|v| v.as_str()) {
return (
"AST-Grep".to_string(),
format!("{pattern_display} [{lang}]"),
);
}
return ("AST-Grep".to_string(), pattern_display);
}
if matches!(tool_name, "list_files" | "Glob") {
let pattern = args.get("pattern").and_then(|v| v.as_str()).unwrap_or("*");
let pattern_display = if pattern.len() > 40 {
format!("{}...", &pattern[..37])
} else {
pattern.to_string()
};
if let Some(path) = args.get("path").and_then(|v| v.as_str())
&& path != "."
&& !path.is_empty()
{
let rel = shortener.shorten(path);
return ("List".to_string(), format!("{pattern_display} in {rel}"));
}
return ("List".to_string(), pattern_display);
}
if entry.verb == "Call" {
let pretty_name = tool_name
.replace('_', " ")
.split_whitespace()
.map(|w| {
let mut c = w.chars();
match c.next() {
Some(ch) => format!("{}{}", ch.to_uppercase(), c.as_str()),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ");
if let Some(arg) = extract_arg_from_keys(entry.primary_arg_keys, args) {
return (pretty_name, arg);
}
return (pretty_name, String::new());
}
if let Some(summary) = extract_arg_from_keys(entry.primary_arg_keys, args) {
let is_path_arg = entry
.primary_arg_keys
.first()
.is_some_and(|k| *k == "file_path" || *k == "path");
let summary = if is_path_arg {
shortener.shorten(&summary)
} else {
summary
};
return (entry.verb.to_string(), summary);
}
if tool_name.starts_with("mcp__") {
let parts: Vec<&str> = tool_name.splitn(3, "__").collect();
if parts.len() == 3 {
return ("MCP".to_string(), format!("{}/{}", parts[1], parts[2]));
}
}
(entry.verb.to_string(), entry.label.to_string())
}
pub const GREEN_GRADIENT: &[Color] = &[
Color::Rgb(0, 200, 80),
Color::Rgb(0, 220, 100),
Color::Rgb(0, 240, 120),
Color::Rgb(0, 255, 140),
Color::Rgb(0, 240, 120),
Color::Rgb(0, 220, 100),
];