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
27bd993cab
26 changed files with 788 additions and 202 deletions
101
aider/coder.py
101
aider/coder.py
|
@ -2,25 +2,25 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
from openai.error import RateLimitError
|
||||
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
from rich.markdown import Markdown
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
import git
|
||||
import openai
|
||||
from openai.error import RateLimitError
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
from rich.markdown import Markdown
|
||||
|
||||
# from aider.dump import dump
|
||||
from aider import utils
|
||||
from aider import prompts
|
||||
from aider import prompts, utils
|
||||
from aider.commands import Commands
|
||||
from aider.repomap import RepoMap
|
||||
|
||||
openai.api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
class MissingAPIKeyError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class Coder:
|
||||
|
@ -37,10 +37,17 @@ class Coder:
|
|||
pretty=True,
|
||||
show_diffs=False,
|
||||
auto_commits=True,
|
||||
dirty_commits=True,
|
||||
dry_run=False,
|
||||
use_ctags=False,
|
||||
verbose=False,
|
||||
openai_api_key=None,
|
||||
):
|
||||
if openai_api_key:
|
||||
openai.api_key = openai_api_key
|
||||
else:
|
||||
raise MissingAPIKeyError("No OpenAI API key provided.")
|
||||
|
||||
self.verbose = verbose
|
||||
self.abs_fnames = set()
|
||||
self.cur_messages = []
|
||||
|
@ -48,6 +55,7 @@ class Coder:
|
|||
|
||||
self.io = io
|
||||
self.auto_commits = auto_commits
|
||||
self.dirty_commits = dirty_commits
|
||||
self.dry_run = dry_run
|
||||
|
||||
if pretty:
|
||||
|
@ -66,7 +74,7 @@ class Coder:
|
|||
|
||||
if self.repo:
|
||||
rel_repo_dir = os.path.relpath(self.repo.git_dir, os.getcwd())
|
||||
self.io.tool("Using git repo:", rel_repo_dir)
|
||||
self.io.tool_output("Using git repo:", rel_repo_dir)
|
||||
else:
|
||||
self.io.tool_error("No suitable git repo, will not automatically commit edits.")
|
||||
self.find_common_root()
|
||||
|
@ -83,7 +91,7 @@ class Coder:
|
|||
else:
|
||||
self.root = os.getcwd()
|
||||
|
||||
self.io.tool(f"Common root directory: {self.root}")
|
||||
self.io.tool_output(f"Common root directory: {self.root}")
|
||||
|
||||
def set_repo(self, cmd_line_fnames):
|
||||
if not cmd_line_fnames:
|
||||
|
@ -93,7 +101,7 @@ class Coder:
|
|||
for fname in cmd_line_fnames:
|
||||
fname = Path(fname)
|
||||
if not fname.exists():
|
||||
self.io.tool(f"Creating empty file {fname}")
|
||||
self.io.tool_output(f"Creating empty file {fname}")
|
||||
fname.parent.mkdir(parents=True, exist_ok=True)
|
||||
fname.touch()
|
||||
|
||||
|
@ -106,7 +114,7 @@ class Coder:
|
|||
if fname.is_dir():
|
||||
continue
|
||||
|
||||
self.io.tool(f"Added {fname} to the chat")
|
||||
self.io.tool_output(f"Added {fname} to the chat")
|
||||
|
||||
fname = fname.resolve()
|
||||
self.abs_fnames.add(str(fname))
|
||||
|
@ -135,18 +143,18 @@ class Coder:
|
|||
if new_files:
|
||||
rel_repo_dir = os.path.relpath(repo.git_dir, os.getcwd())
|
||||
|
||||
self.io.tool(f"Files not tracked in {rel_repo_dir}:")
|
||||
self.io.tool_output(f"Files not tracked in {rel_repo_dir}:")
|
||||
for fn in new_files:
|
||||
self.io.tool(f" - {fn}")
|
||||
self.io.tool_output(f" - {fn}")
|
||||
if self.io.confirm_ask("Add them?"):
|
||||
for relative_fname in new_files:
|
||||
repo.git.add(relative_fname)
|
||||
self.io.tool(f"Added {relative_fname} to the git repo")
|
||||
self.io.tool_output(f"Added {relative_fname} to the git repo")
|
||||
show_files = ", ".join(new_files)
|
||||
commit_message = f"Added new files to the git repo: {show_files}"
|
||||
repo.git.commit("-m", commit_message, "--no-verify")
|
||||
commit_hash = repo.head.commit.hexsha[:7]
|
||||
self.io.tool(f"Commit {commit_hash} {commit_message}")
|
||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}")
|
||||
else:
|
||||
self.io.tool_error("Skipped adding new files to the git repo.")
|
||||
return
|
||||
|
@ -216,7 +224,7 @@ class Coder:
|
|||
if is_commit_command:
|
||||
return
|
||||
|
||||
if not self.auto_commits:
|
||||
if not self.dirty_commits:
|
||||
return
|
||||
if not self.repo:
|
||||
return
|
||||
|
@ -227,7 +235,12 @@ class Coder:
|
|||
return True
|
||||
|
||||
def run_loop(self):
|
||||
inp = self.io.get_input(self.abs_fnames, self.commands)
|
||||
inp = self.io.get_input(
|
||||
self.root,
|
||||
self.get_inchat_relative_files(),
|
||||
self.get_addable_relative_files(),
|
||||
self.commands,
|
||||
)
|
||||
|
||||
self.num_control_c = 0
|
||||
|
||||
|
@ -243,7 +256,7 @@ class Coder:
|
|||
self.cur_messages = []
|
||||
|
||||
if inp.strip():
|
||||
self.io.tool("Use up-arrow to retry previous command:", inp)
|
||||
self.io.tool_output("Use up-arrow to retry previous command:", inp)
|
||||
return
|
||||
|
||||
if not inp:
|
||||
|
@ -252,6 +265,8 @@ class Coder:
|
|||
if self.commands.is_command(inp):
|
||||
return self.commands.run(inp)
|
||||
|
||||
self.check_for_file_mentions(inp)
|
||||
|
||||
return self.send_new_user_message(inp)
|
||||
|
||||
def send_new_user_message(self, inp):
|
||||
|
@ -279,7 +294,7 @@ class Coder:
|
|||
dict(role="assistant", content=content),
|
||||
]
|
||||
|
||||
self.io.tool()
|
||||
self.io.tool_output()
|
||||
if interrupted:
|
||||
return
|
||||
|
||||
|
@ -327,20 +342,29 @@ class Coder:
|
|||
quotes = "".join(['"', "'", "`"])
|
||||
words = set(word.strip(quotes) for word in words)
|
||||
|
||||
addable_rel_fnames = set(self.get_all_relative_files()) - set(
|
||||
self.get_inchat_relative_files()
|
||||
)
|
||||
addable_rel_fnames = self.get_addable_relative_files()
|
||||
|
||||
mentioned_rel_fnames = set()
|
||||
for word in words:
|
||||
if word in addable_rel_fnames:
|
||||
mentioned_rel_fnames.add(word)
|
||||
fname_to_rel_fnames = {}
|
||||
for rel_fname in addable_rel_fnames:
|
||||
fname = os.path.basename(rel_fname)
|
||||
if fname not in fname_to_rel_fnames:
|
||||
fname_to_rel_fnames[fname] = []
|
||||
fname_to_rel_fnames[fname].append(rel_fname)
|
||||
|
||||
for fname, rel_fnames in fname_to_rel_fnames.items():
|
||||
if len(rel_fnames) == 1 and fname in words:
|
||||
mentioned_rel_fnames.add(rel_fnames[0])
|
||||
else:
|
||||
for rel_fname in rel_fnames:
|
||||
if rel_fname in words:
|
||||
mentioned_rel_fnames.add(rel_fname)
|
||||
|
||||
if not mentioned_rel_fnames:
|
||||
return
|
||||
|
||||
for rel_fname in mentioned_rel_fnames:
|
||||
self.io.tool(rel_fname)
|
||||
self.io.tool_output(rel_fname)
|
||||
|
||||
if not self.io.confirm_ask("Add these files to the chat?"):
|
||||
return
|
||||
|
@ -450,9 +474,9 @@ class Coder:
|
|||
edited.add(path)
|
||||
if utils.do_replace(full_path, original, updated, self.dry_run):
|
||||
if self.dry_run:
|
||||
self.io.tool(f"Dry run, did not apply edit to {path}")
|
||||
self.io.tool_output(f"Dry run, did not apply edit to {path}")
|
||||
else:
|
||||
self.io.tool(f"Applied edit to {path}")
|
||||
self.io.tool_output(f"Applied edit to {path}")
|
||||
else:
|
||||
self.io.tool_error(f"Failed to apply edit to {path}")
|
||||
|
||||
|
@ -490,7 +514,9 @@ class Coder:
|
|||
)
|
||||
return
|
||||
|
||||
commit_message = commit_message.strip().strip('"').strip()
|
||||
commit_message = commit_message.strip()
|
||||
if commit_message and commit_message[0] == '"' and commit_message[-1] == '"':
|
||||
commit_message = commit_message[1:-1].strip()
|
||||
|
||||
if interrupted:
|
||||
self.io.tool_error(
|
||||
|
@ -548,7 +574,7 @@ class Coder:
|
|||
raise ValueError(f"Invalid value for 'which': {which}")
|
||||
|
||||
if self.show_diffs or ask:
|
||||
# don't use io.tool() because we don't want to log or further colorize
|
||||
# don't use io.tool_output() because we don't want to log or further colorize
|
||||
print(diffs)
|
||||
|
||||
context = self.get_context_from_history(history)
|
||||
|
@ -562,9 +588,9 @@ class Coder:
|
|||
|
||||
if ask:
|
||||
if which == "repo_files":
|
||||
self.io.tool("Git repo has uncommitted changes.")
|
||||
self.io.tool_output("Git repo has uncommitted changes.")
|
||||
else:
|
||||
self.io.tool("Files have uncommitted changes.")
|
||||
self.io.tool_output("Files have uncommitted changes.")
|
||||
|
||||
res = self.io.prompt_ask(
|
||||
"Commit before the chat proceeds [y/n/commit message]?",
|
||||
|
@ -572,7 +598,7 @@ class Coder:
|
|||
).strip()
|
||||
self.last_asked_for_commit_time = self.get_last_modified()
|
||||
|
||||
self.io.tool()
|
||||
self.io.tool_output()
|
||||
|
||||
if res.lower() in ["n", "no"]:
|
||||
self.io.tool_error("Skipped commmit.")
|
||||
|
@ -585,7 +611,7 @@ class Coder:
|
|||
full_commit_message = commit_message + "\n\n" + context
|
||||
repo.git.commit("-m", full_commit_message, "--no-verify")
|
||||
commit_hash = repo.head.commit.hexsha[:7]
|
||||
self.io.tool(f"Commit {commit_hash} {commit_message}")
|
||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}")
|
||||
|
||||
return commit_hash, commit_message
|
||||
|
||||
|
@ -615,6 +641,9 @@ class Coder:
|
|||
return 0
|
||||
return max(Path(path).stat().st_mtime for path in files)
|
||||
|
||||
def get_addable_relative_files(self):
|
||||
return set(self.get_all_relative_files()) - set(self.get_inchat_relative_files())
|
||||
|
||||
def apply_updates(self, content, inp):
|
||||
try:
|
||||
edited = self.update_files(content, inp)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import sys
|
||||
import os
|
||||
import git
|
||||
import subprocess
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import git
|
||||
from prompt_toolkit.completion import Completion
|
||||
|
||||
from aider import prompts
|
||||
|
||||
|
||||
|
@ -37,7 +39,7 @@ class Commands:
|
|||
if cmd_method:
|
||||
return cmd_method(args)
|
||||
else:
|
||||
self.io.tool(f"Error: Command {cmd_name} not found.")
|
||||
self.io.tool_output(f"Error: Command {cmd_name} not found.")
|
||||
|
||||
def run(self, inp):
|
||||
words = inp.strip().split()
|
||||
|
@ -110,7 +112,7 @@ class Commands:
|
|||
self.io.tool_error("The last commit was not made by aider in this chat session.")
|
||||
return
|
||||
self.coder.repo.git.reset("--hard", "HEAD~1")
|
||||
self.io.tool(
|
||||
self.io.tool_output(
|
||||
f"{last_commit.message.strip()}\n"
|
||||
f"The above commit {self.coder.last_aider_commit_hash} "
|
||||
"was reset and removed from git.\n"
|
||||
|
@ -131,7 +133,7 @@ class Commands:
|
|||
commits = f"{self.coder.last_aider_commit_hash}~1"
|
||||
diff = self.coder.get_diffs(commits, self.coder.last_aider_commit_hash)
|
||||
|
||||
# don't use io.tool() because we don't want to log or further colorize
|
||||
# don't use io.tool_output() because we don't want to log or further colorize
|
||||
print(diff)
|
||||
|
||||
def completions_add(self, partial):
|
||||
|
@ -177,7 +179,7 @@ class Commands:
|
|||
abs_file_path = os.path.abspath(os.path.join(self.coder.root, matched_file))
|
||||
if abs_file_path not in self.coder.abs_fnames:
|
||||
self.coder.abs_fnames.add(abs_file_path)
|
||||
self.io.tool(f"Added {matched_file} to the chat")
|
||||
self.io.tool_output(f"Added {matched_file} to the chat")
|
||||
added_fnames.append(matched_file)
|
||||
else:
|
||||
self.io.tool_error(f"{matched_file} is already in the chat")
|
||||
|
@ -214,7 +216,7 @@ class Commands:
|
|||
for matched_file in matched_files:
|
||||
relative_fname = os.path.relpath(matched_file, self.coder.root)
|
||||
self.coder.abs_fnames.remove(matched_file)
|
||||
self.io.tool(f"Removed {relative_fname} from the chat")
|
||||
self.io.tool_output(f"Removed {relative_fname} from the chat")
|
||||
|
||||
def cmd_run(self, args):
|
||||
"Run a shell command and optionally add the output to the chat"
|
||||
|
@ -229,15 +231,15 @@ class Commands:
|
|||
|
||||
print(combined_output)
|
||||
|
||||
ok = self.io.confirm_ask("Add the output to the chat?", default="y")
|
||||
if not ok:
|
||||
return
|
||||
if self.io.confirm_ask("Add the output to the chat?", default="y"):
|
||||
for line in combined_output.splitlines():
|
||||
self.io.tool_output(line, log_only=True)
|
||||
|
||||
msg = prompts.run_output.format(
|
||||
command=args,
|
||||
output=combined_output,
|
||||
)
|
||||
return msg
|
||||
msg = prompts.run_output.format(
|
||||
command=args,
|
||||
output=combined_output,
|
||||
)
|
||||
return msg
|
||||
|
||||
def cmd_exit(self, args):
|
||||
"Exit the application"
|
||||
|
@ -258,14 +260,14 @@ class Commands:
|
|||
other_files.append(file)
|
||||
|
||||
if chat_files:
|
||||
self.io.tool("Files in chat:\n")
|
||||
self.io.tool_output("Files in chat:\n")
|
||||
for file in chat_files:
|
||||
self.io.tool(f" {file}")
|
||||
self.io.tool_output(f" {file}")
|
||||
|
||||
if other_files:
|
||||
self.io.tool("\nRepo files not in the chat:\n")
|
||||
self.io.tool_output("\nRepo files not in the chat:\n")
|
||||
for file in other_files:
|
||||
self.io.tool(f" {file}")
|
||||
self.io.tool_output(f" {file}")
|
||||
|
||||
def cmd_help(self, args):
|
||||
"Show help about all commands"
|
||||
|
@ -275,6 +277,6 @@ class Commands:
|
|||
cmd_method = getattr(self, cmd_method_name, None)
|
||||
if cmd_method:
|
||||
description = cmd_method.__doc__
|
||||
self.io.tool(f"{cmd} {description}")
|
||||
self.io.tool_output(f"{cmd} {description}")
|
||||
else:
|
||||
self.io.tool(f"{cmd} No description available.")
|
||||
self.io.tool_output(f"{cmd} No description available.")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import traceback
|
||||
import json
|
||||
import traceback
|
||||
|
||||
|
||||
def cvt(s):
|
||||
|
|
106
aider/io.py
106
aider/io.py
|
@ -1,24 +1,49 @@
|
|||
import os
|
||||
from prompt_toolkit.styles import Style
|
||||
from prompt_toolkit.shortcuts import PromptSession, prompt
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from prompt_toolkit.completion import Completer, Completion
|
||||
from prompt_toolkit.history import FileHistory
|
||||
from prompt_toolkit.shortcuts import CompleteStyle
|
||||
from prompt_toolkit.shortcuts import CompleteStyle, PromptSession, prompt
|
||||
from prompt_toolkit.styles import Style
|
||||
from pygments.lexers import guess_lexer_for_filename
|
||||
from pygments.token import Token
|
||||
from pygments.util import ClassNotFound
|
||||
from rich.console import Console
|
||||
from rich.text import Text
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from aider import utils
|
||||
|
||||
|
||||
class FileContentCompleter(Completer):
|
||||
def __init__(self, fnames, commands):
|
||||
def __init__(self, root, rel_fnames, addable_rel_fnames, commands):
|
||||
self.commands = commands
|
||||
self.addable_rel_fnames = addable_rel_fnames
|
||||
self.rel_fnames = rel_fnames
|
||||
|
||||
fname_to_rel_fnames = defaultdict(list)
|
||||
for rel_fname in addable_rel_fnames:
|
||||
fname = os.path.basename(rel_fname)
|
||||
if fname != rel_fname:
|
||||
fname_to_rel_fnames[fname].append(rel_fname)
|
||||
self.fname_to_rel_fnames = fname_to_rel_fnames
|
||||
|
||||
self.words = set()
|
||||
for fname in fnames:
|
||||
self.words.update(utils.get_name_identifiers(fname))
|
||||
|
||||
for rel_fname in addable_rel_fnames:
|
||||
self.words.add(rel_fname)
|
||||
|
||||
for rel_fname in rel_fnames:
|
||||
self.words.add(rel_fname)
|
||||
|
||||
fname = os.path.join(root, rel_fname)
|
||||
with open(fname, "r") as f:
|
||||
content = f.read()
|
||||
try:
|
||||
lexer = guess_lexer_for_filename(fname, content)
|
||||
except ClassNotFound:
|
||||
continue
|
||||
tokens = list(lexer.get_tokens(content))
|
||||
self.words.update(token[1] for token in tokens if token[0] in Token.Name)
|
||||
|
||||
def get_completions(self, document, complete_event):
|
||||
text = document.text_before_cursor
|
||||
|
@ -35,11 +60,17 @@ class FileContentCompleter(Completer):
|
|||
return
|
||||
else:
|
||||
candidates = self.words
|
||||
candidates.update(set(self.fname_to_rel_fnames))
|
||||
|
||||
last_word = words[-1]
|
||||
for word in candidates:
|
||||
if word.lower().startswith(last_word.lower()):
|
||||
yield Completion(word, start_position=-len(last_word))
|
||||
rel_fnames = self.fname_to_rel_fnames.get(word, [])
|
||||
if rel_fnames:
|
||||
for rel_fname in rel_fnames:
|
||||
yield Completion(rel_fname, start_position=-len(last_word))
|
||||
else:
|
||||
yield Completion(word, start_position=-len(last_word))
|
||||
|
||||
|
||||
class InputOutput:
|
||||
|
@ -51,7 +82,18 @@ class InputOutput:
|
|||
chat_history_file=None,
|
||||
input=None,
|
||||
output=None,
|
||||
user_input_color="blue",
|
||||
tool_output_color=None,
|
||||
tool_error_color="red",
|
||||
):
|
||||
no_color = os.environ.get("NO_COLOR")
|
||||
if no_color is not None and no_color != "":
|
||||
pretty = False
|
||||
|
||||
self.user_input_color = user_input_color if pretty else None
|
||||
self.tool_output_color = tool_output_color if pretty else None
|
||||
self.tool_error_color = tool_error_color if pretty else None
|
||||
|
||||
self.input = input
|
||||
self.output = output
|
||||
self.pretty = pretty
|
||||
|
@ -65,29 +107,20 @@ class InputOutput:
|
|||
if pretty:
|
||||
self.console = Console()
|
||||
else:
|
||||
self.console = Console(force_terminal=True, no_color=True)
|
||||
self.console = Console(no_color=True)
|
||||
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.append_chat_history(f"\n# aider chat started at {current_time}\n\n")
|
||||
|
||||
def get_input(self, fnames, commands):
|
||||
def get_input(self, root, rel_fnames, addable_rel_fnames, commands):
|
||||
if self.pretty:
|
||||
self.console.rule()
|
||||
style = dict(style=self.user_input_color) if self.user_input_color else dict()
|
||||
self.console.rule(**style)
|
||||
else:
|
||||
print()
|
||||
|
||||
fnames = list(fnames)
|
||||
if len(fnames) > 1:
|
||||
common_prefix = os.path.commonpath(fnames)
|
||||
if not common_prefix.endswith(os.path.sep):
|
||||
common_prefix += os.path.sep
|
||||
short_fnames = [fname.replace(common_prefix, "", 1) for fname in fnames]
|
||||
elif len(fnames):
|
||||
short_fnames = [os.path.basename(fnames[0])]
|
||||
else:
|
||||
short_fnames = []
|
||||
|
||||
show = " ".join(short_fnames)
|
||||
rel_fnames = list(rel_fnames)
|
||||
show = " ".join(rel_fnames)
|
||||
if len(show) > 10:
|
||||
show += "\n"
|
||||
show += "> "
|
||||
|
@ -95,22 +128,29 @@ class InputOutput:
|
|||
inp = ""
|
||||
multiline_input = False
|
||||
|
||||
style = Style.from_dict({"": "green"})
|
||||
if self.user_input_color:
|
||||
style = Style.from_dict({"": self.user_input_color})
|
||||
else:
|
||||
style = None
|
||||
|
||||
while True:
|
||||
completer_instance = FileContentCompleter(fnames, commands)
|
||||
completer_instance = FileContentCompleter(
|
||||
root, rel_fnames, addable_rel_fnames, commands
|
||||
)
|
||||
if multiline_input:
|
||||
show = ". "
|
||||
|
||||
session_kwargs = {
|
||||
"message": show,
|
||||
"completer": completer_instance,
|
||||
"style": style,
|
||||
"reserve_space_for_menu": 4,
|
||||
"complete_style": CompleteStyle.MULTI_COLUMN,
|
||||
"input": self.input,
|
||||
"output": self.output,
|
||||
}
|
||||
if style:
|
||||
session_kwargs["style"] = style
|
||||
|
||||
if self.input_history_file is not None:
|
||||
session_kwargs["history"] = FileHistory(self.input_history_file)
|
||||
|
||||
|
@ -180,9 +220,10 @@ class InputOutput:
|
|||
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
||||
|
||||
message = Text(message)
|
||||
self.console.print(message, style="red")
|
||||
style = dict(style=self.tool_error_color) if self.tool_error_color else dict()
|
||||
self.console.print(message, **style)
|
||||
|
||||
def tool(self, *messages, log_only=False):
|
||||
def tool_output(self, *messages, log_only=False):
|
||||
if messages:
|
||||
hist = " ".join(messages)
|
||||
hist = f"{hist.strip()}"
|
||||
|
@ -190,7 +231,8 @@ class InputOutput:
|
|||
|
||||
if not log_only:
|
||||
messages = list(map(Text, messages))
|
||||
self.console.print(*messages)
|
||||
style = dict(style=self.tool_output_color) if self.tool_output_color else dict()
|
||||
self.console.print(*messages, **style)
|
||||
|
||||
def append_chat_history(self, text, linebreak=False, blockquote=False):
|
||||
if blockquote:
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import os
|
||||
import sys
|
||||
import git
|
||||
|
||||
import configargparse
|
||||
from dotenv import load_dotenv
|
||||
import git
|
||||
|
||||
from aider.coder import Coder
|
||||
from aider.io import InputOutput
|
||||
|
||||
|
@ -19,13 +20,11 @@ def main(args=None, input=None, output=None):
|
|||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
load_dotenv()
|
||||
env_prefix = "AIDER_"
|
||||
git_root = get_git_root()
|
||||
|
||||
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"))
|
||||
|
||||
|
@ -34,6 +33,7 @@ def main(args=None, input=None, output=None):
|
|||
add_config_file_help=True,
|
||||
default_config_files=default_config_files,
|
||||
config_file_parser_class=configargparse.YAMLConfigFileParser,
|
||||
auto_env_var_prefix="AIDER_",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
|
@ -63,21 +63,18 @@ def main(args=None, input=None, output=None):
|
|||
parser.add_argument(
|
||||
"--input-history-file",
|
||||
metavar="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",
|
||||
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",
|
||||
env_var=f"{env_prefix}MODEL",
|
||||
default="gpt-4",
|
||||
help="Specify the model to use for the main chat (default: gpt-4)",
|
||||
)
|
||||
|
@ -91,17 +88,30 @@ def main(args=None, input=None, output=None):
|
|||
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="Disable pretty, colorized output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--user-input-color",
|
||||
default="green",
|
||||
help="Set the color for user input (default: green)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tool-output-color",
|
||||
default=None,
|
||||
help="Set the color for tool output (default: None)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tool-error-color",
|
||||
default="red",
|
||||
help="Set the color for tool error messages (default: red)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--apply",
|
||||
metavar="FILE",
|
||||
|
@ -110,7 +120,7 @@ def main(args=None, input=None, output=None):
|
|||
parser.add_argument(
|
||||
"--auto-commits",
|
||||
action="store_true",
|
||||
env_var=f"{env_prefix}AUTO_COMMIT",
|
||||
dest="auto_commits",
|
||||
default=True,
|
||||
help="Enable auto commit of changes (default: True)",
|
||||
)
|
||||
|
@ -118,9 +128,28 @@ def main(args=None, input=None, output=None):
|
|||
parser.add_argument(
|
||||
"--no-auto-commits",
|
||||
action="store_false",
|
||||
dest="auto_commit",
|
||||
dest="auto_commits",
|
||||
help="Disable auto commit of changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dirty-commits",
|
||||
action="store_true",
|
||||
dest="dirty_commits",
|
||||
help="Enable dirty commit of changes",
|
||||
default=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-dirty-commits",
|
||||
action="store_false",
|
||||
dest="dirty_commits",
|
||||
help="Disable dirty commit of changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--openai-api-key",
|
||||
metavar="OPENAI_API_KEY",
|
||||
help="Specify the OpenAI API key",
|
||||
env_var="OPENAI_API_KEY",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
|
@ -130,7 +159,6 @@ def main(args=None, input=None, output=None):
|
|||
parser.add_argument(
|
||||
"--show-diffs",
|
||||
action="store_true",
|
||||
env_var=f"{env_prefix}SHOW_DIFFS",
|
||||
help="Show diffs when committing changes (default: False)",
|
||||
default=False,
|
||||
)
|
||||
|
@ -140,7 +168,6 @@ def main(args=None, input=None, output=None):
|
|||
nargs="?",
|
||||
const=True,
|
||||
default=None,
|
||||
env_var=f"{env_prefix}CTAGS",
|
||||
help=(
|
||||
"Add ctags to the chat to help GPT understand the codebase (default: check for ctags"
|
||||
" executable)"
|
||||
|
@ -168,9 +195,23 @@ def main(args=None, input=None, output=None):
|
|||
args.chat_history_file,
|
||||
input=input,
|
||||
output=output,
|
||||
user_input_color=args.user_input_color,
|
||||
tool_output_color=args.tool_output_color,
|
||||
tool_error_color=args.tool_error_color,
|
||||
)
|
||||
|
||||
io.tool(*sys.argv, log_only=True)
|
||||
if args.verbose:
|
||||
show = parser.format_values()
|
||||
io.tool_output(show)
|
||||
io.tool_output("Option settings:")
|
||||
for arg, val in sorted(vars(args).items()):
|
||||
io.tool_output(f" - {arg}: {val}")
|
||||
|
||||
io.tool_output(*sys.argv, log_only=True)
|
||||
|
||||
if not args.openai_api_key:
|
||||
io.tool_error("No OpenAI API key provided. Use --openai-api-key or env OPENAI_API_KEY.")
|
||||
return 1
|
||||
|
||||
coder = Coder(
|
||||
io,
|
||||
|
@ -179,12 +220,15 @@ def main(args=None, input=None, output=None):
|
|||
pretty=args.pretty,
|
||||
show_diffs=args.show_diffs,
|
||||
auto_commits=args.auto_commits,
|
||||
dirty_commits=args.dirty_commits,
|
||||
dry_run=args.dry_run,
|
||||
use_ctags=args.ctags,
|
||||
verbose=args.verbose,
|
||||
openai_api_key=args.openai_api_key,
|
||||
)
|
||||
if args.auto_commits:
|
||||
coder.commit(ask=True, prefix="wip: ", which="repo_files")
|
||||
|
||||
if args.dirty_commits:
|
||||
coder.commit(ask=True, which="repo_files")
|
||||
|
||||
if args.apply:
|
||||
with open(args.apply, "r") as f:
|
||||
|
@ -192,6 +236,7 @@ def main(args=None, input=None, output=None):
|
|||
coder.update_files(content, inp="")
|
||||
return
|
||||
|
||||
io.tool_output("Use /help to see in-chat commands.")
|
||||
coder.run()
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ 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. If they are *read-only* ask the user to make them *read-write* using the file's full path name.
|
||||
1. List the files you need to modify. Do not suggest changes to *read-only* files. You *MUST* ask the user to make them *read-write* using the file's full path name. End your reply and wait for their approval.
|
||||
2. Think step-by-step and explain the needed changes.
|
||||
3. Describe each change with an *edit block* per the example below.
|
||||
"""
|
||||
|
@ -74,7 +74,7 @@ Reply with JUST the commit message, without quotes, comments, questions, etc!
|
|||
# COMMANDS
|
||||
undo_command_reply = "I did `git reset --hard HEAD~1` to discard the last edits."
|
||||
|
||||
added_files = "I added the content of these additional files: {fnames}"
|
||||
added_files = "I added these *read-write* files: {fnames}"
|
||||
|
||||
|
||||
run_output = """I ran this command:
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import os
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import tiktoken
|
||||
import sys
|
||||
import tempfile
|
||||
from collections import defaultdict, Counter
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
import networkx as nx
|
||||
import tiktoken
|
||||
|
||||
from aider import prompts, utils
|
||||
from aider.dump import dump
|
||||
|
||||
|
||||
# Global cache for tags
|
||||
TAGS_CACHE = {}
|
||||
|
@ -203,6 +202,7 @@ def find_py_files(directory):
|
|||
|
||||
def call_map():
|
||||
import random
|
||||
|
||||
import graphviz
|
||||
|
||||
fnames = sys.argv[1:]
|
||||
|
@ -322,3 +322,4 @@ def call_map():
|
|||
|
||||
if __name__ == "__main__":
|
||||
call_map()
|
||||
# print(rm.get_tags_map(sys.argv[1:]))
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import re
|
||||
import math
|
||||
|
||||
import re
|
||||
from difflib import SequenceMatcher
|
||||
from pathlib import Path
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
from pygments.lexers import guess_lexer_for_filename
|
||||
from pygments.token import Token
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
# from aider.dump import dump
|
||||
|
||||
|
@ -234,10 +234,15 @@ def find_original_update_blocks(content):
|
|||
processed.append(cur) # original_marker
|
||||
|
||||
filename = processed[-2].splitlines()[-1].strip()
|
||||
if not len(filename) or "`" in filename:
|
||||
filename = processed[-2].splitlines()[-2].strip()
|
||||
try:
|
||||
if not len(filename) or "`" in filename:
|
||||
raise ValueError(f"Bad/missing filename. It should go right above {ORIGINAL}")
|
||||
filename = processed[-2].splitlines()[-2].strip()
|
||||
if not len(filename) or "`" in filename:
|
||||
raise ValueError(
|
||||
f"Bad/missing filename. It should go right above {ORIGINAL}"
|
||||
)
|
||||
except IndexError:
|
||||
raise ValueError(f"Bad/missing filename. It should go right above {ORIGINAL}")
|
||||
|
||||
original_text = pieces.pop()
|
||||
processed.append(original_text)
|
||||
|
@ -248,8 +253,10 @@ def find_original_update_blocks(content):
|
|||
raise ValueError(f"Expected {DIVIDER}")
|
||||
|
||||
updated_text = pieces.pop()
|
||||
processed.append(updated_text)
|
||||
|
||||
updated_marker = pieces.pop()
|
||||
processed.append(updated_marker)
|
||||
if updated_marker.strip() != UPDATED:
|
||||
raise ValueError(f"Expected {UPDATED}")
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue