mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
125 lines
3.7 KiB
Rust
125 lines
3.7 KiB
Rust
use std::fmt::Write;
|
|
use std::sync::Arc;
|
|
|
|
use agent_client_protocol::schema 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<Project>,
|
|
}
|
|
|
|
impl RenameTool {
|
|
pub fn new(project: Entity<Project>) -> 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<Self::Input, serde_json::Value>,
|
|
_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<Self>,
|
|
input: ToolInput<Self::Input>,
|
|
_event_stream: ToolCallEventStream,
|
|
cx: &mut App,
|
|
) -> Task<Result<String, String>> {
|
|
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::<HashSet<_>>();
|
|
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(|| "<untitled>".to_string());
|
|
writeln!(output, "- {path}").ok();
|
|
});
|
|
}
|
|
|
|
Ok(output)
|
|
})
|
|
}
|
|
}
|