use std::collections::HashSet;
use std::path::Path;
use serde_json::Value;
use super::ToolHandler;
use super::response::{ToolResult, text_result};
use super::shared::{node_kind_filter, node_kind_label, node_matches_query, normalize_slashes};
use super::validation::{not_indexed_message, unindexed_project_path_message};
pub(super) fn run(
handler: &mut ToolHandler,
args: &serde_json::Map<String, Value>,
project_path: Option<&str>,
project_root: Option<&str>,
) -> ToolResult {
let Some(project_root) = project_root else {
return text_result(¬_indexed_message(
handler.default_project_hint.as_deref(),
));
};
let Ok(mut cg) = crate::CodeGraph::open_sync(Path::new(project_root)) else {
return text_result(&unindexed_project_path_message(project_root));
};
let query = args
.get("query")
.and_then(Value::as_str)
.unwrap_or_default();
let needle = query.to_ascii_lowercase();
let limit = args
.get("limit")
.and_then(Value::as_u64)
.unwrap_or(10)
.clamp(1, 100) as usize;
let kind = args
.get("kind")
.and_then(Value::as_str)
.and_then(node_kind_filter);
let mut nodes = if let Some(kind) = kind {
cg.get_nodes_by_kind(kind)
.into_iter()
.filter(|node| node_matches_query(node, &needle))
.collect::<Vec<_>>()
} else {
cg.search_nodes(query, None)
.into_iter()
.map(|result| result.node)
.collect::<Vec<_>>()
};
nodes.truncate(limit);
let referenced_files = nodes
.iter()
.map(|node| normalize_slashes(&node.file_path))
.collect::<HashSet<_>>();
cg.close();
let result = if nodes.is_empty() {
text_result(&format!("No results found for `{query}`"))
} else {
let mut lines = Vec::with_capacity(nodes.len() + 2);
lines.push(format!("Found {} result(s) for `{query}`:", nodes.len()));
for node in &nodes {
lines.push(format!(
"- {} `{}` at {}:{}",
node_kind_label(node.kind),
node.name,
node.file_path,
node.start_line
));
}
text_result(&lines.join("\n"))
};
let result = handler.with_index_state_notice(result, project_root, &referenced_files);
handler.with_worktree_notice(result, project_path)
}