mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-10 22:55:00 +00:00
Merge branch 'main' into call-graph
This commit is contained in:
commit
1e1feeaa21
9 changed files with 222 additions and 92 deletions
|
@ -168,7 +168,10 @@ class Coder:
|
|||
if self.abs_fnames:
|
||||
files_content = prompts.files_content_prefix
|
||||
files_content += self.get_files_content()
|
||||
all_content += files_content
|
||||
else:
|
||||
files_content = prompts.files_no_full_files
|
||||
|
||||
all_content += files_content
|
||||
|
||||
other_files = set(self.get_all_abs_files()) - set(self.abs_fnames)
|
||||
repo_content = self.repo_map.get_repo_map(self.abs_fnames, other_files)
|
||||
|
@ -204,7 +207,7 @@ class Coder:
|
|||
self.num_control_c += 1
|
||||
if self.num_control_c >= 2:
|
||||
break
|
||||
self.io.tool_error("^C again to quit")
|
||||
self.io.tool_error("^C again or /exit to quit")
|
||||
except EOFError:
|
||||
return
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import sys
|
||||
import os
|
||||
import git
|
||||
import subprocess
|
||||
import shlex
|
||||
from rich.prompt import Confirm
|
||||
from prompt_toolkit.completion import Completion
|
||||
from aider import prompts
|
||||
|
||||
|
@ -16,20 +16,8 @@ class Commands:
|
|||
if inp[0] == "/":
|
||||
return True
|
||||
|
||||
def help(self):
|
||||
"Show help about all commands"
|
||||
commands = self.get_commands()
|
||||
for cmd in commands:
|
||||
cmd_method_name = f"cmd_{cmd[1:]}"
|
||||
cmd_method = getattr(self, cmd_method_name, None)
|
||||
if cmd_method:
|
||||
description = cmd_method.__doc__
|
||||
self.io.tool(f"{cmd} {description}")
|
||||
else:
|
||||
self.io.tool(f"{cmd} No description available.")
|
||||
|
||||
def get_commands(self):
|
||||
commands = ["/help"]
|
||||
commands = []
|
||||
for attr in dir(self):
|
||||
if attr.startswith("cmd_"):
|
||||
commands.append("/" + attr[4:])
|
||||
|
@ -62,15 +50,15 @@ class Commands:
|
|||
all_commands = self.get_commands()
|
||||
matching_commands = [cmd for cmd in all_commands if cmd.startswith(first_word)]
|
||||
if len(matching_commands) == 1:
|
||||
if matching_commands[0] == "/help":
|
||||
self.help()
|
||||
else:
|
||||
return self.do_run(matching_commands[0][1:], rest_inp)
|
||||
return self.do_run(matching_commands[0][1:], rest_inp)
|
||||
elif len(matching_commands) > 1:
|
||||
self.io.tool_error("Ambiguous command: ', '.join(matching_commands)}")
|
||||
self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}")
|
||||
else:
|
||||
self.io.tool_error(f"Error: {first_word} is not a valid command.")
|
||||
|
||||
# any method called cmd_xxx becomes a command automatically.
|
||||
# each one must take an args param.
|
||||
|
||||
def cmd_commit(self, args):
|
||||
"Commit edits to the repo made outside the chat (commit message optional)"
|
||||
|
||||
|
@ -251,6 +239,10 @@ class Commands:
|
|||
)
|
||||
return msg
|
||||
|
||||
def cmd_exit(self, args):
|
||||
"Exit the application"
|
||||
sys.exit()
|
||||
|
||||
def cmd_ls(self, args):
|
||||
"List all known files and those included in the chat session"
|
||||
|
||||
|
@ -274,3 +266,15 @@ class Commands:
|
|||
self.io.tool("\nRepo files not in the chat:\n")
|
||||
for file in other_files:
|
||||
self.io.tool(f" {file}")
|
||||
|
||||
def cmd_help(self, args):
|
||||
"Show help about all commands"
|
||||
commands = sorted(self.get_commands())
|
||||
for cmd in commands:
|
||||
cmd_method_name = f"cmd_{cmd[1:]}"
|
||||
cmd_method = getattr(self, cmd_method_name, None)
|
||||
if cmd_method:
|
||||
description = cmd_method.__doc__
|
||||
self.io.tool(f"{cmd} {description}")
|
||||
else:
|
||||
self.io.tool(f"{cmd} No description available.")
|
||||
|
|
109
aider/main.py
109
aider/main.py
|
@ -1,47 +1,85 @@
|
|||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import git
|
||||
import configargparse
|
||||
from dotenv import load_dotenv
|
||||
from aider.coder import Coder
|
||||
from aider.io import InputOutput
|
||||
|
||||
|
||||
def get_git_root():
|
||||
try:
|
||||
repo = git.Repo(search_parent_directories=True)
|
||||
return repo.working_tree_dir
|
||||
except git.InvalidGitRepositoryError:
|
||||
return None
|
||||
|
||||
|
||||
def main(args=None, input=None, output=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
load_dotenv()
|
||||
env_prefix = "AIDER_"
|
||||
parser = argparse.ArgumentParser(description="aider - chat with GPT about your code")
|
||||
|
||||
default_config_files = [
|
||||
os.path.expanduser("~/.aider.conf.yml"),
|
||||
]
|
||||
git_root = get_git_root()
|
||||
if git_root:
|
||||
default_config_files.insert(0, os.path.join(git_root, ".aider.conf.yml"))
|
||||
|
||||
parser = configargparse.ArgumentParser(
|
||||
description="aider - chat with GPT about your code",
|
||||
add_config_file_help=True,
|
||||
default_config_files=default_config_files,
|
||||
config_file_parser_class=configargparse.YAMLConfigFileParser,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
is_config_file=True,
|
||||
metavar="CONFIG_FILE",
|
||||
help=(
|
||||
"Specify the config file (default: search for .aider.conf.yml in git root or home"
|
||||
" directory)"
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"files",
|
||||
metavar="FILE",
|
||||
nargs="*",
|
||||
help="a list of source code files (optional)",
|
||||
)
|
||||
default_input_history_file = (
|
||||
os.path.join(git_root, ".aider.input.history") if git_root else ".aider.input.history"
|
||||
)
|
||||
default_chat_history_file = (
|
||||
os.path.join(git_root, ".aider.chat.history.md") if git_root else ".aider.chat.history.md"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--input-history-file",
|
||||
metavar="INPUT_HISTORY_FILE",
|
||||
default=os.environ.get(f"{env_prefix}INPUT_HISTORY_FILE", ".aider.input.history"),
|
||||
help=(
|
||||
"Specify the chat input history file (default: .aider.input.history,"
|
||||
f" ${env_prefix}INPUT_HISTORY_FILE)"
|
||||
),
|
||||
env_var=f"{env_prefix}INPUT_HISTORY_FILE",
|
||||
default=default_input_history_file,
|
||||
help=f"Specify the chat input history file (default: {default_input_history_file})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--chat-history-file",
|
||||
metavar="CHAT_HISTORY_FILE",
|
||||
default=os.environ.get(f"{env_prefix}CHAT_HISTORY_FILE", ".aider.chat.history.md"),
|
||||
help=(
|
||||
"Specify the chat history file (default: .aider.chat.history.md,"
|
||||
f" ${env_prefix}CHAT_HISTORY_FILE)"
|
||||
),
|
||||
env_var=f"{env_prefix}CHAT_HISTORY_FILE",
|
||||
default=default_chat_history_file,
|
||||
help=f"Specify the chat history file (default: {default_chat_history_file})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
metavar="MODEL",
|
||||
default=os.environ.get(f"{env_prefix}MODEL", "gpt-4"),
|
||||
help=f"Specify the model to use for the main chat (default: gpt-4, ${env_prefix}MODEL)",
|
||||
env_var=f"{env_prefix}MODEL",
|
||||
default="gpt-4",
|
||||
help="Specify the model to use for the main chat (default: gpt-4)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-3",
|
||||
|
@ -50,24 +88,38 @@ def main(args=None, input=None, output=None):
|
|||
const="gpt-3.5-turbo",
|
||||
help="Use gpt-3.5-turbo model for the main chat (not advised)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pretty",
|
||||
action="store_true",
|
||||
env_var=f"{env_prefix}PRETTY",
|
||||
default=True,
|
||||
help="Enable pretty, colorized output (default: True)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-pretty",
|
||||
action="store_false",
|
||||
dest="pretty",
|
||||
help=f"Disable pretty, colorized output (${env_prefix}PRETTY)",
|
||||
default=bool(int(os.environ.get(f"{env_prefix}PRETTY", 1))),
|
||||
help="Disable pretty, colorized output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--apply",
|
||||
metavar="FILE",
|
||||
help="Apply the changes from the given file instead of running the chat (debug)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-commits",
|
||||
action="store_true",
|
||||
env_var=f"{env_prefix}AUTO_COMMIT",
|
||||
default=True,
|
||||
help="Enable auto commit of changes (default: True)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-auto-commits",
|
||||
action="store_false",
|
||||
dest="auto_commits",
|
||||
help=f"Disable auto commit of changes (${env_prefix}AUTO_COMMITS)",
|
||||
default=bool(int(os.environ.get(f"{env_prefix}AUTO_COMMITS", 1))),
|
||||
dest="auto_commit",
|
||||
help="Disable auto commit of changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
|
@ -78,17 +130,21 @@ def main(args=None, input=None, output=None):
|
|||
parser.add_argument(
|
||||
"--show-diffs",
|
||||
action="store_true",
|
||||
help=f"Show diffs when committing changes (default: False, ${env_prefix}SHOW_DIFFS)",
|
||||
default=bool(int(os.environ.get(f"{env_prefix}SHOW_DIFFS", 0))),
|
||||
env_var=f"{env_prefix}SHOW_DIFFS",
|
||||
help="Show diffs when committing changes (default: False)",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ctags",
|
||||
action="store_true",
|
||||
type=lambda x: (str(x).lower() == "true"),
|
||||
nargs="?",
|
||||
const=True,
|
||||
default=None,
|
||||
env_var=f"{env_prefix}CTAGS",
|
||||
help=(
|
||||
"Add ctags to the chat to help GPT understand the codebase (default: False,"
|
||||
f" ${env_prefix}CTAGS)"
|
||||
"Add ctags to the chat to help GPT understand the codebase (default: check for ctags"
|
||||
" executable)"
|
||||
),
|
||||
default=bool(int(os.environ.get(f"{env_prefix}CTAGS", 0))),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--yes",
|
||||
|
@ -97,7 +153,8 @@ def main(args=None, input=None, output=None):
|
|||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Enable verbose output",
|
||||
default=False,
|
||||
|
|
|
@ -8,14 +8,12 @@ Take requests for changes to the supplied code.
|
|||
If the request is ambiguous, ask questions.
|
||||
|
||||
Once you understand the request you MUST:
|
||||
1. List the files you need to modify.
|
||||
1. List the files you need to modify. If they are *read-only* ask the user to make them *read-write* using the file's full path name.
|
||||
2. Think step-by-step and explain the needed changes.
|
||||
3. Describe each change with an *edit block* per the example below.
|
||||
"""
|
||||
|
||||
system_reminder = """Base any edits off the files shown in the user's last msg.
|
||||
|
||||
You MUST format EVERY code change with an *edit block* like this:
|
||||
system_reminder = """You MUST format EVERY code change with an *edit block* like this:
|
||||
|
||||
```python
|
||||
some/dir/example.py
|
||||
|
@ -29,11 +27,11 @@ some/dir/example.py
|
|||
def add(a,b):
|
||||
>>>>>>> UPDATED
|
||||
|
||||
Every *edit block* must be fenced w/triple backticks with the correct code language.
|
||||
Every *edit block* must start with the full path! *NEVER* propose edit blocks for *read-only* files.
|
||||
The ORIGINAL section must be an *exact* set of lines from the file:
|
||||
- NEVER SKIP LINES!
|
||||
- Include all original leading spaces and indentation!
|
||||
Every *edit block* must be fenced w/triple backticks with the correct code language.
|
||||
Every *edit block* must start with the full path!
|
||||
|
||||
Edits to different parts of a file each need their own *edit block*.
|
||||
|
||||
|
@ -54,11 +52,13 @@ files_content_gpt_no_edits = "I didn't see any properly formatted edits in your
|
|||
|
||||
files_content_local_edits = "I edited the files myself."
|
||||
|
||||
files_content_prefix = "Propose changes to *only* these files (ask before editing others):\n"
|
||||
files_content_prefix = "These are the *read-write* files:\n"
|
||||
|
||||
files_no_full_files = "I am not sharing any *read-write* files yet."
|
||||
|
||||
repo_content_prefix = (
|
||||
"Here is a map of all the {other}files{ctags_msg}. You *must* ask with the"
|
||||
" full path before editing these:\n\n"
|
||||
"All the files below here are *read-only* files. Notice that files in directories are indented."
|
||||
" Use their parent dirs to build their full path.\n"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
import sys
|
||||
import subprocess
|
||||
import tiktoken
|
||||
import tempfile
|
||||
from collections import defaultdict
|
||||
|
||||
from aider import prompts, utils
|
||||
|
@ -48,14 +49,20 @@ def fname_to_components(fname, with_colon):
|
|||
|
||||
|
||||
class RepoMap:
|
||||
def __init__(self, use_ctags=True, root=None, main_model="gpt-4"):
|
||||
ctags_cmd = ["ctags", "--fields=+S", "--extras=-F", "--output-format=json"]
|
||||
|
||||
def __init__(self, use_ctags=None, root=None, main_model="gpt-4"):
|
||||
if not root:
|
||||
root = os.getcwd()
|
||||
|
||||
self.use_ctags = use_ctags
|
||||
self.tokenizer = tiktoken.encoding_for_model(main_model)
|
||||
self.root = root
|
||||
|
||||
if use_ctags is None:
|
||||
self.use_ctags = self.check_for_ctags()
|
||||
else:
|
||||
self.use_ctags = use_ctags
|
||||
|
||||
self.tokenizer = tiktoken.encoding_for_model(main_model)
|
||||
|
||||
def get_repo_map(self, chat_files, other_files):
|
||||
res = self.choose_files_listing(other_files)
|
||||
if not res:
|
||||
|
@ -123,7 +130,7 @@ class RepoMap:
|
|||
|
||||
def split_path(self, path):
|
||||
path = os.path.relpath(path, self.root)
|
||||
return fname_to_components(path, True)
|
||||
return [path + ":"]
|
||||
|
||||
def run_ctags(self, filename):
|
||||
# Check if the file is in the cache and if the modification time has not changed
|
||||
|
@ -132,7 +139,7 @@ class RepoMap:
|
|||
if cache_key in TAGS_CACHE and TAGS_CACHE[cache_key]["mtime"] == file_mtime:
|
||||
return TAGS_CACHE[cache_key]["data"]
|
||||
|
||||
cmd = ["ctags", "--fields=+S", "--extras=-F", "--output-format=json", filename]
|
||||
cmd = self.ctags_cmd + [filename]
|
||||
output = subprocess.check_output(cmd).decode("utf-8")
|
||||
output = output.splitlines()
|
||||
|
||||
|
@ -169,6 +176,17 @@ class RepoMap:
|
|||
|
||||
return tags
|
||||
|
||||
def check_for_ctags(self):
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
hello_py = os.path.join(tempdir, "hello.py")
|
||||
with open(hello_py, "w") as f:
|
||||
f.write("def hello():\n print('Hello, world!')\n")
|
||||
self.get_tags(hello_py)
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def find_py_files(directory):
|
||||
if not os.path.isdir(directory):
|
||||
|
@ -197,6 +215,7 @@ def call_map():
|
|||
"""
|
||||
|
||||
rm = RepoMap()
|
||||
|
||||
# res = rm.get_tags_map(fnames)
|
||||
# print(res)
|
||||
|
||||
|
@ -222,7 +241,7 @@ def call_map():
|
|||
# dump("ref", fname, ident)
|
||||
references[ident].append(show_fname)
|
||||
|
||||
for ident,fname in defines.items():
|
||||
for ident, fname in defines.items():
|
||||
dump(fname, ident)
|
||||
|
||||
idents = set(defines.keys()).intersection(set(references.keys()))
|
||||
|
@ -256,7 +275,9 @@ def call_map():
|
|||
ranked = nx.pagerank(G, weight="weight")
|
||||
|
||||
# drop low weight edges for plotting
|
||||
edges_to_remove = [(node1, node2) for node1, node2, data in G.edges(data=True) if data['weight'] < 1]
|
||||
edges_to_remove = [
|
||||
(node1, node2) for node1, node2, data in G.edges(data=True) if data["weight"] < 1
|
||||
]
|
||||
G.remove_edges_from(edges_to_remove)
|
||||
# Remove isolated nodes (nodes with no edges)
|
||||
dump(G.nodes())
|
||||
|
@ -272,8 +293,8 @@ def call_map():
|
|||
dot.node(fname, penwidth=str(pen))
|
||||
|
||||
max_w = max(edges.values())
|
||||
for refs,defs,data in G.edges(data=True):
|
||||
weight = data['weight']
|
||||
for refs, defs, data in G.edges(data=True):
|
||||
weight = data["weight"]
|
||||
|
||||
r = random.randint(0, 255)
|
||||
g = random.randint(0, 255)
|
||||
|
@ -286,7 +307,7 @@ def call_map():
|
|||
print()
|
||||
print(name)
|
||||
for ident in sorted(labels[name]):
|
||||
print('\t', ident)
|
||||
print("\t", ident)
|
||||
# print(f"{refs} -{weight}-> {defs}")
|
||||
|
||||
top_rank = sorted([(rank, node) for (node, rank) in ranked.items()], reverse=True)
|
||||
|
@ -296,5 +317,6 @@ def call_map():
|
|||
|
||||
dot.render("tmp", format="pdf", view=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
call_map()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue