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.
|
||||
*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)]`
|
||||
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.
|
||||
- **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 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.*
|
||||
|
||||
- **DeleteBlock**: `[tool_call(DeleteBlock, file_path="...", start_pattern="...", end_pattern="...", near_context="...", occurrence=1, dry_run=False)]`
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import traceback
|
||||
from .tool_utils import (
|
||||
ToolError,
|
||||
|
@ -11,39 +12,108 @@ from .tool_utils import (
|
|||
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.
|
||||
|
||||
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"
|
||||
try:
|
||||
# 1. Validate parameters
|
||||
if after_pattern and before_pattern:
|
||||
raise ToolError("Cannot specify both after_pattern and before_pattern")
|
||||
if not after_pattern and not before_pattern:
|
||||
raise ToolError("Must specify either after_pattern or before_pattern")
|
||||
if sum(x is not None for x in [after_pattern, before_pattern, position]) != 1:
|
||||
raise ToolError("Must specify exactly one of: after_pattern, before_pattern, or position")
|
||||
|
||||
# 2. Validate file and get content
|
||||
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
||||
lines = original_content.splitlines()
|
||||
|
||||
# 3. Find the target line index
|
||||
# Handle empty files
|
||||
if not lines:
|
||||
lines = [""]
|
||||
|
||||
# 3. Determine insertion point
|
||||
insertion_line_idx = 0
|
||||
pattern_type = ""
|
||||
pattern_desc = ""
|
||||
occurrence_str = ""
|
||||
|
||||
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}'"
|
||||
if near_context:
|
||||
pattern_desc += f" near context '{near_context}'"
|
||||
|
||||
pattern_line_indices = find_pattern_indices(lines, pattern, near_context)
|
||||
# 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 the final insertion line index
|
||||
# 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
|
||||
|
||||
# 4. Prepare the insertion
|
||||
# Format occurrence info for output
|
||||
num_occurrences = len(pattern_line_indices)
|
||||
occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
|
||||
|
||||
# 4. Handle indentation if requested
|
||||
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_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")
|
||||
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)
|
||||
num_occurrences = len(pattern_line_indices)
|
||||
occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
|
||||
|
||||
# 6. Handle dry run
|
||||
# 7. Handle dry run
|
||||
if dry_run:
|
||||
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)
|
||||
|
||||
# 7. Apply Change (Not dry run)
|
||||
# 8. Apply Change (Not dry run)
|
||||
metadata = {
|
||||
'insertion_line_idx': insertion_line_idx,
|
||||
'after_pattern': after_pattern,
|
||||
'before_pattern': before_pattern,
|
||||
'near_context': near_context,
|
||||
'position': position,
|
||||
'occurrence': occurrence,
|
||||
'content': content
|
||||
'content': content,
|
||||
'auto_indent': auto_indent,
|
||||
'use_regex': use_regex
|
||||
}
|
||||
final_change_id = apply_change(
|
||||
coder, abs_path, rel_path, original_content, new_content, 'insertblock', metadata, change_id
|
||||
)
|
||||
|
||||
# 8. Format and return result
|
||||
# 9. Format and return result
|
||||
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(
|
||||
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
||||
)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import difflib
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
class ToolError(Exception):
|
||||
|
@ -46,19 +47,11 @@ def validate_file_for_edit(coder, file_path):
|
|||
|
||||
return abs_path, rel_path, content
|
||||
|
||||
def find_pattern_indices(lines, pattern, near_context=None):
|
||||
"""Finds all line indices matching a pattern, optionally filtered by context."""
|
||||
def find_pattern_indices(lines, pattern, use_regex=False):
|
||||
"""Finds all line indices matching a pattern."""
|
||||
indices = []
|
||||
for i, line in enumerate(lines):
|
||||
if pattern in line:
|
||||
if near_context:
|
||||
# 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:
|
||||
if (use_regex and re.search(pattern, line)) or (not use_regex and pattern in line):
|
||||
indices.append(i)
|
||||
return indices
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue