use std::fmt::Write; use std::sync::Arc; use agent_client_protocol::schema::v1 as acp; use collections::HashSet; use gpui::{App, Entity, SharedString, Task}; use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use super::symbol_locator::SymbolLocator; use crate::{AgentTool, ToolCallEventStream, ToolInput}; /// Renames a symbol across the project using the language server. /// /// This performs a semantic rename, updating all references to the symbol across all files in the project. The language server determines which occurrences to rename based on the symbol's type and scope. /// /// Before using this tool, use read_file or grep to find the exact symbol name and line number. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct RenameToolInput { /// The symbol to rename. pub symbol: SymbolLocator, /// The new name for the symbol. pub new_name: String, } pub struct RenameTool { project: Entity, } impl RenameTool { pub fn new(project: Entity) -> Self { Self { project } } } impl AgentTool for RenameTool { type Input = RenameToolInput; type Output = String; const NAME: &'static str = "rename_symbol"; fn kind() -> acp::ToolKind { acp::ToolKind::Other } fn initial_title( &self, input: Result, _cx: &mut App, ) -> SharedString { if let Ok(input) = input { format!( "Rename `{}` to `{}`", input.symbol.symbol_name, input.new_name ) .into() } else { "Rename symbol".into() } } fn run( self: Arc, input: ToolInput, _event_stream: ToolCallEventStream, cx: &mut App, ) -> Task> { let project = self.project.clone(); cx.spawn(async move |cx| { let input = input .recv() .await .map_err(|e| format!("Failed to receive tool input: {e}"))?; let resolved = input.symbol.resolve(&project, cx).await?; let rename_task = project.update(cx, |project, cx| { project.perform_rename( resolved.buffer.clone(), resolved.position, input.new_name.clone(), cx, ) }); let transaction = rename_task .await .map_err(|e| format!("Rename failed: {e}"))?; if transaction.0.is_empty() { return Ok(format!( "No changes were made. The language server could not rename '{}'.", input.symbol.symbol_name )); } let buffers = transaction.0.keys().cloned().collect::>(); project .update(cx, |project, cx| project.save_buffers(buffers, cx)) .await .map_err(|e| format!("Rename succeeded, but failed to save renamed files: {e}"))?; let mut output = format!( "Renamed `{}` to `{}` in {} file(s):\n", input.symbol.symbol_name, input.new_name, transaction.0.len() ); for (buffer, _) in &transaction.0 { buffer.read_with(cx, |buffer, cx| { let path = buffer .file() .map(|f| f.full_path(cx).display().to_string()) .unwrap_or_else(|| "".to_string()); writeln!(output, "- {path}").ok(); }); } Ok(output) }) } }