mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-08 13:44:59 +00:00
Try to improve InsertBlock
This commit is contained in:
parent
b36aaa6f62
commit
930880151e
3 changed files with 124 additions and 46 deletions
|
@ -84,8 +84,16 @@ Act as an expert software engineer with the ability to autonomously navigate and
|
||||||
Replace ALL occurrences of text. Use with caution. `dry_run=True` simulates the change.
|
Replace ALL occurrences of text. Use with caution. `dry_run=True` simulates the change.
|
||||||
*Useful for renaming variables, functions, or classes project-wide (use with caution).*
|
*Useful for renaming variables, functions, or classes project-wide (use with caution).*
|
||||||
|
|
||||||
- **InsertBlock**: `[tool_call(InsertBlock, file_path="...", content="...", after_pattern="...", near_context="...", occurrence=1, dry_run=False)]`
|
- **InsertBlock**: `[tool_call(InsertBlock, file_path="...", content="...", after_pattern="...", before_pattern="...", position="start_of_file", occurrence=1, auto_indent=True, dry_run=False)]`
|
||||||
Insert a block after (`after_pattern`) or before (`before_pattern`) a pattern line. Use `near_context` and `occurrence` (optional, default 1, -1 for last) to specify which pattern match. `dry_run=True` simulates.
|
Insert a block of code or text. Specify *exactly one* location:
|
||||||
|
- `after_pattern`: Insert after lines matching this pattern (use multi-line patterns for uniqueness)
|
||||||
|
- `before_pattern`: Insert before lines matching this pattern (use multi-line patterns for uniqueness)
|
||||||
|
- `position`: Use "start_of_file" or "end_of_file"
|
||||||
|
|
||||||
|
Optional parameters:
|
||||||
|
- `occurrence`: Which match to use (1-based indexing: 1 for first match, 2 for second, -1 for last match)
|
||||||
|
- `auto_indent`: Automatically adjust indentation to match surrounding code (default True)
|
||||||
|
- `dry_run`: Simulate the change without applying it (default False)
|
||||||
*Useful for adding new functions, methods, or blocks of configuration.*
|
*Useful for adding new functions, methods, or blocks of configuration.*
|
||||||
|
|
||||||
- **DeleteBlock**: `[tool_call(DeleteBlock, file_path="...", start_pattern="...", end_pattern="...", near_context="...", occurrence=1, dry_run=False)]`
|
- **DeleteBlock**: `[tool_call(DeleteBlock, file_path="...", start_pattern="...", end_pattern="...", near_context="...", occurrence=1, dry_run=False)]`
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from .tool_utils import (
|
from .tool_utils import (
|
||||||
ToolError,
|
ToolError,
|
||||||
|
@ -11,39 +12,108 @@ from .tool_utils import (
|
||||||
generate_unified_diff_snippet,
|
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):
|
def _execute_insert_block(coder, file_path, content, after_pattern=None, before_pattern=None,
|
||||||
|
occurrence=1, change_id=None, dry_run=False,
|
||||||
|
position=None, auto_indent=True, use_regex=False):
|
||||||
"""
|
"""
|
||||||
Insert a block of text after or before a specified pattern using utility functions.
|
Insert a block of text after or before a specified pattern using utility functions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
coder: The coder instance
|
||||||
|
file_path: Path to the file to modify
|
||||||
|
content: The content to insert
|
||||||
|
after_pattern: Pattern to insert after (mutually exclusive with before_pattern and position)
|
||||||
|
before_pattern: Pattern to insert before (mutually exclusive with after_pattern and position)
|
||||||
|
occurrence: Which occurrence of the pattern to use (1-based, or -1 for last)
|
||||||
|
change_id: Optional ID for tracking changes
|
||||||
|
dry_run: If True, only simulate the change
|
||||||
|
position: Special position like "start_of_file" or "end_of_file"
|
||||||
|
auto_indent: If True, automatically adjust indentation of inserted content
|
||||||
|
use_regex: If True, treat patterns as regular expressions
|
||||||
"""
|
"""
|
||||||
tool_name = "InsertBlock"
|
tool_name = "InsertBlock"
|
||||||
try:
|
try:
|
||||||
# 1. Validate parameters
|
# 1. Validate parameters
|
||||||
if after_pattern and before_pattern:
|
if sum(x is not None for x in [after_pattern, before_pattern, position]) != 1:
|
||||||
raise ToolError("Cannot specify both after_pattern and before_pattern")
|
raise ToolError("Must specify exactly one of: after_pattern, before_pattern, or position")
|
||||||
if not after_pattern and not before_pattern:
|
|
||||||
raise ToolError("Must specify either after_pattern or before_pattern")
|
|
||||||
|
|
||||||
# 2. Validate file and get content
|
# 2. Validate file and get content
|
||||||
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
||||||
lines = original_content.splitlines()
|
lines = original_content.splitlines()
|
||||||
|
|
||||||
|
# Handle empty files
|
||||||
|
if not lines:
|
||||||
|
lines = [""]
|
||||||
|
|
||||||
# 3. Find the target line index
|
# 3. Determine insertion point
|
||||||
pattern = after_pattern if after_pattern else before_pattern
|
insertion_line_idx = 0
|
||||||
pattern_type = "after" if after_pattern else "before"
|
pattern_type = ""
|
||||||
pattern_desc = f"Pattern '{pattern}'"
|
pattern_desc = ""
|
||||||
if near_context:
|
occurrence_str = ""
|
||||||
pattern_desc += f" near context '{near_context}'"
|
|
||||||
|
if position:
|
||||||
|
# Handle special positions
|
||||||
|
if position == "start_of_file":
|
||||||
|
insertion_line_idx = 0
|
||||||
|
pattern_type = "at start of"
|
||||||
|
elif position == "end_of_file":
|
||||||
|
insertion_line_idx = len(lines)
|
||||||
|
pattern_type = "at end of"
|
||||||
|
else:
|
||||||
|
raise ToolError(f"Invalid position: '{position}'. Valid values are 'start_of_file' or 'end_of_file'")
|
||||||
|
else:
|
||||||
|
# Handle pattern-based insertion
|
||||||
|
pattern = after_pattern if after_pattern else before_pattern
|
||||||
|
pattern_type = "after" if after_pattern else "before"
|
||||||
|
pattern_desc = f"Pattern '{pattern}'"
|
||||||
|
|
||||||
|
# Find pattern matches
|
||||||
|
pattern_line_indices = find_pattern_indices(lines, pattern,
|
||||||
|
use_regex=use_regex)
|
||||||
|
|
||||||
|
# Select the target occurrence
|
||||||
|
target_line_idx = select_occurrence_index(pattern_line_indices, occurrence, pattern_desc)
|
||||||
|
|
||||||
|
# Determine insertion point
|
||||||
|
insertion_line_idx = target_line_idx
|
||||||
|
if pattern_type == "after":
|
||||||
|
insertion_line_idx += 1 # Insert on the line *after* the matched line
|
||||||
|
|
||||||
|
# Format occurrence info for output
|
||||||
|
num_occurrences = len(pattern_line_indices)
|
||||||
|
occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
|
||||||
|
|
||||||
pattern_line_indices = find_pattern_indices(lines, pattern, near_context)
|
# 4. Handle indentation if requested
|
||||||
target_line_idx = select_occurrence_index(pattern_line_indices, occurrence, pattern_desc)
|
|
||||||
|
|
||||||
# Determine the final insertion line index
|
|
||||||
insertion_line_idx = target_line_idx
|
|
||||||
if pattern_type == "after":
|
|
||||||
insertion_line_idx += 1 # Insert on the line *after* the matched line
|
|
||||||
|
|
||||||
# 4. Prepare the insertion
|
|
||||||
content_lines = content.splitlines()
|
content_lines = content.splitlines()
|
||||||
|
|
||||||
|
if auto_indent and content_lines:
|
||||||
|
# Determine base indentation level
|
||||||
|
base_indent = ""
|
||||||
|
if insertion_line_idx > 0 and lines:
|
||||||
|
# Use indentation from the line before insertion point
|
||||||
|
reference_line_idx = min(insertion_line_idx - 1, len(lines) - 1)
|
||||||
|
reference_line = lines[reference_line_idx]
|
||||||
|
base_indent = re.match(r'^(\s*)', reference_line).group(1)
|
||||||
|
|
||||||
|
# Apply indentation to content lines, preserving relative indentation
|
||||||
|
if content_lines:
|
||||||
|
# Find minimum indentation in content to preserve relative indentation
|
||||||
|
content_indents = [len(re.match(r'^(\s*)', line).group(1)) for line in content_lines if line.strip()]
|
||||||
|
min_content_indent = min(content_indents) if content_indents else 0
|
||||||
|
|
||||||
|
# Apply base indentation while preserving relative indentation
|
||||||
|
indented_content_lines = []
|
||||||
|
for line in content_lines:
|
||||||
|
if not line.strip(): # Empty or whitespace-only line
|
||||||
|
indented_content_lines.append("")
|
||||||
|
else:
|
||||||
|
# Remove existing indentation and add new base indentation
|
||||||
|
stripped_line = line[min_content_indent:] if min_content_indent <= len(line) else line
|
||||||
|
indented_content_lines.append(base_indent + stripped_line)
|
||||||
|
|
||||||
|
content_lines = indented_content_lines
|
||||||
|
|
||||||
|
# 5. Prepare the insertion
|
||||||
new_lines = lines[:insertion_line_idx] + content_lines + lines[insertion_line_idx:]
|
new_lines = lines[:insertion_line_idx] + content_lines + lines[insertion_line_idx:]
|
||||||
new_content = '\n'.join(new_lines)
|
new_content = '\n'.join(new_lines)
|
||||||
|
|
||||||
|
@ -51,31 +121,38 @@ def _execute_insert_block(coder, file_path, content, after_pattern=None, before_
|
||||||
coder.io.tool_warning(f"No changes made: insertion would not change file")
|
coder.io.tool_warning(f"No changes made: insertion would not change file")
|
||||||
return f"Warning: No changes made (insertion would not change file)"
|
return f"Warning: No changes made (insertion would not change file)"
|
||||||
|
|
||||||
# 5. Generate diff for feedback
|
# 6. Generate diff for feedback
|
||||||
diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
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 ""
|
# 7. Handle dry run
|
||||||
|
|
||||||
# 6. Handle dry run
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
dry_run_message = f"Dry run: Would insert block {pattern_type} {occurrence_str}pattern '{pattern}' in {file_path} at line {insertion_line_idx + 1}."
|
if position:
|
||||||
|
dry_run_message = f"Dry run: Would insert block {pattern_type} {file_path}."
|
||||||
|
else:
|
||||||
|
dry_run_message = f"Dry run: Would insert block {pattern_type} {occurrence_str}pattern '{pattern}' in {file_path} at line {insertion_line_idx + 1}."
|
||||||
return format_tool_result(coder, tool_name, "", dry_run=True, dry_run_message=dry_run_message, diff_snippet=diff_snippet)
|
return format_tool_result(coder, tool_name, "", dry_run=True, dry_run_message=dry_run_message, diff_snippet=diff_snippet)
|
||||||
|
|
||||||
# 7. Apply Change (Not dry run)
|
# 8. Apply Change (Not dry run)
|
||||||
metadata = {
|
metadata = {
|
||||||
'insertion_line_idx': insertion_line_idx,
|
'insertion_line_idx': insertion_line_idx,
|
||||||
'after_pattern': after_pattern,
|
'after_pattern': after_pattern,
|
||||||
'before_pattern': before_pattern,
|
'before_pattern': before_pattern,
|
||||||
'near_context': near_context,
|
'position': position,
|
||||||
'occurrence': occurrence,
|
'occurrence': occurrence,
|
||||||
'content': content
|
'content': content,
|
||||||
|
'auto_indent': auto_indent,
|
||||||
|
'use_regex': use_regex
|
||||||
}
|
}
|
||||||
final_change_id = apply_change(
|
final_change_id = apply_change(
|
||||||
coder, abs_path, rel_path, original_content, new_content, 'insertblock', metadata, change_id
|
coder, abs_path, rel_path, original_content, new_content, 'insertblock', metadata, change_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# 8. Format and return result
|
# 9. Format and return result
|
||||||
success_message = f"Inserted block {pattern_type} {occurrence_str}pattern in {file_path} at line {insertion_line_idx + 1}"
|
if position:
|
||||||
|
success_message = f"Inserted block {pattern_type} {file_path}"
|
||||||
|
else:
|
||||||
|
success_message = f"Inserted block {pattern_type} {occurrence_str}pattern in {file_path} at line {insertion_line_idx + 1}"
|
||||||
|
|
||||||
return format_tool_result(
|
return format_tool_result(
|
||||||
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
||||||
)
|
)
|
||||||
|
@ -86,4 +163,4 @@ def _execute_insert_block(coder, file_path, content, after_pattern=None, before_
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
coder.io.tool_error(f"Error in InsertBlock: {str(e)}\n{traceback.format_exc()}") # Add traceback
|
coder.io.tool_error(f"Error in InsertBlock: {str(e)}\n{traceback.format_exc()}") # Add traceback
|
||||||
return f"Error: {str(e)}"
|
return f"Error: {str(e)}"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import difflib
|
import difflib
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
class ToolError(Exception):
|
class ToolError(Exception):
|
||||||
|
@ -46,20 +47,12 @@ def validate_file_for_edit(coder, file_path):
|
||||||
|
|
||||||
return abs_path, rel_path, content
|
return abs_path, rel_path, content
|
||||||
|
|
||||||
def find_pattern_indices(lines, pattern, near_context=None):
|
def find_pattern_indices(lines, pattern, use_regex=False):
|
||||||
"""Finds all line indices matching a pattern, optionally filtered by context."""
|
"""Finds all line indices matching a pattern."""
|
||||||
indices = []
|
indices = []
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
if pattern in line:
|
if (use_regex and re.search(pattern, line)) or (not use_regex and pattern in line):
|
||||||
if near_context:
|
indices.append(i)
|
||||||
# Check if near_context is within a window around the match
|
|
||||||
context_window_start = max(0, i - 5) # Check 5 lines before/after
|
|
||||||
context_window_end = min(len(lines), i + 6)
|
|
||||||
context_block = "\n".join(lines[context_window_start:context_window_end])
|
|
||||||
if near_context in context_block:
|
|
||||||
indices.append(i)
|
|
||||||
else:
|
|
||||||
indices.append(i)
|
|
||||||
return indices
|
return indices
|
||||||
|
|
||||||
def select_occurrence_index(indices, occurrence, pattern_desc="Pattern"):
|
def select_occurrence_index(indices, occurrence, pattern_desc="Pattern"):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue