| 57 | } |
| 58 | |
| 59 | async fn execute(&self, args: &serde_json::Value, ctx: &ToolContext) -> Result<ToolOutput> { |
| 60 | let file_path = match args.get("file_path").and_then(|v| v.as_str()) { |
| 61 | Some(p) => p, |
| 62 | None => return Ok(ToolOutput::error("file_path parameter is required")), |
| 63 | }; |
| 64 | |
| 65 | let old_string = match args.get("old_string").and_then(|v| v.as_str()) { |
| 66 | Some(s) => s, |
| 67 | None => return Ok(ToolOutput::error("old_string parameter is required")), |
| 68 | }; |
| 69 | |
| 70 | let new_string = match args.get("new_string").and_then(|v| v.as_str()) { |
| 71 | Some(s) => s, |
| 72 | None => return Ok(ToolOutput::error("new_string parameter is required")), |
| 73 | }; |
| 74 | |
| 75 | let replace_all = args |
| 76 | .get("replace_all") |
| 77 | .and_then(|v| v.as_bool()) |
| 78 | .unwrap_or(false); |
| 79 | |
| 80 | let workspace_path = match ctx.resolve_workspace_path(file_path) { |
| 81 | Ok(path) => path, |
| 82 | Err(e) => return Ok(ToolOutput::error(format!("Failed to resolve path: {}", e))), |
| 83 | }; |
| 84 | let display_path = ctx.workspace_services.display_path(&workspace_path); |
| 85 | |
| 86 | let (content, version) = match ctx.workspace_services.read_for_edit(&workspace_path).await { |
| 87 | Ok(pair) => pair, |
| 88 | Err(e) => { |
| 89 | return Ok(ToolOutput::error(format!( |
| 90 | "Failed to read file {}: {}", |
| 91 | display_path, e |
| 92 | ))) |
| 93 | } |
| 94 | }; |
| 95 | |
| 96 | let count = content.matches(old_string).count(); |
| 97 | |
| 98 | if count == 0 { |
| 99 | return Ok(ToolOutput::error(format!( |
| 100 | "old_string not found in {}", |
| 101 | display_path |
| 102 | ))); |
| 103 | } |
| 104 | |
| 105 | if count > 1 && !replace_all { |
| 106 | return Ok(ToolOutput::error(format!( |
| 107 | "old_string found {} times in {}. Use replace_all=true to replace all occurrences, or provide a more specific string.", |
| 108 | count, |
| 109 | display_path |
| 110 | ))); |
| 111 | } |
| 112 | |
| 113 | let new_content = if replace_all { |
| 114 | content.replace(old_string, new_string) |
| 115 | } else { |
| 116 | content.replacen(old_string, new_string, 1) |