mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-09 14:14:59 +00:00
Refactor the indent lines function to use tool_utils
This commit is contained in:
parent
c55d789c25
commit
7eac68e267
1 changed files with 73 additions and 147 deletions
|
@ -1,9 +1,19 @@
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
from .tool_utils import (
|
||||||
|
ToolError,
|
||||||
|
validate_file_for_edit,
|
||||||
|
find_pattern_indices,
|
||||||
|
select_occurrence_index,
|
||||||
|
determine_line_range,
|
||||||
|
apply_change,
|
||||||
|
handle_tool_error,
|
||||||
|
format_tool_result,
|
||||||
|
)
|
||||||
|
|
||||||
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):
|
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):
|
||||||
"""
|
"""
|
||||||
Indent or unindent a block of lines in a file.
|
Indent or unindent a block of lines in a file using utility functions.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- coder: The Coder instance
|
- coder: The Coder instance
|
||||||
|
@ -19,116 +29,34 @@ def _execute_indent_lines(coder, file_path, start_pattern, end_pattern=None, lin
|
||||||
|
|
||||||
Returns a result message.
|
Returns a result message.
|
||||||
"""
|
"""
|
||||||
|
tool_name = "IndentLines"
|
||||||
try:
|
try:
|
||||||
# Get absolute file path
|
# 1. Validate file and get content
|
||||||
abs_path = coder.abs_root_path(file_path)
|
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
||||||
rel_path = coder.get_rel_fname(abs_path)
|
lines = original_content.splitlines()
|
||||||
|
|
||||||
# Check if file exists
|
# 2. Find the start line
|
||||||
if not os.path.isfile(abs_path):
|
pattern_desc = f"Start pattern '{start_pattern}'"
|
||||||
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 IndentLines operation.")
|
|
||||||
return f"Error: Could not read file '{file_path}'"
|
|
||||||
|
|
||||||
# Validate we have either end_pattern or line_count, but not both
|
|
||||||
if end_pattern and line_count:
|
|
||||||
coder.io.tool_error("Cannot specify both end_pattern and line_count")
|
|
||||||
return "Error: Cannot specify both end_pattern and line_count"
|
|
||||||
|
|
||||||
# Split into lines for easier handling
|
|
||||||
lines = file_content.splitlines()
|
|
||||||
original_content = file_content
|
|
||||||
|
|
||||||
# Find occurrences of the start_pattern
|
|
||||||
start_pattern_line_indices = []
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if start_pattern in line:
|
|
||||||
if near_context:
|
if near_context:
|
||||||
context_window_start = max(0, i - 5)
|
pattern_desc += f" near context '{near_context}'"
|
||||||
context_window_end = min(len(lines), i + 6)
|
start_pattern_indices = find_pattern_indices(lines, start_pattern, near_context)
|
||||||
context_block = "\n".join(lines[context_window_start:context_window_end])
|
start_line_idx = select_occurrence_index(start_pattern_indices, occurrence, pattern_desc)
|
||||||
if near_context in context_block:
|
|
||||||
start_pattern_line_indices.append(i)
|
|
||||||
else:
|
|
||||||
start_pattern_line_indices.append(i)
|
|
||||||
|
|
||||||
if not start_pattern_line_indices:
|
# 3. Determine the end line
|
||||||
err_msg = f"Start pattern '{start_pattern}' not found"
|
start_line, end_line = determine_line_range(
|
||||||
if near_context: err_msg += f" near context '{near_context}'"
|
lines, start_line_idx, end_pattern, line_count, pattern_desc=pattern_desc
|
||||||
err_msg += f" in file '{file_path}'."
|
)
|
||||||
coder.io.tool_error(err_msg)
|
|
||||||
return f"Error: {err_msg}"
|
|
||||||
|
|
||||||
# Select the occurrence for the start pattern
|
# 4. Validate and prepare indentation
|
||||||
num_occurrences = len(start_pattern_line_indices)
|
|
||||||
try:
|
|
||||||
occurrence = int(occurrence)
|
|
||||||
if occurrence == -1:
|
|
||||||
target_idx = num_occurrences - 1
|
|
||||||
elif occurrence > 0 and occurrence <= num_occurrences:
|
|
||||||
target_idx = occurrence - 1
|
|
||||||
else:
|
|
||||||
err_msg = f"Occurrence number {occurrence} is out of range for start pattern '{start_pattern}'. Found {num_occurrences} occurrences"
|
|
||||||
if near_context: err_msg += f" near '{near_context}'"
|
|
||||||
err_msg += f" in '{file_path}'."
|
|
||||||
coder.io.tool_error(err_msg)
|
|
||||||
return f"Error: {err_msg}"
|
|
||||||
except ValueError:
|
|
||||||
coder.io.tool_error(f"Invalid occurrence value: '{occurrence}'. Must be an integer.")
|
|
||||||
return f"Error: Invalid occurrence value '{occurrence}'"
|
|
||||||
|
|
||||||
start_line = start_pattern_line_indices[target_idx]
|
|
||||||
occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
|
|
||||||
|
|
||||||
# Find the end line based on end_pattern or line_count
|
|
||||||
end_line = -1
|
|
||||||
if end_pattern:
|
|
||||||
for i in range(start_line, len(lines)):
|
|
||||||
if end_pattern in lines[i]:
|
|
||||||
end_line = i
|
|
||||||
break
|
|
||||||
if end_line == -1:
|
|
||||||
err_msg = f"End pattern '{end_pattern}' not found after {occurrence_str}start pattern '{start_pattern}' (line {start_line + 1}) in '{file_path}'."
|
|
||||||
coder.io.tool_error(err_msg)
|
|
||||||
return f"Error: {err_msg}"
|
|
||||||
elif line_count:
|
|
||||||
try:
|
|
||||||
line_count = int(line_count)
|
|
||||||
if line_count <= 0: raise ValueError("Line count must be positive")
|
|
||||||
end_line = min(start_line + line_count - 1, len(lines) - 1)
|
|
||||||
except ValueError:
|
|
||||||
coder.io.tool_error(f"Invalid line_count value: '{line_count}'. Must be a positive integer.")
|
|
||||||
return f"Error: Invalid line_count value '{line_count}'"
|
|
||||||
else:
|
|
||||||
end_line = start_line
|
|
||||||
|
|
||||||
# Determine indentation amount
|
|
||||||
try:
|
try:
|
||||||
indent_levels = int(indent_levels)
|
indent_levels = int(indent_levels)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
coder.io.tool_error(f"Invalid indent_levels value: '{indent_levels}'. Must be an integer.")
|
raise ToolError(f"Invalid indent_levels value: '{indent_levels}'. Must be an integer.")
|
||||||
return f"Error: Invalid indent_levels value '{indent_levels}'"
|
|
||||||
|
|
||||||
indent_str = ' ' * 4 # Assume 4 spaces per level
|
indent_str = ' ' * 4 # Assume 4 spaces per level
|
||||||
|
|
||||||
# Create a temporary copy to calculate the change
|
|
||||||
modified_lines = list(lines)
|
modified_lines = list(lines)
|
||||||
|
|
||||||
# Apply indentation to the temporary copy
|
# Apply indentation logic (core logic remains)
|
||||||
for i in range(start_line, end_line + 1):
|
for i in range(start_line, end_line + 1):
|
||||||
if indent_levels > 0:
|
if indent_levels > 0:
|
||||||
modified_lines[i] = (indent_str * indent_levels) + modified_lines[i]
|
modified_lines[i] = (indent_str * indent_levels) + modified_lines[i]
|
||||||
|
@ -139,27 +67,27 @@ def _execute_indent_lines(coder, file_path, start_pattern, end_pattern=None, lin
|
||||||
if actual_remove > 0:
|
if actual_remove > 0:
|
||||||
modified_lines[i] = modified_lines[i][actual_remove:]
|
modified_lines[i] = modified_lines[i][actual_remove:]
|
||||||
|
|
||||||
# Join lines back into a string
|
|
||||||
new_content = '\n'.join(modified_lines)
|
new_content = '\n'.join(modified_lines)
|
||||||
|
|
||||||
if original_content == new_content:
|
if original_content == new_content:
|
||||||
coder.io.tool_warning(f"No changes made: indentation would not change file")
|
coder.io.tool_warning(f"No changes made: indentation would not change file")
|
||||||
return f"Warning: No changes made (indentation would not change file)"
|
return f"Warning: No changes made (indentation would not change file)"
|
||||||
|
|
||||||
# Generate diff for feedback (assuming _generate_diff_snippet_indent is available on coder)
|
# 5. Generate diff for feedback
|
||||||
diff_snippet = coder._generate_diff_snippet_indent(original_content, new_content, start_line, end_line)
|
diff_snippet = coder._generate_diff_snippet_indent(original_content, new_content, start_line, end_line)
|
||||||
|
num_occurrences = len(start_pattern_indices)
|
||||||
# Handle dry run
|
occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
|
||||||
if dry_run:
|
|
||||||
action = "indent" if indent_levels > 0 else "unindent"
|
action = "indent" if indent_levels > 0 else "unindent"
|
||||||
coder.io.tool_output(f"Dry run: Would {action} lines {start_line+1}-{end_line+1} (based on {occurrence_str}start pattern '{start_pattern}') in {file_path}")
|
levels = abs(indent_levels)
|
||||||
return f"Dry run: Would {action} block. Diff snippet:\n{diff_snippet}"
|
level_text = "level" if levels == 1 else "levels"
|
||||||
|
num_lines = end_line - start_line + 1
|
||||||
|
|
||||||
# --- Apply Change (Not dry run) ---
|
# 6. Handle dry run
|
||||||
coder.io.write_text(abs_path, new_content)
|
if dry_run:
|
||||||
|
dry_run_message = f"Dry run: Would {action} {num_lines} lines ({start_line+1}-{end_line+1}) by {levels} {level_text} (based on {occurrence_str}start pattern '{start_pattern}') in {file_path}."
|
||||||
|
return format_tool_result(coder, tool_name, "", dry_run=True, dry_run_message=dry_run_message, diff_snippet=diff_snippet)
|
||||||
|
|
||||||
# Track the change
|
# 7. Apply Change (Not dry run)
|
||||||
try:
|
|
||||||
metadata = {
|
metadata = {
|
||||||
'start_line': start_line + 1,
|
'start_line': start_line + 1,
|
||||||
'end_line': end_line + 1,
|
'end_line': end_line + 1,
|
||||||
|
@ -170,25 +98,23 @@ def _execute_indent_lines(coder, file_path, start_pattern, end_pattern=None, lin
|
||||||
'near_context': near_context,
|
'near_context': near_context,
|
||||||
'occurrence': occurrence,
|
'occurrence': occurrence,
|
||||||
}
|
}
|
||||||
change_id = coder.change_tracker.track_change(
|
final_change_id = apply_change(
|
||||||
file_path=rel_path,
|
coder, abs_path, rel_path, original_content, new_content, 'indentlines', metadata, change_id
|
||||||
change_type='indentlines',
|
|
||||||
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 IndentLines: {track_e}")
|
|
||||||
change_id = "TRACKING_FAILED"
|
|
||||||
|
|
||||||
coder.aider_edited_files.add(rel_path)
|
# 8. Format and return result
|
||||||
|
action_past = "Indented" if indent_levels > 0 else "Unindented"
|
||||||
|
success_message = f"{action_past} {num_lines} lines by {levels} {level_text} (from {occurrence_str}start pattern) in {file_path}"
|
||||||
|
return format_tool_result(
|
||||||
|
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
||||||
|
)
|
||||||
|
|
||||||
# Improve feedback
|
except ToolError as e:
|
||||||
action = "Indented" if indent_levels > 0 else "Unindented"
|
# Handle errors raised by utility functions (expected errors)
|
||||||
levels = abs(indent_levels)
|
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
||||||
level_text = "level" if levels == 1 else "levels"
|
except Exception as e:
|
||||||
num_lines = end_line - start_line + 1
|
# Handle unexpected errors
|
||||||
|
return handle_tool_error(coder, tool_name, e)
|
||||||
coder.io.tool_output(f"✅ {action} {num_lines} lines (from {occurrence_str}start pattern) by {levels} {level_text} in {file_path} (change_id: {change_id})")
|
coder.io.tool_output(f"✅ {action} {num_lines} lines (from {occurrence_str}start pattern) by {levels} {level_text} in {file_path} (change_id: {change_id})")
|
||||||
return f"Successfully {action.lower()} {num_lines} lines by {levels} {level_text} (change_id: {change_id}). Diff snippet:\n{diff_snippet}"
|
return f"Successfully {action.lower()} {num_lines} lines by {levels} {level_text} (change_id: {change_id}). Diff snippet:\n{diff_snippet}"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue