diff --git a/aider/tools/delete_block.py b/aider/tools/delete_block.py index c23d23666..5ab2d6d37 100644 --- a/aider/tools/delete_block.py +++ b/aider/tools/delete_block.py @@ -8,6 +8,7 @@ from .tool_utils import ( apply_change, handle_tool_error, format_tool_result, + generate_unified_diff_snippet, ) def _execute_delete_block(coder, file_path, start_pattern, end_pattern=None, line_count=None, near_context=None, occurrence=1, change_id=None, dry_run=False): @@ -50,7 +51,7 @@ def _execute_delete_block(coder, file_path, start_pattern, end_pattern=None, lin return f"Warning: No changes made (deletion would not change file)" # 5. Generate diff for feedback - diff_snippet = coder._generate_diff_snippet_delete(original_content, start_line, end_line) + diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path) num_deleted = end_line - start_line + 1 num_occurrences = len(start_pattern_indices) occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else "" @@ -86,4 +87,4 @@ def _execute_delete_block(coder, file_path, start_pattern, end_pattern=None, lin return handle_tool_error(coder, tool_name, e, add_traceback=False) except Exception as e: # Handle unexpected errors - return handle_tool_error(coder, tool_name, e) + return handle_tool_error(coder, tool_name, e) \ No newline at end of file diff --git a/aider/tools/delete_line.py b/aider/tools/delete_line.py index e3b470ed2..1e3f1d38a 100644 --- a/aider/tools/delete_line.py +++ b/aider/tools/delete_line.py @@ -1,5 +1,6 @@ import os import traceback +from .tool_utils import generate_unified_diff_snippet def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=False): """ @@ -62,7 +63,7 @@ def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run= return f"Warning: No changes made (deleting line {line_num_int} would not change file)" # Generate diff snippet (using the existing delete block helper for simplicity) - diff_snippet = coder._generate_diff_snippet_delete(original_content, line_idx, line_idx) + diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path) # Handle dry run if dry_run: @@ -97,4 +98,4 @@ def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run= except Exception as e: coder.io.tool_error(f"Error in DeleteLine: {str(e)}\n{traceback.format_exc()}") - return f"Error: {str(e)}" + return f"Error: {str(e)}" \ No newline at end of file diff --git a/aider/tools/delete_lines.py b/aider/tools/delete_lines.py index c24e123ad..bb49690ff 100644 --- a/aider/tools/delete_lines.py +++ b/aider/tools/delete_lines.py @@ -1,5 +1,6 @@ import os import traceback +from .tool_utils import generate_unified_diff_snippet def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None, dry_run=False): """ @@ -71,7 +72,7 @@ def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None return f"Warning: No changes made (deleting lines {start_line_int}-{end_line_int} would not change file)" # Generate diff snippet - diff_snippet = coder._generate_diff_snippet_delete(original_content, start_idx, end_idx) + diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path) # Handle dry run if dry_run: @@ -108,4 +109,4 @@ def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None except Exception as e: coder.io.tool_error(f"Error in DeleteLines: {str(e)}\n{traceback.format_exc()}") - return f"Error: {str(e)}" + return f"Error: {str(e)}" \ No newline at end of file diff --git a/aider/tools/extract_lines.py b/aider/tools/extract_lines.py index b4577beb7..a9a318e27 100644 --- a/aider/tools/extract_lines.py +++ b/aider/tools/extract_lines.py @@ -1,5 +1,6 @@ import os import traceback +from .tool_utils import generate_unified_diff_snippet def _execute_extract_lines(coder, source_file_path, target_file_path, start_pattern, end_pattern=None, line_count=None, near_context=None, occurrence=1, dry_run=False): """ @@ -145,9 +146,9 @@ def _execute_extract_lines(coder, source_file_path, target_file_path, start_patt new_target_content = target_content + extracted_block # --- Generate Diffs --- - source_diff_snippet = coder._generate_diff_snippet_delete(original_source_content, start_line, end_line) + source_diff_snippet = generate_unified_diff_snippet(original_source_content, new_source_content, rel_source_path) target_insertion_line = len(target_content.splitlines()) if target_content else 0 - target_diff_snippet = coder._generate_diff_snippet_insert(original_target_content, target_insertion_line, extracted_lines) + target_diff_snippet = generate_unified_diff_snippet(original_target_content, new_target_content, rel_target_path) # --- Handle Dry Run --- if dry_run: @@ -217,4 +218,4 @@ def _execute_extract_lines(coder, source_file_path, target_file_path, start_patt except Exception as e: coder.io.tool_error(f"Error in ExtractLines: {str(e)}\n{traceback.format_exc()}") - return f"Error: {str(e)}" + return f"Error: {str(e)}" \ No newline at end of file diff --git a/aider/tools/indent_lines.py b/aider/tools/indent_lines.py index 708da3de0..4ac823fcc 100644 --- a/aider/tools/indent_lines.py +++ b/aider/tools/indent_lines.py @@ -9,6 +9,7 @@ from .tool_utils import ( apply_change, handle_tool_error, format_tool_result, + generate_unified_diff_snippet, ) def _execute_indent_lines(coder, file_path, start_pattern, end_pattern=None, line_count=None, indent_levels=1, near_context=None, occurrence=1, change_id=None, dry_run=False): @@ -81,7 +82,7 @@ def _execute_indent_lines(coder, file_path, start_pattern, end_pattern=None, lin return f"Warning: No changes made (indentation would not change file)" # 5. Generate diff for feedback - diff_snippet = coder._generate_diff_snippet_indent(original_content, new_content, start_line, end_line) + diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path) num_occurrences = len(start_pattern_indices) occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else "" action = "indent" if indent_levels > 0 else "unindent" diff --git a/aider/tools/insert_block.py b/aider/tools/insert_block.py index 26e83fed2..75443e987 100644 --- a/aider/tools/insert_block.py +++ b/aider/tools/insert_block.py @@ -8,6 +8,7 @@ from .tool_utils import ( apply_change, handle_tool_error, format_tool_result, + generate_unified_diff_snippet, ) def _execute_insert_block(coder, file_path, content, after_pattern=None, before_pattern=None, near_context=None, occurrence=1, change_id=None, dry_run=False): @@ -51,7 +52,7 @@ def _execute_insert_block(coder, file_path, content, after_pattern=None, before_ return f"Warning: No changes made (insertion would not change file)" # 5. Generate diff for feedback - diff_snippet = coder._generate_diff_snippet_insert(original_content, insertion_line_idx, content_lines) + diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path) num_occurrences = len(pattern_line_indices) occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else "" diff --git a/aider/tools/replace_lines.py b/aider/tools/replace_lines.py index f6b641e7b..53e0e607c 100644 --- a/aider/tools/replace_lines.py +++ b/aider/tools/replace_lines.py @@ -1,5 +1,7 @@ import os import traceback +from .tool_utils import generate_unified_diff_snippet +from .tool_utils import generate_unified_diff_snippet def _execute_replace_lines(coder, file_path, start_line, end_line, new_content, change_id=None, dry_run=False): """ @@ -7,7 +9,6 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content, Useful for fixing errors identified by error messages or linters. Parameters: - - coder: The Coder instance - file_path: Path to the file to modify - start_line: The first line number to replace (1-based) - end_line: The last line number to replace (1-based) @@ -86,6 +87,7 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content, if original_content == new_content_full: coder.io.tool_warning("No changes made: new content is identical to original") return f"Warning: No changes made (new content identical to original)" + diff_snippet = generate_unified_diff_snippet(original_content, new_content_full, rel_path) # Create a readable diff for the lines replacement diff = f"Lines {start_line}-{end_line}:\n" @@ -101,7 +103,7 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content, # Handle dry run if dry_run: coder.io.tool_output(f"Dry run: Would replace lines {start_line}-{end_line} in {file_path}") - return f"Dry run: Would replace lines {start_line}-{end_line}. Diff:\n{diff}" + return f"Dry run: Would replace lines {start_line}-{end_line}. Diff snippet:\n{diff_snippet}" # --- Apply Change (Not dry run) --- coder.io.write_text(abs_path, new_content_full) @@ -136,4 +138,4 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content, except Exception as e: coder.io.tool_error(f"Error in ReplaceLines: {str(e)}\n{traceback.format_exc()}") - return f"Error: {str(e)}" + return f"Error: {str(e)}" \ No newline at end of file diff --git a/aider/tools/replace_text.py b/aider/tools/replace_text.py index 9e1a3dfcc..b6409d40a 100644 --- a/aider/tools/replace_text.py +++ b/aider/tools/replace_text.py @@ -5,6 +5,7 @@ from .tool_utils import ( apply_change, handle_tool_error, format_tool_result, + generate_unified_diff_snippet, ) def _execute_replace_text(coder, file_path, find_text, replace_text, near_context=None, occurrence=1, change_id=None, dry_run=False): @@ -57,7 +58,7 @@ def _execute_replace_text(coder, file_path, find_text, replace_text, near_contex # 5. Generate diff for feedback # Note: _generate_diff_snippet is currently on the Coder class - diff_snippet = coder._generate_diff_snippet(original_content, start_index, len(find_text), replace_text) + diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path) occurrence_str = f"occurrence {occurrence}" if num_occurrences > 1 else "text" # 6. Handle dry run @@ -88,4 +89,4 @@ def _execute_replace_text(coder, file_path, find_text, replace_text, near_contex return handle_tool_error(coder, tool_name, e, add_traceback=False) except Exception as e: # Handle unexpected errors - return handle_tool_error(coder, tool_name, e) + return handle_tool_error(coder, tool_name, e) \ No newline at end of file diff --git a/aider/tools/tool_utils.py b/aider/tools/tool_utils.py index 401f2b594..6492b12ab 100644 --- a/aider/tools/tool_utils.py +++ b/aider/tools/tool_utils.py @@ -1,3 +1,4 @@ +import difflib import os import traceback @@ -170,6 +171,42 @@ def determine_line_range( return start_line, end_line +def generate_unified_diff_snippet(original_content, new_content, file_path, context_lines=3): + """ + Generates a unified diff snippet between original and new content. + + Args: + original_content (str): The original file content. + new_content (str): The modified file content. + file_path (str): The relative path to the file (for display in diff header). + context_lines (int): Number of context lines to show around changes. + + Returns: + str: A formatted unified diff snippet, or an empty string if no changes. + """ + if original_content == new_content: + return "" + + original_lines = original_content.splitlines(keepends=True) + new_lines = new_content.splitlines(keepends=True) + + diff = difflib.unified_diff( + original_lines, + new_lines, + fromfile=f"a/{file_path}", + tofile=f"b/{file_path}", + n=context_lines, # Number of context lines + ) + + # Join the diff lines, potentially skipping the header if desired, + # but let's keep it for standard format. + diff_snippet = "".join(diff) + + # Ensure snippet ends with a newline for cleaner formatting in results + if diff_snippet and not diff_snippet.endswith('\n'): + diff_snippet += '\n' + + return diff_snippet def apply_change(coder, abs_path, rel_path, original_content, new_content, change_type, metadata, change_id=None): """ Writes the new content, tracks the change, and updates coder state. @@ -237,4 +274,4 @@ def format_tool_result(coder, tool_name, success_message, change_id=None, diff_s # except ToolError as e: # return handle_tool_error(coder, "MyTool", e, add_traceback=False) # Don't need traceback for ToolErrors # except Exception as e: -# return handle_tool_error(coder, "MyTool", e) \ No newline at end of file +# return handle_tool_error(coder, "MyTool", e)