Skip to content

fix: issue 12 edit_file tool panics #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 34 additions & 15 deletions src/fs_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ impl FileSystemService {
let expanded_path = expand_home(requested_path.to_path_buf());

// Resolve the absolute path
let absolute_path = expanded_path
.as_path()
.is_absolute()
.then(|| expanded_path.clone())
.unwrap_or_else(|| env::current_dir().unwrap().join(&expanded_path));
let absolute_path = if expanded_path.as_path().is_absolute() {
expanded_path.clone()
} else {
env::current_dir().unwrap().join(&expanded_path)
};

// Normalize the path
let normalized_requested = normalize_path(&absolute_path);
Expand Down Expand Up @@ -121,6 +121,16 @@ impl FileSystemService {
})
}

fn detect_line_ending(&self, text: &str) -> &str {
if text.contains("\r\n") {
"\r\n"
} else if text.contains('\r') {
"\r"
} else {
"\n"
}
}

pub async fn zip_directory(
&self,
input_dir: String,
Expand Down Expand Up @@ -472,6 +482,7 @@ impl FileSystemService {

// Read file content and normalize line endings
let content_str = tokio::fs::read_to_string(&valid_path).await?;
let original_line_ending = self.detect_line_ending(&content_str);
let content_str = normalize_line_endings(&content_str);

// Apply edits sequentially
Expand All @@ -480,15 +491,13 @@ impl FileSystemService {
for edit in edits {
let normalized_old = normalize_line_endings(&edit.old_text);
let normalized_new = normalize_line_endings(&edit.new_text);

// If exact match exists, use it
if modified_content.contains(&normalized_old) {
modified_content = modified_content.replacen(&normalized_old, &normalized_new, 1);
continue;
}

// Otherwise, try line-by-line matching with flexibility for whitespace
// trim ends help to avoid inconsistencies empty lines at the end that may break the comparison
let old_lines: Vec<String> = normalized_old
.trim_end()
.split('\n')
Expand All @@ -514,7 +523,6 @@ impl FileSystemService {

if is_match {
// Preserve original indentation of first line
// leading spaces
let original_indent = content_lines[i]
.chars()
.take_while(|&c| c.is_whitespace())
Expand All @@ -524,12 +532,12 @@ impl FileSystemService {
.split('\n')
.enumerate()
.map(|(j, line)| {
// keep indentation of the first line
// Keep indentation of the first line
if j == 0 {
return format!("{}{}", original_indent, line.trim_start());
}

// For subsequent lines, try to preserve relative indentation
// For subsequent lines, preserve relative indentation and original whitespace type
let old_indent = old_lines
.get(j)
.map(|line| {
Expand All @@ -544,12 +552,22 @@ impl FileSystemService {
.take_while(|&c| c.is_whitespace())
.collect::<String>();

let relative_indent = new_indent.len() - old_indent.len();

// Use the same whitespace character as original_indent (tabs or spaces)
let indent_char = if original_indent.contains('\t') {
"\t"
} else {
" "
};
let relative_indent = if new_indent.len() >= old_indent.len() {
new_indent.len() - old_indent.len()
} else {
0 // Don't reduce indentation below original
};
format!(
"{}{}",
original_indent,
" ".repeat(relative_indent.max(0)) + line.trim_start()
"{}{}{}",
&original_indent,
&indent_char.repeat(relative_indent),
line.trim_start()
)
})
.collect();
Expand Down Expand Up @@ -593,6 +611,7 @@ impl FileSystemService {

if !is_dry_run {
let target = save_to.unwrap_or(valid_path.as_path());
let modified_content = modified_content.replace("\n", original_line_ending);
tokio::fs::write(target, modified_content).await?;
}

Expand Down
2 changes: 1 addition & 1 deletion src/fs_service/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub async fn write_zip_entry(
}

pub fn normalize_line_endings(text: &str) -> String {
text.replace("\r\n", "\n")
text.replace("\r\n", "\n").replace('\r', "\n")
}

// checks if path component is a Prefix::VerbatimDisk
Expand Down
Loading