feat: Tampilkan unified diff pada hasil tool edit
This commit is contained in:
+61
-8
@@ -9,7 +9,8 @@ use super::super::Tool;
|
|||||||
use super::super::ToolCtx;
|
use super::super::ToolCtx;
|
||||||
use super::super::resolve_path;
|
use super::super::resolve_path;
|
||||||
use super::super::check_graduated_checks;
|
use super::super::check_graduated_checks;
|
||||||
use super::helpers::arg_str;
|
use super::helpers::{self, arg_str};
|
||||||
|
use similar::TextDiff;
|
||||||
|
|
||||||
/// Tool: replace text in a file. Requires the old string to be unique unless `replace_all` is true.
|
/// Tool: replace text in a file. Requires the old string to be unique unless `replace_all` is true.
|
||||||
pub struct Edit;
|
pub struct Edit;
|
||||||
@@ -99,11 +100,12 @@ impl Tool for Edit {
|
|||||||
};
|
};
|
||||||
fs::write(&path, &new_content)
|
fs::write(&path, &new_content)
|
||||||
.map_err(|e| anyhow!("failed to write '{rel}': {e}"))?;
|
.map_err(|e| anyhow!("failed to write '{rel}': {e}"))?;
|
||||||
let bytes_diff = if new_content.len() > content.len() {
|
let text_diff = TextDiff::from_lines(content.as_str(), new_content.as_str());
|
||||||
new_content.len() - content.len()
|
let diff_text = format!(
|
||||||
} else {
|
"{}",
|
||||||
content.len() - new_content.len()
|
text_diff.unified_diff().context_radius(3).header(&rel, &rel)
|
||||||
};
|
);
|
||||||
|
let diff_block = format!("```diff\n{}\n```", helpers::truncate_diff(&diff_text));
|
||||||
// Notify the LSP server of the on-disk change so diagnostics stay fresh.
|
// Notify the LSP server of the on-disk change so diagnostics stay fresh.
|
||||||
// Never fail the edit because of this — LSP errors are surfaced as a
|
// Never fail the edit because of this — LSP errors are surfaced as a
|
||||||
// trailing annotation on the success message instead.
|
// trailing annotation on the success message instead.
|
||||||
@@ -114,9 +116,60 @@ impl Tool for Edit {
|
|||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
if check_matches.is_empty() {
|
if check_matches.is_empty() {
|
||||||
Ok(format!("edited {} ({} byte delta){}", rel, bytes_diff as isize, lsp_note))
|
Ok(format!("edited {rel}\n{diff_block}{lsp_note}"))
|
||||||
} else {
|
} else {
|
||||||
Ok(format!("edited {} ({} byte delta). Graduated checks matched: {}{}", rel, bytes_diff as isize, check_matches.join(", "), lsp_note))
|
Ok(format!("edited {rel}. Graduated checks matched: {}\n{diff_block}{lsp_note}", check_matches.join(", ")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn test_ctx(workspace: std::path::PathBuf) -> crate::tool::ToolCtx {
|
||||||
|
crate::tool::ToolCtx::builder().workspaces(vec![workspace]).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn temp_workspace() -> std::path::PathBuf {
|
||||||
|
let dir = std::env::temp_dir().join(format!("zesdex-edit-test-{}", uuid::Uuid::new_v4()));
|
||||||
|
fs::create_dir_all(&dir).unwrap();
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edit_returns_a_diff_block_for_a_single_replace() {
|
||||||
|
let workspace = temp_workspace();
|
||||||
|
fs::write(workspace.join("a.txt"), "line1\nline2\nline3\n").unwrap();
|
||||||
|
let ctx = test_ctx(workspace.clone());
|
||||||
|
let args = json!({
|
||||||
|
"path": "a.txt",
|
||||||
|
"old": "line2",
|
||||||
|
"new": "changed",
|
||||||
|
"reason": "test edit"
|
||||||
|
});
|
||||||
|
let result = Edit.run(&ctx, &args).unwrap();
|
||||||
|
assert!(result.contains("```diff"));
|
||||||
|
assert!(result.contains("-line2"));
|
||||||
|
assert!(result.contains("+changed"));
|
||||||
|
fs::remove_dir_all(&workspace).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edit_truncates_a_very_large_diff() {
|
||||||
|
let workspace = temp_workspace();
|
||||||
|
let old_content: String = (0..300).map(|i| format!("line{i}\n")).collect();
|
||||||
|
let new_content: String = (0..300).map(|i| format!("changed{i}\n")).collect();
|
||||||
|
fs::write(workspace.join("big.txt"), &old_content).unwrap();
|
||||||
|
let ctx = test_ctx(workspace.clone());
|
||||||
|
let args = json!({
|
||||||
|
"path": "big.txt",
|
||||||
|
"old": &old_content,
|
||||||
|
"new": &new_content,
|
||||||
|
"reason": "test large replace"
|
||||||
|
});
|
||||||
|
let result = Edit.run(&ctx, &args).unwrap();
|
||||||
|
assert!(result.contains("more lines truncated"));
|
||||||
|
fs::remove_dir_all(&workspace).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user