aider-ce 0.88.20__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.
Files changed (279) hide show
  1. aider/__init__.py +20 -0
  2. aider/__main__.py +4 -0
  3. aider/_version.py +34 -0
  4. aider/analytics.py +258 -0
  5. aider/args.py +1056 -0
  6. aider/args_formatter.py +228 -0
  7. aider/change_tracker.py +133 -0
  8. aider/coders/__init__.py +36 -0
  9. aider/coders/agent_coder.py +2166 -0
  10. aider/coders/agent_prompts.py +104 -0
  11. aider/coders/architect_coder.py +48 -0
  12. aider/coders/architect_prompts.py +40 -0
  13. aider/coders/ask_coder.py +9 -0
  14. aider/coders/ask_prompts.py +35 -0
  15. aider/coders/base_coder.py +3613 -0
  16. aider/coders/base_prompts.py +87 -0
  17. aider/coders/chat_chunks.py +64 -0
  18. aider/coders/context_coder.py +53 -0
  19. aider/coders/context_prompts.py +75 -0
  20. aider/coders/editblock_coder.py +657 -0
  21. aider/coders/editblock_fenced_coder.py +10 -0
  22. aider/coders/editblock_fenced_prompts.py +143 -0
  23. aider/coders/editblock_func_coder.py +141 -0
  24. aider/coders/editblock_func_prompts.py +27 -0
  25. aider/coders/editblock_prompts.py +175 -0
  26. aider/coders/editor_diff_fenced_coder.py +9 -0
  27. aider/coders/editor_diff_fenced_prompts.py +11 -0
  28. aider/coders/editor_editblock_coder.py +9 -0
  29. aider/coders/editor_editblock_prompts.py +21 -0
  30. aider/coders/editor_whole_coder.py +9 -0
  31. aider/coders/editor_whole_prompts.py +12 -0
  32. aider/coders/help_coder.py +16 -0
  33. aider/coders/help_prompts.py +46 -0
  34. aider/coders/patch_coder.py +706 -0
  35. aider/coders/patch_prompts.py +159 -0
  36. aider/coders/search_replace.py +757 -0
  37. aider/coders/shell.py +37 -0
  38. aider/coders/single_wholefile_func_coder.py +102 -0
  39. aider/coders/single_wholefile_func_prompts.py +27 -0
  40. aider/coders/udiff_coder.py +429 -0
  41. aider/coders/udiff_prompts.py +115 -0
  42. aider/coders/udiff_simple.py +14 -0
  43. aider/coders/udiff_simple_prompts.py +25 -0
  44. aider/coders/wholefile_coder.py +144 -0
  45. aider/coders/wholefile_func_coder.py +134 -0
  46. aider/coders/wholefile_func_prompts.py +27 -0
  47. aider/coders/wholefile_prompts.py +65 -0
  48. aider/commands.py +2173 -0
  49. aider/copypaste.py +72 -0
  50. aider/deprecated.py +126 -0
  51. aider/diffs.py +128 -0
  52. aider/dump.py +29 -0
  53. aider/editor.py +147 -0
  54. aider/exceptions.py +115 -0
  55. aider/format_settings.py +26 -0
  56. aider/gui.py +545 -0
  57. aider/help.py +163 -0
  58. aider/help_pats.py +19 -0
  59. aider/helpers/__init__.py +9 -0
  60. aider/helpers/similarity.py +98 -0
  61. aider/history.py +180 -0
  62. aider/io.py +1608 -0
  63. aider/linter.py +304 -0
  64. aider/llm.py +55 -0
  65. aider/main.py +1415 -0
  66. aider/mcp/__init__.py +174 -0
  67. aider/mcp/server.py +149 -0
  68. aider/mdstream.py +243 -0
  69. aider/models.py +1313 -0
  70. aider/onboarding.py +429 -0
  71. aider/openrouter.py +129 -0
  72. aider/prompts.py +56 -0
  73. aider/queries/tree-sitter-language-pack/README.md +7 -0
  74. aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  75. aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  76. aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  77. aider/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  78. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  79. aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  80. aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  81. aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  82. aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  83. aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  84. aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  85. aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  86. aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  87. aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  88. aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  89. aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  90. aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  91. aider/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  92. aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  93. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  94. aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  95. aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  96. aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  97. aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  98. aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  99. aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  100. aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  101. aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  102. aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  103. aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  104. aider/queries/tree-sitter-languages/README.md +24 -0
  105. aider/queries/tree-sitter-languages/c-tags.scm +9 -0
  106. aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  107. aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  108. aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
  109. aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  110. aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  111. aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
  112. aider/queries/tree-sitter-languages/fortran-tags.scm +15 -0
  113. aider/queries/tree-sitter-languages/go-tags.scm +30 -0
  114. aider/queries/tree-sitter-languages/haskell-tags.scm +3 -0
  115. aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  116. aider/queries/tree-sitter-languages/java-tags.scm +20 -0
  117. aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  118. aider/queries/tree-sitter-languages/julia-tags.scm +60 -0
  119. aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  120. aider/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  121. aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  122. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  123. aider/queries/tree-sitter-languages/php-tags.scm +26 -0
  124. aider/queries/tree-sitter-languages/python-tags.scm +12 -0
  125. aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
  126. aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  127. aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
  128. aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
  129. aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  130. aider/queries/tree-sitter-languages/zig-tags.scm +3 -0
  131. aider/reasoning_tags.py +82 -0
  132. aider/repo.py +621 -0
  133. aider/repomap.py +1174 -0
  134. aider/report.py +260 -0
  135. aider/resources/__init__.py +3 -0
  136. aider/resources/model-metadata.json +776 -0
  137. aider/resources/model-settings.yml +2068 -0
  138. aider/run_cmd.py +133 -0
  139. aider/scrape.py +293 -0
  140. aider/sendchat.py +242 -0
  141. aider/sessions.py +256 -0
  142. aider/special.py +203 -0
  143. aider/tools/__init__.py +72 -0
  144. aider/tools/command.py +105 -0
  145. aider/tools/command_interactive.py +122 -0
  146. aider/tools/delete_block.py +182 -0
  147. aider/tools/delete_line.py +155 -0
  148. aider/tools/delete_lines.py +184 -0
  149. aider/tools/extract_lines.py +341 -0
  150. aider/tools/finished.py +48 -0
  151. aider/tools/git_branch.py +129 -0
  152. aider/tools/git_diff.py +60 -0
  153. aider/tools/git_log.py +57 -0
  154. aider/tools/git_remote.py +53 -0
  155. aider/tools/git_show.py +51 -0
  156. aider/tools/git_status.py +46 -0
  157. aider/tools/grep.py +256 -0
  158. aider/tools/indent_lines.py +221 -0
  159. aider/tools/insert_block.py +288 -0
  160. aider/tools/list_changes.py +86 -0
  161. aider/tools/ls.py +93 -0
  162. aider/tools/make_editable.py +85 -0
  163. aider/tools/make_readonly.py +69 -0
  164. aider/tools/remove.py +91 -0
  165. aider/tools/replace_all.py +126 -0
  166. aider/tools/replace_line.py +173 -0
  167. aider/tools/replace_lines.py +217 -0
  168. aider/tools/replace_text.py +187 -0
  169. aider/tools/show_numbered_context.py +147 -0
  170. aider/tools/tool_utils.py +313 -0
  171. aider/tools/undo_change.py +95 -0
  172. aider/tools/update_todo_list.py +156 -0
  173. aider/tools/view.py +57 -0
  174. aider/tools/view_files_matching.py +141 -0
  175. aider/tools/view_files_with_symbol.py +129 -0
  176. aider/urls.py +17 -0
  177. aider/utils.py +456 -0
  178. aider/versioncheck.py +113 -0
  179. aider/voice.py +205 -0
  180. aider/waiting.py +38 -0
  181. aider/watch.py +318 -0
  182. aider/watch_prompts.py +12 -0
  183. aider/website/Gemfile +8 -0
  184. aider/website/_includes/blame.md +162 -0
  185. aider/website/_includes/get-started.md +22 -0
  186. aider/website/_includes/help-tip.md +5 -0
  187. aider/website/_includes/help.md +24 -0
  188. aider/website/_includes/install.md +5 -0
  189. aider/website/_includes/keys.md +4 -0
  190. aider/website/_includes/model-warnings.md +67 -0
  191. aider/website/_includes/multi-line.md +22 -0
  192. aider/website/_includes/python-m-aider.md +5 -0
  193. aider/website/_includes/recording.css +228 -0
  194. aider/website/_includes/recording.md +34 -0
  195. aider/website/_includes/replit-pipx.md +9 -0
  196. aider/website/_includes/works-best.md +1 -0
  197. aider/website/_sass/custom/custom.scss +103 -0
  198. aider/website/docs/config/adv-model-settings.md +2261 -0
  199. aider/website/docs/config/agent-mode.md +194 -0
  200. aider/website/docs/config/aider_conf.md +548 -0
  201. aider/website/docs/config/api-keys.md +90 -0
  202. aider/website/docs/config/dotenv.md +493 -0
  203. aider/website/docs/config/editor.md +127 -0
  204. aider/website/docs/config/mcp.md +95 -0
  205. aider/website/docs/config/model-aliases.md +104 -0
  206. aider/website/docs/config/options.md +890 -0
  207. aider/website/docs/config/reasoning.md +210 -0
  208. aider/website/docs/config.md +44 -0
  209. aider/website/docs/faq.md +384 -0
  210. aider/website/docs/git.md +76 -0
  211. aider/website/docs/index.md +47 -0
  212. aider/website/docs/install/codespaces.md +39 -0
  213. aider/website/docs/install/docker.md +57 -0
  214. aider/website/docs/install/optional.md +100 -0
  215. aider/website/docs/install/replit.md +8 -0
  216. aider/website/docs/install.md +115 -0
  217. aider/website/docs/languages.md +264 -0
  218. aider/website/docs/legal/contributor-agreement.md +111 -0
  219. aider/website/docs/legal/privacy.md +104 -0
  220. aider/website/docs/llms/anthropic.md +77 -0
  221. aider/website/docs/llms/azure.md +48 -0
  222. aider/website/docs/llms/bedrock.md +132 -0
  223. aider/website/docs/llms/cohere.md +34 -0
  224. aider/website/docs/llms/deepseek.md +32 -0
  225. aider/website/docs/llms/gemini.md +49 -0
  226. aider/website/docs/llms/github.md +111 -0
  227. aider/website/docs/llms/groq.md +36 -0
  228. aider/website/docs/llms/lm-studio.md +39 -0
  229. aider/website/docs/llms/ollama.md +75 -0
  230. aider/website/docs/llms/openai-compat.md +39 -0
  231. aider/website/docs/llms/openai.md +58 -0
  232. aider/website/docs/llms/openrouter.md +78 -0
  233. aider/website/docs/llms/other.md +117 -0
  234. aider/website/docs/llms/vertex.md +50 -0
  235. aider/website/docs/llms/warnings.md +10 -0
  236. aider/website/docs/llms/xai.md +53 -0
  237. aider/website/docs/llms.md +54 -0
  238. aider/website/docs/more/analytics.md +127 -0
  239. aider/website/docs/more/edit-formats.md +116 -0
  240. aider/website/docs/more/infinite-output.md +165 -0
  241. aider/website/docs/more-info.md +8 -0
  242. aider/website/docs/recordings/auto-accept-architect.md +31 -0
  243. aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
  244. aider/website/docs/recordings/index.md +21 -0
  245. aider/website/docs/recordings/model-accepts-settings.md +69 -0
  246. aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
  247. aider/website/docs/repomap.md +112 -0
  248. aider/website/docs/scripting.md +100 -0
  249. aider/website/docs/sessions.md +203 -0
  250. aider/website/docs/troubleshooting/aider-not-found.md +24 -0
  251. aider/website/docs/troubleshooting/edit-errors.md +76 -0
  252. aider/website/docs/troubleshooting/imports.md +62 -0
  253. aider/website/docs/troubleshooting/models-and-keys.md +54 -0
  254. aider/website/docs/troubleshooting/support.md +79 -0
  255. aider/website/docs/troubleshooting/token-limits.md +96 -0
  256. aider/website/docs/troubleshooting/warnings.md +12 -0
  257. aider/website/docs/troubleshooting.md +11 -0
  258. aider/website/docs/usage/browser.md +57 -0
  259. aider/website/docs/usage/caching.md +49 -0
  260. aider/website/docs/usage/commands.md +133 -0
  261. aider/website/docs/usage/conventions.md +119 -0
  262. aider/website/docs/usage/copypaste.md +121 -0
  263. aider/website/docs/usage/images-urls.md +48 -0
  264. aider/website/docs/usage/lint-test.md +118 -0
  265. aider/website/docs/usage/modes.md +211 -0
  266. aider/website/docs/usage/not-code.md +179 -0
  267. aider/website/docs/usage/notifications.md +87 -0
  268. aider/website/docs/usage/tips.md +79 -0
  269. aider/website/docs/usage/tutorials.md +30 -0
  270. aider/website/docs/usage/voice.md +121 -0
  271. aider/website/docs/usage/watch.md +294 -0
  272. aider/website/docs/usage.md +102 -0
  273. aider/website/share/index.md +101 -0
  274. aider_ce-0.88.20.dist-info/METADATA +187 -0
  275. aider_ce-0.88.20.dist-info/RECORD +279 -0
  276. aider_ce-0.88.20.dist-info/WHEEL +5 -0
  277. aider_ce-0.88.20.dist-info/entry_points.txt +2 -0
  278. aider_ce-0.88.20.dist-info/licenses/LICENSE.txt +202 -0
  279. aider_ce-0.88.20.dist-info/top_level.txt +1 -0
@@ -0,0 +1,51 @@
1
+ from aider.repo import ANY_GIT_ERROR
2
+
3
+ schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "GitShow",
7
+ "description": "Show various types of objects (blobs, trees, tags, and commits).",
8
+ "parameters": {
9
+ "type": "object",
10
+ "properties": {
11
+ "object": {
12
+ "type": "string",
13
+ "description": "The object to show. Defaults to HEAD.",
14
+ },
15
+ },
16
+ "required": [],
17
+ },
18
+ },
19
+ }
20
+
21
+ # Normalized tool name for lookup
22
+ NORM_NAME = "gitshow"
23
+
24
+
25
+ def _execute_git_show(coder, object="HEAD"):
26
+ """
27
+ Show various types of objects (blobs, trees, tags, and commits).
28
+ """
29
+ if not coder.repo:
30
+ return "Not in a git repository."
31
+
32
+ try:
33
+ return coder.repo.repo.git.show(object)
34
+ except ANY_GIT_ERROR as e:
35
+ coder.io.tool_error(f"Error running git show: {e}")
36
+ return f"Error running git show: {e}"
37
+
38
+
39
+ def process_response(coder, params):
40
+ """
41
+ Process the GitShow tool response.
42
+
43
+ Args:
44
+ coder: The Coder instance
45
+ params: Dictionary of parameters
46
+
47
+ Returns:
48
+ str: Result message
49
+ """
50
+ object = params.get("object", "HEAD")
51
+ return _execute_git_show(coder, object)
@@ -0,0 +1,46 @@
1
+ from aider.repo import ANY_GIT_ERROR
2
+
3
+ schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "GitStatus",
7
+ "description": "Show the working tree status.",
8
+ "parameters": {
9
+ "type": "object",
10
+ "properties": {},
11
+ "required": [],
12
+ },
13
+ },
14
+ }
15
+
16
+ # Normalized tool name for lookup
17
+ NORM_NAME = "gitstatus"
18
+
19
+
20
+ def _execute_git_status(coder):
21
+ """
22
+ Show the working tree status.
23
+ """
24
+ if not coder.repo:
25
+ return "Not in a git repository."
26
+
27
+ try:
28
+ return coder.repo.repo.git.status()
29
+ except ANY_GIT_ERROR as e:
30
+ coder.io.tool_error(f"Error running git status: {e}")
31
+ return f"Error running git status: {e}"
32
+
33
+
34
+ def process_response(coder, params):
35
+ """
36
+ Process the GitStatus tool response.
37
+
38
+ Args:
39
+ coder: The Coder instance
40
+ params: Dictionary of parameters (should be empty for GitStatus)
41
+
42
+ Returns:
43
+ str: Result message
44
+ """
45
+ # GitStatus tool has no parameters to validate
46
+ return _execute_git_status(coder)
aider/tools/grep.py ADDED
@@ -0,0 +1,256 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+ import oslex
5
+
6
+ from aider.run_cmd import run_cmd_subprocess
7
+
8
+ schema = {
9
+ "type": "function",
10
+ "function": {
11
+ "name": "Grep",
12
+ "description": "Search for a pattern in files.",
13
+ "parameters": {
14
+ "type": "object",
15
+ "properties": {
16
+ "pattern": {
17
+ "type": "string",
18
+ "description": "The pattern to search for.",
19
+ },
20
+ "file_pattern": {
21
+ "type": "string",
22
+ "description": "Glob pattern for files to search. Defaults to '*'.",
23
+ },
24
+ "directory": {
25
+ "type": "string",
26
+ "description": "Directory to search in. Defaults to '.'.",
27
+ },
28
+ "use_regex": {
29
+ "type": "boolean",
30
+ "description": "Whether to use regex. Defaults to False.",
31
+ },
32
+ "case_insensitive": {
33
+ "type": "boolean",
34
+ "description": (
35
+ "Whether to perform a case-insensitive search. Defaults to False."
36
+ ),
37
+ },
38
+ "context_before": {
39
+ "type": "integer",
40
+ "description": "Number of lines to show before a match. Defaults to 5.",
41
+ },
42
+ "context_after": {
43
+ "type": "integer",
44
+ "description": "Number of lines to show after a match. Defaults to 5.",
45
+ },
46
+ },
47
+ "required": ["pattern"],
48
+ },
49
+ },
50
+ }
51
+
52
+ # Normalized tool name for lookup
53
+ NORM_NAME = "grep"
54
+
55
+
56
+ def _find_search_tool():
57
+ """Find the best available command-line search tool (rg, ag, grep)."""
58
+ if shutil.which("rg"):
59
+ return "rg", shutil.which("rg")
60
+ elif shutil.which("ag"):
61
+ return "ag", shutil.which("ag")
62
+ elif shutil.which("grep"):
63
+ return "grep", shutil.which("grep")
64
+ else:
65
+ return None, None
66
+
67
+
68
+ def _execute_grep(
69
+ coder,
70
+ pattern,
71
+ file_pattern="*",
72
+ directory=".",
73
+ use_regex=False,
74
+ case_insensitive=False,
75
+ context_before=5,
76
+ context_after=5,
77
+ ):
78
+ """
79
+ Search for lines matching a pattern in files within the project repository.
80
+ Uses rg (ripgrep), ag (the silver searcher), or grep, whichever is available.
81
+
82
+ Args:
83
+ coder: The Coder instance.
84
+ pattern (str): The pattern to search for.
85
+ file_pattern (str, optional): Glob pattern to filter files. Defaults to "*".
86
+ directory (str, optional): Directory to search within relative to repo root. Defaults to ".".
87
+ use_regex (bool, optional): Whether the pattern is a regular expression. Defaults to False.
88
+ case_insensitive (bool, optional): Whether the search should be case-insensitive. Defaults to False.
89
+ context_before (int, optional): Number of context lines to show before matches. Defaults to 5.
90
+ context_after (int, optional): Number of context lines to show after matches. Defaults to 5.
91
+
92
+ Returns:
93
+ str: Formatted result indicating success or failure, including matching lines or error message.
94
+ """
95
+ repo = coder.repo
96
+ if not repo:
97
+ coder.io.tool_error("Not in a git repository.")
98
+ return "Error: Not in a git repository."
99
+
100
+ tool_name, tool_path = _find_search_tool()
101
+ if not tool_path:
102
+ coder.io.tool_error("No search tool (rg, ag, grep) found in PATH.")
103
+ return "Error: No search tool (rg, ag, grep) found."
104
+
105
+ try:
106
+ search_dir_path = Path(repo.root) / directory
107
+ if not search_dir_path.is_dir():
108
+ coder.io.tool_error(f"Directory not found: {directory}")
109
+ return f"Error: Directory not found: {directory}"
110
+
111
+ # Build the command arguments based on the available tool
112
+ cmd_args = [tool_path]
113
+
114
+ # Common options or tool-specific equivalents
115
+ if tool_name in ["rg", "grep"]:
116
+ cmd_args.append("-n") # Line numbers for rg and grep
117
+ # ag includes line numbers by default
118
+
119
+ if tool_name in ["rg"]:
120
+ cmd_args.append("--heading") # Filename above output for ripgrep
121
+
122
+ # Context lines (Before and After)
123
+ if context_before > 0:
124
+ # All tools use -B for lines before
125
+ cmd_args.extend(["-B", str(context_before)])
126
+ if context_after > 0:
127
+ # All tools use -A for lines after
128
+ cmd_args.extend(["-A", str(context_after)])
129
+
130
+ # Case sensitivity
131
+ if case_insensitive:
132
+ cmd_args.append("-i") # Add case-insensitivity flag for all tools
133
+
134
+ # Pattern type (regex vs fixed string)
135
+ if use_regex:
136
+ if tool_name == "grep":
137
+ cmd_args.append("-E") # Use extended regex for grep
138
+ # rg and ag use regex by default, no flag needed for basic ERE
139
+ else:
140
+ if tool_name == "rg":
141
+ cmd_args.append("-F") # Fixed strings for rg
142
+ elif tool_name == "ag":
143
+ cmd_args.append("-Q") # Literal/fixed strings for ag
144
+ elif tool_name == "grep":
145
+ cmd_args.append("-F") # Fixed strings for grep
146
+
147
+ # File filtering
148
+ if (
149
+ file_pattern != "*"
150
+ ): # Avoid adding glob if it's the default '*' which might behave differently
151
+ if tool_name == "rg":
152
+ cmd_args.extend(["-g", file_pattern])
153
+ elif tool_name == "ag":
154
+ cmd_args.extend(["-G", file_pattern])
155
+ elif tool_name == "grep":
156
+ # grep needs recursive flag when filtering
157
+ cmd_args.append("-r")
158
+ cmd_args.append(f"--include={file_pattern}")
159
+ elif tool_name == "grep":
160
+ # grep needs recursive flag even without include filter
161
+ cmd_args.append("-r")
162
+
163
+ # Directory exclusion (rg and ag respect .gitignore/.git by default)
164
+ if tool_name == "grep":
165
+ cmd_args.append("--exclude-dir=.git")
166
+
167
+ # Add pattern and directory path
168
+ cmd_args.extend([pattern, str(search_dir_path)])
169
+
170
+ # Convert list to command string for run_cmd_subprocess
171
+ command_string = oslex.join(cmd_args)
172
+
173
+ coder.io.tool_output(f"⚙️ Executing {tool_name}: {command_string}")
174
+
175
+ # Use run_cmd_subprocess for execution
176
+ # Note: rg, ag, and grep return 1 if no matches are found, which is not an error for this tool.
177
+ exit_status, combined_output = run_cmd_subprocess(
178
+ command_string, verbose=coder.verbose, cwd=coder.root # Execute in the project root
179
+ )
180
+
181
+ # Format the output for the result message
182
+ output_content = combined_output or ""
183
+
184
+ # Handle exit codes (consistent across rg, ag, grep)
185
+ if exit_status == 0:
186
+ # Limit output size if necessary
187
+ max_output_lines = 50 # Consider making this configurable
188
+ output_lines = output_content.splitlines()
189
+ if len(output_lines) > max_output_lines:
190
+ truncated_output = "\n".join(output_lines[:max_output_lines])
191
+ result_message = (
192
+ f"Found matches (truncated):\n```text\n{truncated_output}\n..."
193
+ f" ({len(output_lines) - max_output_lines} more lines)\n```"
194
+ )
195
+ elif not output_content:
196
+ # Should not happen if return code is 0, but handle defensively
197
+ coder.io.tool_warning(f"{tool_name} returned 0 but produced no output.")
198
+ result_message = "No matches found (unexpected)."
199
+ else:
200
+ result_message = f"Found matches:\n```text\n{output_content}\n```"
201
+ return result_message
202
+
203
+ elif exit_status == 1:
204
+ # Exit code 1 means no matches found - this is expected behavior, not an error.
205
+ return "No matches found."
206
+ else:
207
+ # Exit code > 1 indicates an actual error
208
+ error_message = f"{tool_name.capitalize()} command failed with exit code {exit_status}."
209
+ if output_content:
210
+ # Truncate error output as well if it's too long
211
+ error_limit = 1000 # Example limit for error output
212
+ if len(output_content) > error_limit:
213
+ output_content = output_content[:error_limit] + "\n... (error output truncated)"
214
+ error_message += f" Output:\n{output_content}"
215
+ coder.io.tool_error(error_message)
216
+ return f"Error: {error_message}"
217
+
218
+ except Exception as e:
219
+ # Add command_string to the error message if it's defined
220
+ cmd_str_info = f"'{command_string}' " if "command_string" in locals() else ""
221
+ coder.io.tool_error(f"Error executing {tool_name} command {cmd_str_info}: {str(e)}")
222
+ return f"Error executing {tool_name}: {str(e)}"
223
+
224
+
225
+ def process_response(coder, params):
226
+ """
227
+ Process the Grep tool response.
228
+
229
+ Args:
230
+ coder: The Coder instance
231
+ params: Dictionary of parameters
232
+
233
+ Returns:
234
+ str: Result message
235
+ """
236
+ pattern = params.get("pattern")
237
+ file_pattern = params.get("file_pattern", "*") # Default to all files
238
+ directory = params.get("directory", ".") # Default to current directory
239
+ use_regex = params.get("use_regex", False) # Default to literal search
240
+ case_insensitive = params.get("case_insensitive", False) # Default to case-sensitive
241
+ context_before = params.get("context_before", 5)
242
+ context_after = params.get("context_after", 5)
243
+
244
+ if pattern is not None:
245
+ return _execute_grep(
246
+ coder,
247
+ pattern,
248
+ file_pattern,
249
+ directory,
250
+ use_regex,
251
+ case_insensitive,
252
+ context_before,
253
+ context_after,
254
+ )
255
+ else:
256
+ return "Error: Missing required 'pattern' parameter for Grep"
@@ -0,0 +1,221 @@
1
+ from .tool_utils import (
2
+ ToolError,
3
+ apply_change,
4
+ determine_line_range,
5
+ find_pattern_indices,
6
+ format_tool_result,
7
+ generate_unified_diff_snippet,
8
+ handle_tool_error,
9
+ select_occurrence_index,
10
+ validate_file_for_edit,
11
+ )
12
+
13
+ schema = {
14
+ "type": "function",
15
+ "function": {
16
+ "name": "IndentLines",
17
+ "description": "Indent a block of lines in a file.",
18
+ "parameters": {
19
+ "type": "object",
20
+ "properties": {
21
+ "file_path": {"type": "string"},
22
+ "start_pattern": {"type": "string"},
23
+ "end_pattern": {"type": "string"},
24
+ "line_count": {"type": "integer"},
25
+ "indent_levels": {"type": "integer", "default": 1},
26
+ "near_context": {"type": "string"},
27
+ "occurrence": {"type": "integer", "default": 1},
28
+ "change_id": {"type": "string"},
29
+ "dry_run": {"type": "boolean", "default": False},
30
+ },
31
+ "required": ["file_path", "start_pattern"],
32
+ },
33
+ },
34
+ }
35
+
36
+ # Normalized tool name for lookup
37
+ NORM_NAME = "indentlines"
38
+
39
+
40
+ def _execute_indent_lines(
41
+ coder,
42
+ file_path,
43
+ start_pattern,
44
+ end_pattern=None,
45
+ line_count=None,
46
+ indent_levels=1,
47
+ near_context=None,
48
+ occurrence=1,
49
+ change_id=None,
50
+ dry_run=False,
51
+ ):
52
+ """
53
+ Indent or unindent a block of lines in a file using utility functions.
54
+
55
+ Parameters:
56
+ - coder: The Coder instance
57
+ - file_path: Path to the file to modify
58
+ - start_pattern: Pattern marking the start of the block to indent (line containing this pattern)
59
+ - end_pattern: Optional pattern marking the end of the block (line containing this pattern)
60
+ - line_count: Optional number of lines to indent (alternative to end_pattern)
61
+ - indent_levels: Number of levels to indent (positive) or unindent (negative)
62
+ - near_context: Optional text nearby to help locate the correct instance of the start_pattern
63
+ - occurrence: Which occurrence of the start_pattern to use (1-based index, or -1 for last)
64
+ - change_id: Optional ID for tracking the change
65
+ - dry_run: If True, simulate the change without modifying the file
66
+
67
+ Returns a result message.
68
+ """
69
+ tool_name = "IndentLines"
70
+ try:
71
+ # 1. Validate file and get content
72
+ abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
73
+ lines = original_content.splitlines()
74
+
75
+ # 2. Find the start line
76
+ pattern_desc = f"Start pattern '{start_pattern}'"
77
+ if near_context:
78
+ pattern_desc += f" near context '{near_context}'"
79
+ start_pattern_indices = find_pattern_indices(lines, start_pattern, near_context)
80
+ start_line_idx = select_occurrence_index(start_pattern_indices, occurrence, pattern_desc)
81
+
82
+ # 3. Determine the end line
83
+ start_line, end_line = determine_line_range(
84
+ coder=coder,
85
+ file_path=rel_path,
86
+ lines=lines,
87
+ start_pattern_line_index=start_line_idx,
88
+ end_pattern=end_pattern,
89
+ line_count=line_count,
90
+ target_symbol=None, # IndentLines uses patterns, not symbols
91
+ pattern_desc=pattern_desc,
92
+ )
93
+
94
+ # 4. Validate and prepare indentation
95
+ try:
96
+ indent_levels = int(indent_levels)
97
+ except ValueError:
98
+ raise ToolError(f"Invalid indent_levels value: '{indent_levels}'. Must be an integer.")
99
+
100
+ indent_str = " " * 4 # Assume 4 spaces per level
101
+ modified_lines = list(lines)
102
+
103
+ # Apply indentation logic (core logic remains)
104
+ for i in range(start_line, end_line + 1):
105
+ if indent_levels > 0:
106
+ modified_lines[i] = (indent_str * indent_levels) + modified_lines[i]
107
+ elif indent_levels < 0:
108
+ spaces_to_remove = abs(indent_levels) * len(indent_str)
109
+ current_leading_spaces = len(modified_lines[i]) - len(modified_lines[i].lstrip(" "))
110
+ actual_remove = min(spaces_to_remove, current_leading_spaces)
111
+ if actual_remove > 0:
112
+ modified_lines[i] = modified_lines[i][actual_remove:]
113
+
114
+ new_content = "\n".join(modified_lines)
115
+
116
+ if original_content == new_content:
117
+ coder.io.tool_warning("No changes made: indentation would not change file")
118
+ return "Warning: No changes made (indentation would not change file)"
119
+
120
+ # 5. Generate diff for feedback
121
+ diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path)
122
+ num_occurrences = len(start_pattern_indices)
123
+ occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
124
+ action = "indent" if indent_levels > 0 else "unindent"
125
+ levels = abs(indent_levels)
126
+ level_text = "level" if levels == 1 else "levels"
127
+ num_lines = end_line - start_line + 1
128
+
129
+ # 6. Handle dry run
130
+ if dry_run:
131
+ dry_run_message = (
132
+ f"Dry run: Would {action} {num_lines} lines ({start_line + 1}-{end_line + 1}) by"
133
+ f" {levels} {level_text} (based on {occurrence_str}start pattern '{start_pattern}')"
134
+ f" in {file_path}."
135
+ )
136
+ return format_tool_result(
137
+ coder,
138
+ tool_name,
139
+ "",
140
+ dry_run=True,
141
+ dry_run_message=dry_run_message,
142
+ diff_snippet=diff_snippet,
143
+ )
144
+
145
+ # 7. Apply Change (Not dry run)
146
+ metadata = {
147
+ "start_line": start_line + 1,
148
+ "end_line": end_line + 1,
149
+ "start_pattern": start_pattern,
150
+ "end_pattern": end_pattern,
151
+ "line_count": line_count,
152
+ "indent_levels": indent_levels,
153
+ "near_context": near_context,
154
+ "occurrence": occurrence,
155
+ }
156
+ final_change_id = apply_change(
157
+ coder,
158
+ abs_path,
159
+ rel_path,
160
+ original_content,
161
+ new_content,
162
+ "indentlines",
163
+ metadata,
164
+ change_id,
165
+ )
166
+
167
+ coder.files_edited_by_tools.add(rel_path)
168
+
169
+ # 8. Format and return result
170
+ action_past = "Indented" if indent_levels > 0 else "Unindented"
171
+ success_message = (
172
+ f"{action_past} {num_lines} lines by {levels} {level_text} (from {occurrence_str}start"
173
+ f" pattern) in {file_path}"
174
+ )
175
+ return format_tool_result(
176
+ coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
177
+ )
178
+ except ToolError as e:
179
+ # Handle errors raised by utility functions (expected errors)
180
+ return handle_tool_error(coder, tool_name, e, add_traceback=False)
181
+ except Exception as e:
182
+ # Handle unexpected errors
183
+ return handle_tool_error(coder, tool_name, e)
184
+
185
+
186
+ def process_response(coder, params):
187
+ """
188
+ Process the IndentLines tool response.
189
+
190
+ Args:
191
+ coder: The Coder instance
192
+ params: Dictionary of parameters
193
+
194
+ Returns:
195
+ str: Result message
196
+ """
197
+ file_path = params.get("file_path")
198
+ start_pattern = params.get("start_pattern")
199
+ end_pattern = params.get("end_pattern")
200
+ line_count = params.get("line_count")
201
+ indent_levels = params.get("indent_levels", 1)
202
+ near_context = params.get("near_context")
203
+ occurrence = params.get("occurrence", 1)
204
+ change_id = params.get("change_id")
205
+ dry_run = params.get("dry_run", False)
206
+
207
+ if file_path is not None and start_pattern is not None:
208
+ return _execute_indent_lines(
209
+ coder,
210
+ file_path,
211
+ start_pattern,
212
+ end_pattern,
213
+ line_count,
214
+ indent_levels,
215
+ near_context,
216
+ occurrence,
217
+ change_id,
218
+ dry_run,
219
+ )
220
+ else:
221
+ return "Error: Missing required parameters for IndentLines (file_path, start_pattern)"