aider-ce 0.87.2.dev9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aider-ce might be problematic. Click here for more details.
- aider/__init__.py +20 -0
- aider/__main__.py +4 -0
- aider/_version.py +34 -0
- aider/analytics.py +258 -0
- aider/args.py +1014 -0
- aider/args_formatter.py +228 -0
- aider/change_tracker.py +133 -0
- aider/coders/__init__.py +36 -0
- aider/coders/architect_coder.py +48 -0
- aider/coders/architect_prompts.py +40 -0
- aider/coders/ask_coder.py +9 -0
- aider/coders/ask_prompts.py +35 -0
- aider/coders/base_coder.py +3013 -0
- aider/coders/base_prompts.py +87 -0
- aider/coders/chat_chunks.py +64 -0
- aider/coders/context_coder.py +53 -0
- aider/coders/context_prompts.py +75 -0
- aider/coders/editblock_coder.py +657 -0
- aider/coders/editblock_fenced_coder.py +10 -0
- aider/coders/editblock_fenced_prompts.py +143 -0
- aider/coders/editblock_func_coder.py +141 -0
- aider/coders/editblock_func_prompts.py +27 -0
- aider/coders/editblock_prompts.py +177 -0
- aider/coders/editor_diff_fenced_coder.py +9 -0
- aider/coders/editor_diff_fenced_prompts.py +11 -0
- aider/coders/editor_editblock_coder.py +9 -0
- aider/coders/editor_editblock_prompts.py +21 -0
- aider/coders/editor_whole_coder.py +9 -0
- aider/coders/editor_whole_prompts.py +12 -0
- aider/coders/help_coder.py +16 -0
- aider/coders/help_prompts.py +46 -0
- aider/coders/navigator_coder.py +2711 -0
- aider/coders/navigator_legacy_prompts.py +338 -0
- aider/coders/navigator_prompts.py +530 -0
- aider/coders/patch_coder.py +706 -0
- aider/coders/patch_prompts.py +161 -0
- aider/coders/search_replace.py +757 -0
- aider/coders/shell.py +37 -0
- aider/coders/single_wholefile_func_coder.py +102 -0
- aider/coders/single_wholefile_func_prompts.py +27 -0
- aider/coders/udiff_coder.py +429 -0
- aider/coders/udiff_prompts.py +117 -0
- aider/coders/udiff_simple.py +14 -0
- aider/coders/udiff_simple_prompts.py +25 -0
- aider/coders/wholefile_coder.py +144 -0
- aider/coders/wholefile_func_coder.py +134 -0
- aider/coders/wholefile_func_prompts.py +27 -0
- aider/coders/wholefile_prompts.py +70 -0
- aider/commands.py +1946 -0
- aider/copypaste.py +72 -0
- aider/deprecated.py +126 -0
- aider/diffs.py +128 -0
- aider/dump.py +29 -0
- aider/editor.py +147 -0
- aider/exceptions.py +107 -0
- aider/format_settings.py +26 -0
- aider/gui.py +545 -0
- aider/help.py +163 -0
- aider/help_pats.py +19 -0
- aider/history.py +178 -0
- aider/io.py +1257 -0
- aider/linter.py +304 -0
- aider/llm.py +47 -0
- aider/main.py +1297 -0
- aider/mcp/__init__.py +94 -0
- aider/mcp/server.py +119 -0
- aider/mdstream.py +243 -0
- aider/models.py +1344 -0
- aider/onboarding.py +428 -0
- aider/openrouter.py +129 -0
- aider/prompts.py +56 -0
- aider/queries/tree-sitter-language-pack/README.md +7 -0
- aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- aider/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- aider/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- aider/queries/tree-sitter-languages/README.md +23 -0
- aider/queries/tree-sitter-languages/c-tags.scm +9 -0
- aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
- aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
- aider/queries/tree-sitter-languages/go-tags.scm +30 -0
- aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- aider/queries/tree-sitter-languages/java-tags.scm +20 -0
- aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- aider/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-languages/php-tags.scm +26 -0
- aider/queries/tree-sitter-languages/python-tags.scm +12 -0
- aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
- aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
- aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
- aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- aider/reasoning_tags.py +82 -0
- aider/repo.py +621 -0
- aider/repomap.py +988 -0
- aider/report.py +200 -0
- aider/resources/__init__.py +3 -0
- aider/resources/model-metadata.json +699 -0
- aider/resources/model-settings.yml +2046 -0
- aider/run_cmd.py +132 -0
- aider/scrape.py +284 -0
- aider/sendchat.py +61 -0
- aider/special.py +203 -0
- aider/tools/__init__.py +26 -0
- aider/tools/command.py +58 -0
- aider/tools/command_interactive.py +53 -0
- aider/tools/delete_block.py +120 -0
- aider/tools/delete_line.py +112 -0
- aider/tools/delete_lines.py +137 -0
- aider/tools/extract_lines.py +276 -0
- aider/tools/grep.py +171 -0
- aider/tools/indent_lines.py +155 -0
- aider/tools/insert_block.py +211 -0
- aider/tools/list_changes.py +51 -0
- aider/tools/ls.py +49 -0
- aider/tools/make_editable.py +46 -0
- aider/tools/make_readonly.py +29 -0
- aider/tools/remove.py +48 -0
- aider/tools/replace_all.py +77 -0
- aider/tools/replace_line.py +125 -0
- aider/tools/replace_lines.py +160 -0
- aider/tools/replace_text.py +125 -0
- aider/tools/show_numbered_context.py +101 -0
- aider/tools/tool_utils.py +313 -0
- aider/tools/undo_change.py +60 -0
- aider/tools/view.py +13 -0
- aider/tools/view_files_at_glob.py +65 -0
- aider/tools/view_files_matching.py +103 -0
- aider/tools/view_files_with_symbol.py +121 -0
- aider/urls.py +17 -0
- aider/utils.py +454 -0
- aider/versioncheck.py +113 -0
- aider/voice.py +187 -0
- aider/waiting.py +221 -0
- aider/watch.py +318 -0
- aider/watch_prompts.py +12 -0
- aider/website/Gemfile +8 -0
- aider/website/_includes/blame.md +162 -0
- aider/website/_includes/get-started.md +22 -0
- aider/website/_includes/help-tip.md +5 -0
- aider/website/_includes/help.md +24 -0
- aider/website/_includes/install.md +5 -0
- aider/website/_includes/keys.md +4 -0
- aider/website/_includes/model-warnings.md +67 -0
- aider/website/_includes/multi-line.md +22 -0
- aider/website/_includes/python-m-aider.md +5 -0
- aider/website/_includes/recording.css +228 -0
- aider/website/_includes/recording.md +34 -0
- aider/website/_includes/replit-pipx.md +9 -0
- aider/website/_includes/works-best.md +1 -0
- aider/website/_sass/custom/custom.scss +103 -0
- aider/website/docs/config/adv-model-settings.md +2260 -0
- aider/website/docs/config/aider_conf.md +548 -0
- aider/website/docs/config/api-keys.md +90 -0
- aider/website/docs/config/dotenv.md +493 -0
- aider/website/docs/config/editor.md +127 -0
- aider/website/docs/config/mcp.md +95 -0
- aider/website/docs/config/model-aliases.md +104 -0
- aider/website/docs/config/options.md +890 -0
- aider/website/docs/config/reasoning.md +210 -0
- aider/website/docs/config.md +44 -0
- aider/website/docs/faq.md +384 -0
- aider/website/docs/git.md +76 -0
- aider/website/docs/index.md +47 -0
- aider/website/docs/install/codespaces.md +39 -0
- aider/website/docs/install/docker.md +57 -0
- aider/website/docs/install/optional.md +100 -0
- aider/website/docs/install/replit.md +8 -0
- aider/website/docs/install.md +115 -0
- aider/website/docs/languages.md +264 -0
- aider/website/docs/legal/contributor-agreement.md +111 -0
- aider/website/docs/legal/privacy.md +104 -0
- aider/website/docs/llms/anthropic.md +77 -0
- aider/website/docs/llms/azure.md +48 -0
- aider/website/docs/llms/bedrock.md +132 -0
- aider/website/docs/llms/cohere.md +34 -0
- aider/website/docs/llms/deepseek.md +32 -0
- aider/website/docs/llms/gemini.md +49 -0
- aider/website/docs/llms/github.md +111 -0
- aider/website/docs/llms/groq.md +36 -0
- aider/website/docs/llms/lm-studio.md +39 -0
- aider/website/docs/llms/ollama.md +75 -0
- aider/website/docs/llms/openai-compat.md +39 -0
- aider/website/docs/llms/openai.md +58 -0
- aider/website/docs/llms/openrouter.md +78 -0
- aider/website/docs/llms/other.md +111 -0
- aider/website/docs/llms/vertex.md +50 -0
- aider/website/docs/llms/warnings.md +10 -0
- aider/website/docs/llms/xai.md +53 -0
- aider/website/docs/llms.md +54 -0
- aider/website/docs/more/analytics.md +127 -0
- aider/website/docs/more/edit-formats.md +116 -0
- aider/website/docs/more/infinite-output.md +159 -0
- aider/website/docs/more-info.md +8 -0
- aider/website/docs/recordings/auto-accept-architect.md +31 -0
- aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
- aider/website/docs/recordings/index.md +21 -0
- aider/website/docs/recordings/model-accepts-settings.md +69 -0
- aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
- aider/website/docs/repomap.md +112 -0
- aider/website/docs/scripting.md +100 -0
- aider/website/docs/troubleshooting/aider-not-found.md +24 -0
- aider/website/docs/troubleshooting/edit-errors.md +76 -0
- aider/website/docs/troubleshooting/imports.md +62 -0
- aider/website/docs/troubleshooting/models-and-keys.md +54 -0
- aider/website/docs/troubleshooting/support.md +79 -0
- aider/website/docs/troubleshooting/token-limits.md +96 -0
- aider/website/docs/troubleshooting/warnings.md +12 -0
- aider/website/docs/troubleshooting.md +11 -0
- aider/website/docs/usage/browser.md +57 -0
- aider/website/docs/usage/caching.md +49 -0
- aider/website/docs/usage/commands.md +133 -0
- aider/website/docs/usage/conventions.md +119 -0
- aider/website/docs/usage/copypaste.md +121 -0
- aider/website/docs/usage/images-urls.md +48 -0
- aider/website/docs/usage/lint-test.md +118 -0
- aider/website/docs/usage/modes.md +211 -0
- aider/website/docs/usage/not-code.md +179 -0
- aider/website/docs/usage/notifications.md +87 -0
- aider/website/docs/usage/tips.md +79 -0
- aider/website/docs/usage/tutorials.md +30 -0
- aider/website/docs/usage/voice.md +121 -0
- aider/website/docs/usage/watch.md +294 -0
- aider/website/docs/usage.md +102 -0
- aider/website/share/index.md +101 -0
- aider_ce-0.87.2.dev9.dist-info/METADATA +543 -0
- aider_ce-0.87.2.dev9.dist-info/RECORD +264 -0
- aider_ce-0.87.2.dev9.dist-info/WHEEL +5 -0
- aider_ce-0.87.2.dev9.dist-info/entry_points.txt +3 -0
- aider_ce-0.87.2.dev9.dist-info/licenses/LICENSE.txt +202 -0
- aider_ce-0.87.2.dev9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
4
|
+
from .tool_utils import (
|
|
5
|
+
ToolError,
|
|
6
|
+
apply_change,
|
|
7
|
+
find_pattern_indices,
|
|
8
|
+
format_tool_result,
|
|
9
|
+
generate_unified_diff_snippet,
|
|
10
|
+
handle_tool_error,
|
|
11
|
+
select_occurrence_index,
|
|
12
|
+
validate_file_for_edit,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _execute_insert_block(
|
|
17
|
+
coder,
|
|
18
|
+
file_path,
|
|
19
|
+
content,
|
|
20
|
+
after_pattern=None,
|
|
21
|
+
before_pattern=None,
|
|
22
|
+
occurrence=1,
|
|
23
|
+
change_id=None,
|
|
24
|
+
dry_run=False,
|
|
25
|
+
position=None,
|
|
26
|
+
auto_indent=True,
|
|
27
|
+
use_regex=False,
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Insert a block of text after or before a specified pattern using utility functions.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
coder: The coder instance
|
|
34
|
+
file_path: Path to the file to modify
|
|
35
|
+
content: The content to insert
|
|
36
|
+
after_pattern: Pattern to insert after (mutually exclusive with before_pattern and position)
|
|
37
|
+
before_pattern: Pattern to insert before (mutually exclusive with after_pattern and position)
|
|
38
|
+
occurrence: Which occurrence of the pattern to use (1-based, or -1 for last)
|
|
39
|
+
change_id: Optional ID for tracking changes
|
|
40
|
+
dry_run: If True, only simulate the change
|
|
41
|
+
position: Special position like "start_of_file" or "end_of_file"
|
|
42
|
+
auto_indent: If True, automatically adjust indentation of inserted content
|
|
43
|
+
use_regex: If True, treat patterns as regular expressions
|
|
44
|
+
"""
|
|
45
|
+
tool_name = "InsertBlock"
|
|
46
|
+
try:
|
|
47
|
+
# 1. Validate parameters
|
|
48
|
+
if sum(x is not None for x in [after_pattern, before_pattern, position]) != 1:
|
|
49
|
+
raise ToolError(
|
|
50
|
+
"Must specify exactly one of: after_pattern, before_pattern, or position"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# 2. Validate file and get content
|
|
54
|
+
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
55
|
+
lines = original_content.splitlines()
|
|
56
|
+
|
|
57
|
+
# Handle empty files
|
|
58
|
+
if not lines:
|
|
59
|
+
lines = [""]
|
|
60
|
+
|
|
61
|
+
# 3. Determine insertion point
|
|
62
|
+
insertion_line_idx = 0
|
|
63
|
+
pattern_type = ""
|
|
64
|
+
pattern_desc = ""
|
|
65
|
+
occurrence_str = ""
|
|
66
|
+
|
|
67
|
+
if position:
|
|
68
|
+
# Handle special positions
|
|
69
|
+
if position == "start_of_file":
|
|
70
|
+
insertion_line_idx = 0
|
|
71
|
+
pattern_type = "at start of"
|
|
72
|
+
elif position == "end_of_file":
|
|
73
|
+
insertion_line_idx = len(lines)
|
|
74
|
+
pattern_type = "at end of"
|
|
75
|
+
else:
|
|
76
|
+
raise ToolError(
|
|
77
|
+
f"Invalid position: '{position}'. Valid values are 'start_of_file' or"
|
|
78
|
+
" 'end_of_file'"
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
# Handle pattern-based insertion
|
|
82
|
+
pattern = after_pattern if after_pattern else before_pattern
|
|
83
|
+
pattern_type = "after" if after_pattern else "before"
|
|
84
|
+
pattern_desc = f"Pattern '{pattern}'"
|
|
85
|
+
|
|
86
|
+
# Find pattern matches
|
|
87
|
+
pattern_line_indices = find_pattern_indices(lines, pattern, use_regex=use_regex)
|
|
88
|
+
|
|
89
|
+
# Select the target occurrence
|
|
90
|
+
target_line_idx = select_occurrence_index(
|
|
91
|
+
pattern_line_indices, occurrence, pattern_desc
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Determine insertion point
|
|
95
|
+
insertion_line_idx = target_line_idx
|
|
96
|
+
if pattern_type == "after":
|
|
97
|
+
insertion_line_idx += 1 # Insert on the line *after* the matched line
|
|
98
|
+
|
|
99
|
+
# Format occurrence info for output
|
|
100
|
+
num_occurrences = len(pattern_line_indices)
|
|
101
|
+
occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
|
|
102
|
+
|
|
103
|
+
# 4. Handle indentation if requested
|
|
104
|
+
content_lines = content.splitlines()
|
|
105
|
+
|
|
106
|
+
if auto_indent and content_lines:
|
|
107
|
+
# Determine base indentation level
|
|
108
|
+
base_indent = ""
|
|
109
|
+
if insertion_line_idx > 0 and lines:
|
|
110
|
+
# Use indentation from the line before insertion point
|
|
111
|
+
reference_line_idx = min(insertion_line_idx - 1, len(lines) - 1)
|
|
112
|
+
reference_line = lines[reference_line_idx]
|
|
113
|
+
base_indent = re.match(r"^(\s*)", reference_line).group(1)
|
|
114
|
+
|
|
115
|
+
# Apply indentation to content lines, preserving relative indentation
|
|
116
|
+
if content_lines:
|
|
117
|
+
# Find minimum indentation in content to preserve relative indentation
|
|
118
|
+
content_indents = [
|
|
119
|
+
len(re.match(r"^(\s*)", line).group(1))
|
|
120
|
+
for line in content_lines
|
|
121
|
+
if line.strip()
|
|
122
|
+
]
|
|
123
|
+
min_content_indent = min(content_indents) if content_indents else 0
|
|
124
|
+
|
|
125
|
+
# Apply base indentation while preserving relative indentation
|
|
126
|
+
indented_content_lines = []
|
|
127
|
+
for line in content_lines:
|
|
128
|
+
if not line.strip(): # Empty or whitespace-only line
|
|
129
|
+
indented_content_lines.append("")
|
|
130
|
+
else:
|
|
131
|
+
# Remove existing indentation and add new base indentation
|
|
132
|
+
stripped_line = (
|
|
133
|
+
line[min_content_indent:] if min_content_indent <= len(line) else line
|
|
134
|
+
)
|
|
135
|
+
indented_content_lines.append(base_indent + stripped_line)
|
|
136
|
+
|
|
137
|
+
content_lines = indented_content_lines
|
|
138
|
+
|
|
139
|
+
# 5. Prepare the insertion
|
|
140
|
+
new_lines = lines[:insertion_line_idx] + content_lines + lines[insertion_line_idx:]
|
|
141
|
+
new_content = "\n".join(new_lines)
|
|
142
|
+
|
|
143
|
+
if original_content == new_content:
|
|
144
|
+
coder.io.tool_warning("No changes made: insertion would not change file")
|
|
145
|
+
return "Warning: No changes made (insertion would not change file)"
|
|
146
|
+
|
|
147
|
+
# 6. Generate diff for feedback
|
|
148
|
+
diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
|
149
|
+
|
|
150
|
+
# 7. Handle dry run
|
|
151
|
+
if dry_run:
|
|
152
|
+
if position:
|
|
153
|
+
dry_run_message = f"Dry run: Would insert block {pattern_type} {file_path}."
|
|
154
|
+
else:
|
|
155
|
+
dry_run_message = (
|
|
156
|
+
f"Dry run: Would insert block {pattern_type} {occurrence_str}pattern"
|
|
157
|
+
f" '{pattern}' in {file_path} at line {insertion_line_idx + 1}."
|
|
158
|
+
)
|
|
159
|
+
return format_tool_result(
|
|
160
|
+
coder,
|
|
161
|
+
tool_name,
|
|
162
|
+
"",
|
|
163
|
+
dry_run=True,
|
|
164
|
+
dry_run_message=dry_run_message,
|
|
165
|
+
diff_snippet=diff_snippet,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# 8. Apply Change (Not dry run)
|
|
169
|
+
metadata = {
|
|
170
|
+
"insertion_line_idx": insertion_line_idx,
|
|
171
|
+
"after_pattern": after_pattern,
|
|
172
|
+
"before_pattern": before_pattern,
|
|
173
|
+
"position": position,
|
|
174
|
+
"occurrence": occurrence,
|
|
175
|
+
"content": content,
|
|
176
|
+
"auto_indent": auto_indent,
|
|
177
|
+
"use_regex": use_regex,
|
|
178
|
+
}
|
|
179
|
+
final_change_id = apply_change(
|
|
180
|
+
coder,
|
|
181
|
+
abs_path,
|
|
182
|
+
rel_path,
|
|
183
|
+
original_content,
|
|
184
|
+
new_content,
|
|
185
|
+
"insertblock",
|
|
186
|
+
metadata,
|
|
187
|
+
change_id,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# 9. Format and return result
|
|
191
|
+
if position:
|
|
192
|
+
success_message = f"Inserted block {pattern_type} {file_path}"
|
|
193
|
+
else:
|
|
194
|
+
success_message = (
|
|
195
|
+
f"Inserted block {pattern_type} {occurrence_str}pattern in {file_path} at line"
|
|
196
|
+
f" {insertion_line_idx + 1}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return format_tool_result(
|
|
200
|
+
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
except ToolError as e:
|
|
204
|
+
# Handle errors raised by utility functions (expected errors)
|
|
205
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
coder.io.tool_error(
|
|
209
|
+
f"Error in InsertBlock: {str(e)}\n{traceback.format_exc()}"
|
|
210
|
+
) # Add traceback
|
|
211
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _execute_list_changes(coder, file_path=None, limit=10):
|
|
6
|
+
"""
|
|
7
|
+
List recent changes made to files.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
- coder: The Coder instance
|
|
11
|
+
- file_path: Optional path to filter changes by file
|
|
12
|
+
- limit: Maximum number of changes to list
|
|
13
|
+
|
|
14
|
+
Returns a formatted list of changes.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
# If file_path is specified, get the absolute path
|
|
18
|
+
rel_file_path = None
|
|
19
|
+
if file_path:
|
|
20
|
+
abs_path = coder.abs_root_path(file_path)
|
|
21
|
+
rel_file_path = coder.get_rel_fname(abs_path)
|
|
22
|
+
|
|
23
|
+
# Get the list of changes
|
|
24
|
+
changes = coder.change_tracker.list_changes(rel_file_path, limit)
|
|
25
|
+
|
|
26
|
+
if not changes:
|
|
27
|
+
if file_path:
|
|
28
|
+
return f"No changes found for file '{file_path}'"
|
|
29
|
+
else:
|
|
30
|
+
return "No changes have been made yet"
|
|
31
|
+
|
|
32
|
+
# Format the changes into a readable list
|
|
33
|
+
result = "Recent changes:\n"
|
|
34
|
+
for i, change in enumerate(changes):
|
|
35
|
+
change_time = datetime.fromtimestamp(change["timestamp"]).strftime("%H:%M:%S")
|
|
36
|
+
change_type = change["type"]
|
|
37
|
+
file_path = change["file_path"]
|
|
38
|
+
change_id = change["id"]
|
|
39
|
+
|
|
40
|
+
result += (
|
|
41
|
+
f"{i + 1}. [{change_id}] {change_time} - {change_type.upper()} on {file_path}\n"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
coder.io.tool_output(result) # Also print to console for user
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
coder.io.tool_error(
|
|
49
|
+
f"Error in ListChanges: {str(e)}\n{traceback.format_exc()}"
|
|
50
|
+
) # Add traceback
|
|
51
|
+
return f"Error: {str(e)}"
|
aider/tools/ls.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def execute_ls(coder, dir_path):
|
|
5
|
+
"""
|
|
6
|
+
List files in directory and optionally add some to context.
|
|
7
|
+
|
|
8
|
+
This provides information about the structure of the codebase,
|
|
9
|
+
similar to how a developer would explore directories.
|
|
10
|
+
"""
|
|
11
|
+
try:
|
|
12
|
+
# Make the path relative to root if it's absolute
|
|
13
|
+
if dir_path.startswith("/"):
|
|
14
|
+
rel_dir = os.path.relpath(dir_path, coder.root)
|
|
15
|
+
else:
|
|
16
|
+
rel_dir = dir_path
|
|
17
|
+
|
|
18
|
+
# Get absolute path
|
|
19
|
+
abs_dir = coder.abs_root_path(rel_dir)
|
|
20
|
+
|
|
21
|
+
# Check if path exists
|
|
22
|
+
if not os.path.exists(abs_dir):
|
|
23
|
+
coder.io.tool_output(f"⚠️ Directory '{dir_path}' not found")
|
|
24
|
+
return "Directory not found"
|
|
25
|
+
|
|
26
|
+
# Get directory contents
|
|
27
|
+
contents = []
|
|
28
|
+
try:
|
|
29
|
+
with os.scandir(abs_dir) as entries:
|
|
30
|
+
for entry in entries:
|
|
31
|
+
if entry.is_file() and not entry.name.startswith("."):
|
|
32
|
+
rel_path = os.path.join(rel_dir, entry.name)
|
|
33
|
+
contents.append(rel_path)
|
|
34
|
+
except NotADirectoryError:
|
|
35
|
+
# If it's a file, just return the file
|
|
36
|
+
contents = [rel_dir]
|
|
37
|
+
|
|
38
|
+
if contents:
|
|
39
|
+
coder.io.tool_output(f"📋 Listed {len(contents)} file(s) in '{dir_path}'")
|
|
40
|
+
if len(contents) > 10:
|
|
41
|
+
return f"Found {len(contents)} files: {', '.join(contents[:10])}..."
|
|
42
|
+
else:
|
|
43
|
+
return f"Found {len(contents)} files: {', '.join(contents)}"
|
|
44
|
+
else:
|
|
45
|
+
coder.io.tool_output(f"📋 No files found in '{dir_path}'")
|
|
46
|
+
return "No files found in directory"
|
|
47
|
+
except Exception as e:
|
|
48
|
+
coder.io.tool_error(f"Error in ls: {str(e)}")
|
|
49
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Keep the underscore prefix as this function is primarily for internal coder use
|
|
5
|
+
def _execute_make_editable(coder, file_path):
|
|
6
|
+
"""
|
|
7
|
+
Convert a read-only file to an editable file.
|
|
8
|
+
|
|
9
|
+
This allows the LLM to upgrade a file from read-only to editable
|
|
10
|
+
when it determines it needs to make changes to that file.
|
|
11
|
+
"""
|
|
12
|
+
try:
|
|
13
|
+
# Get absolute path
|
|
14
|
+
abs_path = coder.abs_root_path(file_path)
|
|
15
|
+
|
|
16
|
+
# Check if file is already editable
|
|
17
|
+
if abs_path in coder.abs_fnames:
|
|
18
|
+
coder.io.tool_output(f"📝 File '{file_path}' is already editable")
|
|
19
|
+
return "File is already editable"
|
|
20
|
+
|
|
21
|
+
# Check if file exists on disk
|
|
22
|
+
if not os.path.isfile(abs_path):
|
|
23
|
+
coder.io.tool_output(f"⚠️ File '{file_path}' not found")
|
|
24
|
+
return "Error: File not found"
|
|
25
|
+
|
|
26
|
+
# File exists, is not editable, might be read-only or not in context yet
|
|
27
|
+
was_read_only = False
|
|
28
|
+
if abs_path in coder.abs_read_only_fnames:
|
|
29
|
+
coder.abs_read_only_fnames.remove(abs_path)
|
|
30
|
+
was_read_only = True
|
|
31
|
+
|
|
32
|
+
# Add to editable files
|
|
33
|
+
coder.abs_fnames.add(abs_path)
|
|
34
|
+
|
|
35
|
+
if was_read_only:
|
|
36
|
+
coder.io.tool_output(f"📝 Moved '{file_path}' from read-only to editable")
|
|
37
|
+
return "File is now editable (moved from read-only)"
|
|
38
|
+
else:
|
|
39
|
+
# File was not previously in context at all
|
|
40
|
+
coder.io.tool_output(f"📝 Added '{file_path}' directly to editable context")
|
|
41
|
+
# Track if added during exploration? Maybe not needed for direct MakeEditable.
|
|
42
|
+
# coder.files_added_in_exploration.add(rel_path) # Consider if needed
|
|
43
|
+
return "File is now editable (added directly)"
|
|
44
|
+
except Exception as e:
|
|
45
|
+
coder.io.tool_error(f"Error in MakeEditable for '{file_path}': {str(e)}")
|
|
46
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
def _execute_make_readonly(coder, file_path):
|
|
2
|
+
"""
|
|
3
|
+
Convert an editable file to a read-only file.
|
|
4
|
+
|
|
5
|
+
This allows the LLM to downgrade a file from editable to read-only
|
|
6
|
+
when it determines it no longer needs to make changes to that file.
|
|
7
|
+
"""
|
|
8
|
+
try:
|
|
9
|
+
# Get absolute path
|
|
10
|
+
abs_path = coder.abs_root_path(file_path)
|
|
11
|
+
|
|
12
|
+
# Check if file is in editable context
|
|
13
|
+
if abs_path not in coder.abs_fnames:
|
|
14
|
+
if abs_path in coder.abs_read_only_fnames:
|
|
15
|
+
coder.io.tool_output(f"📚 File '{file_path}' is already read-only")
|
|
16
|
+
return "File is already read-only"
|
|
17
|
+
else:
|
|
18
|
+
coder.io.tool_output(f"⚠️ File '{file_path}' not in context")
|
|
19
|
+
return "File not in context"
|
|
20
|
+
|
|
21
|
+
# Move from editable to read-only
|
|
22
|
+
coder.abs_fnames.remove(abs_path)
|
|
23
|
+
coder.abs_read_only_fnames.add(abs_path)
|
|
24
|
+
|
|
25
|
+
coder.io.tool_output(f"📚 Made '{file_path}' read-only")
|
|
26
|
+
return "File is now read-only"
|
|
27
|
+
except Exception as e:
|
|
28
|
+
coder.io.tool_error(f"Error making file read-only: {str(e)}")
|
|
29
|
+
return f"Error: {str(e)}"
|
aider/tools/remove.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _execute_remove(coder, file_path):
|
|
5
|
+
"""
|
|
6
|
+
Explicitly remove a file from context.
|
|
7
|
+
|
|
8
|
+
This allows the LLM to clean up its context when files are no
|
|
9
|
+
longer needed, keeping the context focused and efficient.
|
|
10
|
+
"""
|
|
11
|
+
try:
|
|
12
|
+
# Get absolute path
|
|
13
|
+
abs_path = coder.abs_root_path(file_path)
|
|
14
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
15
|
+
|
|
16
|
+
# Check if file is in context (either editable or read-only)
|
|
17
|
+
removed = False
|
|
18
|
+
if abs_path in coder.abs_fnames:
|
|
19
|
+
# Don't remove if it's the last editable file and there are no read-only files
|
|
20
|
+
if len(coder.abs_fnames) <= 1 and not coder.abs_read_only_fnames:
|
|
21
|
+
coder.io.tool_output(
|
|
22
|
+
f"⚠️ Cannot remove '{file_path}' - it's the only file in context"
|
|
23
|
+
)
|
|
24
|
+
return "Cannot remove - last file in context"
|
|
25
|
+
coder.abs_fnames.remove(abs_path)
|
|
26
|
+
removed = True
|
|
27
|
+
elif abs_path in coder.abs_read_only_fnames:
|
|
28
|
+
# Don't remove if it's the last read-only file and there are no editable files
|
|
29
|
+
if len(coder.abs_read_only_fnames) <= 1 and not coder.abs_fnames:
|
|
30
|
+
coder.io.tool_output(
|
|
31
|
+
f"⚠️ Cannot remove '{file_path}' - it's the only file in context"
|
|
32
|
+
)
|
|
33
|
+
return "Cannot remove - last file in context"
|
|
34
|
+
coder.abs_read_only_fnames.remove(abs_path)
|
|
35
|
+
removed = True
|
|
36
|
+
|
|
37
|
+
if not removed:
|
|
38
|
+
coder.io.tool_output(f"⚠️ File '{file_path}' not in context")
|
|
39
|
+
return "File not in context"
|
|
40
|
+
|
|
41
|
+
# Track in recently removed
|
|
42
|
+
coder.recently_removed[rel_path] = {"removed_at": time.time()}
|
|
43
|
+
|
|
44
|
+
coder.io.tool_output(f"🗑️ Explicitly removed '{file_path}' from context")
|
|
45
|
+
return "Removed file from context"
|
|
46
|
+
except Exception as e:
|
|
47
|
+
coder.io.tool_error(f"Error removing file: {str(e)}")
|
|
48
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from .tool_utils import (
|
|
2
|
+
ToolError,
|
|
3
|
+
apply_change,
|
|
4
|
+
format_tool_result,
|
|
5
|
+
generate_unified_diff_snippet,
|
|
6
|
+
handle_tool_error,
|
|
7
|
+
validate_file_for_edit,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _execute_replace_all(coder, file_path, find_text, replace_text, change_id=None, dry_run=False):
|
|
12
|
+
"""
|
|
13
|
+
Replace all occurrences of text in a file using utility functions.
|
|
14
|
+
"""
|
|
15
|
+
# Get absolute file path
|
|
16
|
+
abs_path = coder.abs_root_path(file_path)
|
|
17
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
18
|
+
tool_name = "ReplaceAll"
|
|
19
|
+
try:
|
|
20
|
+
# 1. Validate file and get content
|
|
21
|
+
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
22
|
+
|
|
23
|
+
# 2. Count occurrences
|
|
24
|
+
count = original_content.count(find_text)
|
|
25
|
+
if count == 0:
|
|
26
|
+
coder.io.tool_warning(f"Text '{find_text}' not found in file '{file_path}'")
|
|
27
|
+
return "Warning: Text not found in file"
|
|
28
|
+
|
|
29
|
+
# 3. Perform the replacement
|
|
30
|
+
new_content = original_content.replace(find_text, replace_text)
|
|
31
|
+
|
|
32
|
+
if original_content == new_content:
|
|
33
|
+
coder.io.tool_warning("No changes made: replacement text is identical to original")
|
|
34
|
+
return "Warning: No changes made (replacement identical to original)"
|
|
35
|
+
|
|
36
|
+
# 4. Generate diff for feedback
|
|
37
|
+
diff_examples = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
|
38
|
+
|
|
39
|
+
# 5. Handle dry run
|
|
40
|
+
if dry_run:
|
|
41
|
+
dry_run_message = (
|
|
42
|
+
f"Dry run: Would replace {count} occurrences of '{find_text}' in {file_path}."
|
|
43
|
+
)
|
|
44
|
+
return format_tool_result(
|
|
45
|
+
coder,
|
|
46
|
+
tool_name,
|
|
47
|
+
"",
|
|
48
|
+
dry_run=True,
|
|
49
|
+
dry_run_message=dry_run_message,
|
|
50
|
+
diff_snippet=diff_examples,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# 6. Apply Change (Not dry run)
|
|
54
|
+
metadata = {"find_text": find_text, "replace_text": replace_text, "occurrences": count}
|
|
55
|
+
final_change_id = apply_change(
|
|
56
|
+
coder,
|
|
57
|
+
abs_path,
|
|
58
|
+
rel_path,
|
|
59
|
+
original_content,
|
|
60
|
+
new_content,
|
|
61
|
+
"replaceall",
|
|
62
|
+
metadata,
|
|
63
|
+
change_id,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# 7. Format and return result
|
|
67
|
+
success_message = f"Replaced {count} occurrences in {file_path}"
|
|
68
|
+
return format_tool_result(
|
|
69
|
+
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_examples
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
except ToolError as e:
|
|
73
|
+
# Handle errors raised by utility functions
|
|
74
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
# Handle unexpected errors
|
|
77
|
+
return handle_tool_error(coder, tool_name, e)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _execute_replace_line(
|
|
6
|
+
coder, file_path, line_number, new_content, change_id=None, dry_run=False
|
|
7
|
+
):
|
|
8
|
+
"""
|
|
9
|
+
Replace a specific line identified by line number.
|
|
10
|
+
Useful for fixing errors identified by error messages or linters.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
- coder: The Coder instance
|
|
14
|
+
- file_path: Path to the file to modify
|
|
15
|
+
- line_number: The line number to replace (1-based)
|
|
16
|
+
- new_content: New content for the line
|
|
17
|
+
- change_id: Optional ID for tracking the change
|
|
18
|
+
- dry_run: If True, simulate the change without modifying the file
|
|
19
|
+
|
|
20
|
+
Returns a result message.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
# Get absolute file path
|
|
24
|
+
abs_path = coder.abs_root_path(file_path)
|
|
25
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
26
|
+
|
|
27
|
+
# Check if file exists
|
|
28
|
+
if not os.path.isfile(abs_path):
|
|
29
|
+
coder.io.tool_error(f"File '{file_path}' not found")
|
|
30
|
+
return "Error: File not found"
|
|
31
|
+
|
|
32
|
+
# Check if file is in editable context
|
|
33
|
+
if abs_path not in coder.abs_fnames:
|
|
34
|
+
if abs_path in coder.abs_read_only_fnames:
|
|
35
|
+
coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
|
36
|
+
return "Error: File is read-only. Use MakeEditable first."
|
|
37
|
+
else:
|
|
38
|
+
coder.io.tool_error(f"File '{file_path}' not in context")
|
|
39
|
+
return "Error: File not in context"
|
|
40
|
+
|
|
41
|
+
# Reread file content immediately before modification
|
|
42
|
+
file_content = coder.io.read_text(abs_path)
|
|
43
|
+
if file_content is None:
|
|
44
|
+
coder.io.tool_error(f"Could not read file '{file_path}' before ReplaceLine operation.")
|
|
45
|
+
return f"Error: Could not read file '{file_path}'"
|
|
46
|
+
|
|
47
|
+
# Split into lines
|
|
48
|
+
lines = file_content.splitlines()
|
|
49
|
+
|
|
50
|
+
# Validate line number
|
|
51
|
+
if not isinstance(line_number, int):
|
|
52
|
+
try:
|
|
53
|
+
line_number = int(line_number)
|
|
54
|
+
except ValueError:
|
|
55
|
+
coder.io.tool_error(f"Line number must be an integer, got '{line_number}'")
|
|
56
|
+
coder.io.tool_error(
|
|
57
|
+
f"Invalid line_number value: '{line_number}'. Must be an integer."
|
|
58
|
+
)
|
|
59
|
+
return f"Error: Invalid line_number value '{line_number}'"
|
|
60
|
+
|
|
61
|
+
# Convert 1-based line number to 0-based index
|
|
62
|
+
idx = line_number - 1
|
|
63
|
+
|
|
64
|
+
if idx < 0 or idx >= len(lines):
|
|
65
|
+
coder.io.tool_error(
|
|
66
|
+
f"Line number {line_number} is out of range for file '{file_path}' (has"
|
|
67
|
+
f" {len(lines)} lines)."
|
|
68
|
+
)
|
|
69
|
+
return f"Error: Line number {line_number} out of range"
|
|
70
|
+
|
|
71
|
+
# Store original content for change tracking
|
|
72
|
+
original_content = file_content
|
|
73
|
+
original_line = lines[idx]
|
|
74
|
+
|
|
75
|
+
# Replace the line
|
|
76
|
+
lines[idx] = new_content
|
|
77
|
+
|
|
78
|
+
# Join lines back into a string
|
|
79
|
+
new_content_full = "\n".join(lines)
|
|
80
|
+
|
|
81
|
+
if original_content == new_content_full:
|
|
82
|
+
coder.io.tool_warning("No changes made: new line content is identical to original")
|
|
83
|
+
return "Warning: No changes made (new content identical to original)"
|
|
84
|
+
|
|
85
|
+
# Create a readable diff for the line replacement
|
|
86
|
+
diff = f"Line {line_number}:\n- {original_line}\n+ {new_content}"
|
|
87
|
+
|
|
88
|
+
# Handle dry run
|
|
89
|
+
if dry_run:
|
|
90
|
+
coder.io.tool_output(f"Dry run: Would replace line {line_number} in {file_path}")
|
|
91
|
+
return f"Dry run: Would replace line {line_number}. Diff:\n{diff}"
|
|
92
|
+
|
|
93
|
+
# --- Apply Change (Not dry run) ---
|
|
94
|
+
coder.io.write_text(abs_path, new_content_full)
|
|
95
|
+
|
|
96
|
+
# Track the change
|
|
97
|
+
try:
|
|
98
|
+
metadata = {
|
|
99
|
+
"line_number": line_number,
|
|
100
|
+
"original_line": original_line,
|
|
101
|
+
"new_line": new_content,
|
|
102
|
+
}
|
|
103
|
+
change_id = coder.change_tracker.track_change(
|
|
104
|
+
file_path=rel_path,
|
|
105
|
+
change_type="replaceline",
|
|
106
|
+
original_content=original_content,
|
|
107
|
+
new_content=new_content_full,
|
|
108
|
+
metadata=metadata,
|
|
109
|
+
change_id=change_id,
|
|
110
|
+
)
|
|
111
|
+
except Exception as track_e:
|
|
112
|
+
coder.io.tool_error(f"Error tracking change for ReplaceLine: {track_e}")
|
|
113
|
+
change_id = "TRACKING_FAILED"
|
|
114
|
+
|
|
115
|
+
coder.aider_edited_files.add(rel_path)
|
|
116
|
+
|
|
117
|
+
# Improve feedback
|
|
118
|
+
coder.io.tool_output(
|
|
119
|
+
f"✅ Replaced line {line_number} in {file_path} (change_id: {change_id})"
|
|
120
|
+
)
|
|
121
|
+
return f"Successfully replaced line {line_number} (change_id: {change_id}). Diff:\n{diff}"
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
coder.io.tool_error(f"Error in ReplaceLine: {str(e)}\n{traceback.format_exc()}")
|
|
125
|
+
return f"Error: {str(e)}"
|