From 930880151ef5be211c67fc6806093966c0dc136c Mon Sep 17 00:00:00 2001 From: "Amar Sood (tekacs)" Date: Sun, 13 Apr 2025 15:42:09 -0400 Subject: [PATCH] Try to improve InsertBlock --- aider/coders/navigator_prompts.py | 12 ++- aider/tools/insert_block.py | 141 +++++++++++++++++++++++------- aider/tools/tool_utils.py | 17 ++-- 3 files changed, 124 insertions(+), 46 deletions(-) diff --git a/aider/coders/navigator_prompts.py b/aider/coders/navigator_prompts.py index 335fd3ec6..52f87324a 100644 --- a/aider/coders/navigator_prompts.py +++ b/aider/coders/navigator_prompts.py @@ -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)]` diff --git a/aider/tools/insert_block.py b/aider/tools/insert_block.py index 75443e987..85e24dd4e 100644 --- a/aider/tools/insert_block.py +++ b/aider/tools/insert_block.py @@ -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() + + # Handle empty files + if not lines: + lines = [""] - # 3. Find the target line index - 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}'" + # 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}'" + + # 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) - 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 + # 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: - 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) - # 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 - success_message = f"Inserted block {pattern_type} {occurrence_str}pattern in {file_path} at line {insertion_line_idx + 1}" + # 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 ) @@ -86,4 +163,4 @@ def _execute_insert_block(coder, file_path, content, after_pattern=None, before_ except Exception as e: coder.io.tool_error(f"Error in InsertBlock: {str(e)}\n{traceback.format_exc()}") # Add traceback - return f"Error: {str(e)}" \ No newline at end of file + return f"Error: {str(e)}" diff --git a/aider/tools/tool_utils.py b/aider/tools/tool_utils.py index 19ff1b4f1..8c43ca5bb 100644 --- a/aider/tools/tool_utils.py +++ b/aider/tools/tool_utils.py @@ -1,5 +1,6 @@ import difflib import os +import re import traceback class ToolError(Exception): @@ -46,20 +47,12 @@ 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: - indices.append(i) + if (use_regex and re.search(pattern, line)) or (not use_regex and pattern in line): + indices.append(i) return indices def select_occurrence_index(indices, occurrence, pattern_desc="Pattern"):