mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-08 21:55:00 +00:00
Add DeleteLine{,s} tools
This commit is contained in:
parent
17964f476c
commit
ee94452ffe
5 changed files with 289 additions and 3 deletions
|
@ -51,6 +51,8 @@ from aider.tools.delete_block import _execute_delete_block
|
|||
from aider.tools.replace_line import _execute_replace_line
|
||||
from aider.tools.replace_lines import _execute_replace_lines
|
||||
from aider.tools.indent_lines import _execute_indent_lines
|
||||
from aider.tools.delete_line import _execute_delete_line # New
|
||||
from aider.tools.delete_lines import _execute_delete_lines # New
|
||||
from aider.tools.undo_change import _execute_undo_change
|
||||
from aider.tools.list_changes import _execute_list_changes
|
||||
from aider.tools.extract_lines import _execute_extract_lines
|
||||
|
@ -851,7 +853,34 @@ class NavigatorCoder(Coder):
|
|||
)
|
||||
else:
|
||||
result_message = "Error: Missing required parameters for IndentLines (file_path, start_pattern)"
|
||||
|
||||
|
||||
elif norm_tool_name == 'deleteline':
|
||||
file_path = params.get('file_path')
|
||||
line_number = params.get('line_number')
|
||||
change_id = params.get('change_id')
|
||||
dry_run = params.get('dry_run', False)
|
||||
|
||||
if file_path is not None and line_number is not None:
|
||||
result_message = _execute_delete_line(
|
||||
self, file_path, line_number, change_id, dry_run
|
||||
)
|
||||
else:
|
||||
result_message = "Error: Missing required parameters for DeleteLine (file_path, line_number)"
|
||||
|
||||
elif norm_tool_name == 'deletelines':
|
||||
file_path = params.get('file_path')
|
||||
start_line = params.get('start_line')
|
||||
end_line = params.get('end_line')
|
||||
change_id = params.get('change_id')
|
||||
dry_run = params.get('dry_run', False)
|
||||
|
||||
if file_path is not None and start_line is not None and end_line is not None:
|
||||
result_message = _execute_delete_lines(
|
||||
self, file_path, start_line, end_line, change_id, dry_run
|
||||
)
|
||||
else:
|
||||
result_message = "Error: Missing required parameters for DeleteLines (file_path, start_line, end_line)"
|
||||
|
||||
elif norm_tool_name == 'undochange':
|
||||
change_id = params.get('change_id')
|
||||
file_path = params.get('file_path')
|
||||
|
|
|
@ -94,10 +94,18 @@ Act as an expert software engineer with the ability to autonomously navigate and
|
|||
- **IndentLines**: `[tool_call(IndentLines, file_path="...", start_pattern="...", end_pattern="...", indent_levels=1, near_context="...", occurrence=1, dry_run=False)]`
|
||||
Indent (`indent_levels` > 0) or unindent (`indent_levels` < 0) a block. Use `end_pattern` or `line_count` for range. Use `near_context` and `occurrence` (optional, default 1, -1 for last) for `start_pattern`. `dry_run=True` simulates.
|
||||
*Useful for fixing indentation errors reported by linters or reformatting code blocks. Also helpful for adjusting indentation after moving code with `ExtractLines`.*
|
||||
|
||||
|
||||
- **DeleteLine**: `[tool_call(DeleteLine, file_path="...", line_number=42, dry_run=False)]`
|
||||
Delete a specific line number (1-based). `dry_run=True` simulates.
|
||||
*Useful for removing single erroneous lines identified by linters or exact line number.*
|
||||
|
||||
- **DeleteLines**: `[tool_call(DeleteLines, file_path="...", start_line=42, end_line=45, dry_run=False)]`
|
||||
Delete a range of lines (1-based, inclusive). `dry_run=True` simulates.
|
||||
*Useful for removing multi-line blocks when exact line numbers are known.*
|
||||
|
||||
- **UndoChange**: `[tool_call(UndoChange, change_id="a1b2c3d4")]` or `[tool_call(UndoChange, file_path="...")]`
|
||||
Undo a specific change by ID, or the last change made to the specified `file_path`.
|
||||
|
||||
|
||||
- **ListChanges**: `[tool_call(ListChanges, file_path="...", limit=5)]`
|
||||
List recent changes, optionally filtered by `file_path` and limited.
|
||||
|
||||
|
@ -222,6 +230,7 @@ SEARCH/REPLACE blocks can appear anywhere in your response if needed.
|
|||
- `InsertBlock`: For adding code blocks.
|
||||
- `DeleteBlock`: For removing code sections.
|
||||
- `ReplaceLine`/`ReplaceLines`: For line-specific fixes (requires strict `ShowNumberedContext` verification).
|
||||
- `DeleteLine`/`DeleteLines`: For removing lines by number (requires strict `ShowNumberedContext` verification).
|
||||
- `IndentLines`: For adjusting indentation.
|
||||
- `ExtractLines`: For moving code between files.
|
||||
- `UndoChange`: For reverting specific edits.
|
||||
|
@ -239,6 +248,8 @@ Warning in /path/to/file.py lines 105-107: This block should be indented
|
|||
For these cases, use:
|
||||
- `ReplaceLine` for single line fixes (e.g., syntax errors)
|
||||
- `ReplaceLines` for multi-line issues
|
||||
- `DeleteLine` for removing single erroneous lines
|
||||
- `DeleteLines` for removing multi-line blocks by number
|
||||
- `IndentLines` for indentation problems
|
||||
|
||||
#### Multiline Tool Call Content Format
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# flake8: noqa: F401
|
||||
# Import tool functions into the aider.tools namespace
|
||||
|
||||
# Discovery
|
||||
from .ls import execute_ls
|
||||
from .view_files_at_glob import execute_view_files_at_glob
|
||||
from .view_files_matching import execute_view_files_matching
|
||||
from .view_files_with_symbol import _execute_view_files_with_symbol
|
||||
|
||||
# Context Management
|
||||
from .view import execute_view
|
||||
from .remove import _execute_remove
|
||||
from .make_editable import _execute_make_editable
|
||||
from .make_readonly import _execute_make_readonly
|
||||
from .show_numbered_context import execute_show_numbered_context
|
||||
|
||||
# Granular Editing
|
||||
from .replace_text import _execute_replace_text
|
||||
from .replace_all import _execute_replace_all
|
||||
from .insert_block import _execute_insert_block
|
||||
from .delete_block import _execute_delete_block
|
||||
from .replace_line import _execute_replace_line
|
||||
from .replace_lines import _execute_replace_lines
|
||||
from .indent_lines import _execute_indent_lines
|
||||
from .extract_lines import _execute_extract_lines
|
||||
from .delete_line import _execute_delete_line
|
||||
from .delete_lines import _execute_delete_lines
|
||||
|
||||
# Change Tracking
|
||||
from .undo_change import _execute_undo_change
|
||||
from .list_changes import _execute_list_changes
|
||||
|
||||
# Other
|
||||
from .command import _execute_command
|
||||
from .command_interactive import _execute_command_interactive
|
100
aider/tools/delete_line.py
Normal file
100
aider/tools/delete_line.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
import os
|
||||
import traceback
|
||||
|
||||
def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=False):
|
||||
"""
|
||||
Delete a specific line number (1-based).
|
||||
|
||||
Parameters:
|
||||
- coder: The Coder instance
|
||||
- file_path: Path to the file to modify
|
||||
- line_number: The 1-based line number to delete
|
||||
- change_id: Optional ID for tracking the change
|
||||
- dry_run: If True, simulate the change without modifying the file
|
||||
|
||||
Returns a result message.
|
||||
"""
|
||||
try:
|
||||
# Get absolute file path
|
||||
abs_path = coder.abs_root_path(file_path)
|
||||
rel_path = coder.get_rel_fname(abs_path)
|
||||
|
||||
# Check if file exists
|
||||
if not os.path.isfile(abs_path):
|
||||
coder.io.tool_error(f"File '{file_path}' not found")
|
||||
return f"Error: File not found"
|
||||
|
||||
# Check if file is in editable context
|
||||
if abs_path not in coder.abs_fnames:
|
||||
if abs_path in coder.abs_read_only_fnames:
|
||||
coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
return f"Error: File is read-only. Use MakeEditable first."
|
||||
else:
|
||||
coder.io.tool_error(f"File '{file_path}' not in context")
|
||||
return f"Error: File not in context"
|
||||
|
||||
# Reread file content immediately before modification
|
||||
file_content = coder.io.read_text(abs_path)
|
||||
if file_content is None:
|
||||
coder.io.tool_error(f"Could not read file '{file_path}' before DeleteLine operation.")
|
||||
return f"Error: Could not read file '{file_path}'"
|
||||
|
||||
lines = file_content.splitlines()
|
||||
original_content = file_content
|
||||
|
||||
# Validate line number
|
||||
try:
|
||||
line_num_int = int(line_number)
|
||||
if line_num_int < 1 or line_num_int > len(lines):
|
||||
raise ValueError(f"Line number {line_num_int} is out of range (1-{len(lines)})")
|
||||
line_idx = line_num_int - 1 # Convert to 0-based index
|
||||
except ValueError as e:
|
||||
coder.io.tool_error(f"Invalid line_number: {e}")
|
||||
return f"Error: Invalid line_number '{line_number}'"
|
||||
|
||||
# Prepare the deletion
|
||||
deleted_line = lines[line_idx]
|
||||
new_lines = lines[:line_idx] + lines[line_idx+1:]
|
||||
new_content = '\n'.join(new_lines)
|
||||
|
||||
if original_content == new_content:
|
||||
coder.io.tool_warning(f"No changes made: deleting line {line_num_int} would not change file")
|
||||
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)
|
||||
|
||||
# Handle dry run
|
||||
if dry_run:
|
||||
coder.io.tool_output(f"Dry run: Would delete line {line_num_int} in {file_path}")
|
||||
return f"Dry run: Would delete line {line_num_int}. Diff snippet:\n{diff_snippet}"
|
||||
|
||||
# --- Apply Change (Not dry run) ---
|
||||
coder.io.write_text(abs_path, new_content)
|
||||
|
||||
# Track the change
|
||||
try:
|
||||
metadata = {
|
||||
'line_number': line_num_int,
|
||||
'deleted_content': deleted_line
|
||||
}
|
||||
change_id = coder.change_tracker.track_change(
|
||||
file_path=rel_path,
|
||||
change_type='deleteline',
|
||||
original_content=original_content,
|
||||
new_content=new_content,
|
||||
metadata=metadata,
|
||||
change_id=change_id
|
||||
)
|
||||
except Exception as track_e:
|
||||
coder.io.tool_error(f"Error tracking change for DeleteLine: {track_e}")
|
||||
change_id = "TRACKING_FAILED"
|
||||
|
||||
coder.aider_edited_files.add(rel_path)
|
||||
|
||||
coder.io.tool_output(f"✅ Deleted line {line_num_int} in {file_path} (change_id: {change_id})")
|
||||
return f"Successfully deleted line {line_num_int} (change_id: {change_id}). Diff snippet:\n{diff_snippet}"
|
||||
|
||||
except Exception as e:
|
||||
coder.io.tool_error(f"Error in DeleteLine: {str(e)}\n{traceback.format_exc()}")
|
||||
return f"Error: {str(e)}"
|
111
aider/tools/delete_lines.py
Normal file
111
aider/tools/delete_lines.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import os
|
||||
import traceback
|
||||
|
||||
def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None, dry_run=False):
|
||||
"""
|
||||
Delete a range of lines (1-based, inclusive).
|
||||
|
||||
Parameters:
|
||||
- coder: The Coder instance
|
||||
- file_path: Path to the file to modify
|
||||
- start_line: The 1-based starting line number to delete
|
||||
- end_line: The 1-based ending line number to delete
|
||||
- change_id: Optional ID for tracking the change
|
||||
- dry_run: If True, simulate the change without modifying the file
|
||||
|
||||
Returns a result message.
|
||||
"""
|
||||
try:
|
||||
# Get absolute file path
|
||||
abs_path = coder.abs_root_path(file_path)
|
||||
rel_path = coder.get_rel_fname(abs_path)
|
||||
|
||||
# Check if file exists
|
||||
if not os.path.isfile(abs_path):
|
||||
coder.io.tool_error(f"File '{file_path}' not found")
|
||||
return f"Error: File not found"
|
||||
|
||||
# Check if file is in editable context
|
||||
if abs_path not in coder.abs_fnames:
|
||||
if abs_path in coder.abs_read_only_fnames:
|
||||
coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
return f"Error: File is read-only. Use MakeEditable first."
|
||||
else:
|
||||
coder.io.tool_error(f"File '{file_path}' not in context")
|
||||
return f"Error: File not in context"
|
||||
|
||||
# Reread file content immediately before modification
|
||||
file_content = coder.io.read_text(abs_path)
|
||||
if file_content is None:
|
||||
coder.io.tool_error(f"Could not read file '{file_path}' before DeleteLines operation.")
|
||||
return f"Error: Could not read file '{file_path}'"
|
||||
|
||||
lines = file_content.splitlines()
|
||||
original_content = file_content
|
||||
|
||||
# Validate line numbers
|
||||
try:
|
||||
start_line_int = int(start_line)
|
||||
end_line_int = int(end_line)
|
||||
|
||||
if start_line_int < 1 or start_line_int > len(lines):
|
||||
raise ValueError(f"Start line {start_line_int} is out of range (1-{len(lines)})")
|
||||
if end_line_int < 1 or end_line_int > len(lines):
|
||||
raise ValueError(f"End line {end_line_int} is out of range (1-{len(lines)})")
|
||||
if start_line_int > end_line_int:
|
||||
raise ValueError(f"Start line {start_line_int} cannot be after end line {end_line_int}")
|
||||
|
||||
start_idx = start_line_int - 1 # Convert to 0-based index
|
||||
end_idx = end_line_int - 1 # Convert to 0-based index
|
||||
except ValueError as e:
|
||||
coder.io.tool_error(f"Invalid line numbers: {e}")
|
||||
return f"Error: Invalid line numbers '{start_line}', '{end_line}'"
|
||||
|
||||
# Prepare the deletion
|
||||
deleted_lines = lines[start_idx:end_idx+1]
|
||||
new_lines = lines[:start_idx] + lines[end_idx+1:]
|
||||
new_content = '\n'.join(new_lines)
|
||||
|
||||
if original_content == new_content:
|
||||
coder.io.tool_warning(f"No changes made: deleting lines {start_line_int}-{end_line_int} would not change file")
|
||||
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)
|
||||
|
||||
# Handle dry run
|
||||
if dry_run:
|
||||
coder.io.tool_output(f"Dry run: Would delete lines {start_line_int}-{end_line_int} in {file_path}")
|
||||
return f"Dry run: Would delete lines {start_line_int}-{end_line_int}. Diff snippet:\n{diff_snippet}"
|
||||
|
||||
# --- Apply Change (Not dry run) ---
|
||||
coder.io.write_text(abs_path, new_content)
|
||||
|
||||
# Track the change
|
||||
try:
|
||||
metadata = {
|
||||
'start_line': start_line_int,
|
||||
'end_line': end_line_int,
|
||||
'deleted_content': '\n'.join(deleted_lines)
|
||||
}
|
||||
change_id = coder.change_tracker.track_change(
|
||||
file_path=rel_path,
|
||||
change_type='deletelines',
|
||||
original_content=original_content,
|
||||
new_content=new_content,
|
||||
metadata=metadata,
|
||||
change_id=change_id
|
||||
)
|
||||
except Exception as track_e:
|
||||
coder.io.tool_error(f"Error tracking change for DeleteLines: {track_e}")
|
||||
change_id = "TRACKING_FAILED"
|
||||
|
||||
coder.aider_edited_files.add(rel_path)
|
||||
|
||||
num_deleted = end_idx - start_idx + 1
|
||||
coder.io.tool_output(f"✅ Deleted {num_deleted} lines ({start_line_int}-{end_line_int}) in {file_path} (change_id: {change_id})")
|
||||
return f"Successfully deleted {num_deleted} lines ({start_line_int}-{end_line_int}) (change_id: {change_id}). Diff snippet:\n{diff_snippet}"
|
||||
|
||||
except Exception as e:
|
||||
coder.io.tool_error(f"Error in DeleteLines: {str(e)}\n{traceback.format_exc()}")
|
||||
return f"Error: {str(e)}"
|
Loading…
Add table
Add a link
Reference in a new issue