Merge branch 'main' into glob-add

This commit is contained in:
Paul Gauthier 2023-07-06 17:53:46 -07:00
commit cb2b2c5ce3
22 changed files with 465 additions and 94 deletions

View file

@ -1 +1 @@
__version__ = "0.7.2"
__version__ = "0.8.0"

View file

@ -167,13 +167,13 @@ class Coder:
self.find_common_root()
if main_model.use_repo_map and self.repo and self.gpt_prompts.repo_content_prefix:
rm_io = io if self.verbose else None
self.repo_map = RepoMap(
map_tokens,
self.root,
self.main_model,
rm_io,
io,
self.gpt_prompts.repo_content_prefix,
self.verbose,
)
if self.repo_map.use_ctags:
@ -287,12 +287,21 @@ class Coder:
]
fence = fences[0]
def get_abs_fnames_content(self):
for fname in list(self.abs_fnames):
content = self.io.read_text(fname)
if content is None:
relative_fname = self.get_rel_fname(fname)
self.io.tool_error(f"Dropping {relative_fname} from the chat.")
self.abs_fnames.remove(fname)
else:
yield fname, content
def choose_fence(self):
all_content = ""
for fname in self.abs_fnames:
all_content += Path(fname).read_text() + "\n"
all_content = all_content.splitlines()
for _fname, content in self.get_abs_fnames_content():
all_content += content + "\n"
good = False
for fence_open, fence_close in self.fences:
@ -317,15 +326,15 @@ class Coder:
fnames = self.abs_fnames
prompt = ""
for fname in fnames:
for fname, content in self.get_abs_fnames_content():
relative_fname = self.get_rel_fname(fname)
prompt += utils.quoted_file(fname, relative_fname, fence=self.fence)
return prompt
prompt = "\n"
prompt += relative_fname
prompt += f"\n{self.fence[0]}\n"
prompt += content
prompt += f"{self.fence[1]}\n"
def recheck_abs_fnames(self):
self.abs_fnames = set(
fname for fname in self.abs_fnames if Path(fname).exists() and Path(fname).is_file()
)
return prompt
def get_files_messages(self):
all_content = ""
@ -454,10 +463,6 @@ class Coder:
]
messages += self.done_messages
# notice if files disappear
self.recheck_abs_fnames()
messages += self.get_files_messages()
messages += self.cur_messages
@ -917,8 +922,8 @@ class Coder:
full_path = os.path.abspath(os.path.join(self.root, path))
if full_path in self.abs_fnames:
if not self.dry_run and write_content:
Path(full_path).write_text(write_content)
if write_content:
self.io.write_text(full_path, write_content)
return full_path
if not Path(full_path).exists():
@ -943,8 +948,8 @@ class Coder:
if not self.dry_run:
self.repo.git.add(full_path)
if not self.dry_run and write_content:
Path(full_path).write_text(write_content)
if write_content:
self.io.write_text(full_path, write_content)
return full_path

View file

@ -26,7 +26,10 @@ class EditBlockCoder(Coder):
full_path = self.allowed_to_edit(path)
if not full_path:
continue
if do_replace(full_path, original, updated, self.dry_run):
content = self.io.read_text(full_path)
content = do_replace(full_path, content, original, updated)
if content:
self.io.write_text(full_path, content)
edited.add(path)
continue
self.io.tool_error(f"Failed to apply edit to {path}")
@ -211,7 +214,7 @@ def strip_quoted_wrapping(res, fname=None):
return res
def do_replace(fname, before_text, after_text, dry_run=False):
def do_replace(fname, content, before_text, after_text):
before_text = strip_quoted_wrapping(before_text, fname)
after_text = strip_quoted_wrapping(after_text, fname)
fname = Path(fname)
@ -219,21 +222,18 @@ def do_replace(fname, before_text, after_text, dry_run=False):
# does it want to make a new file?
if not fname.exists() and not before_text.strip():
fname.touch()
content = ""
content = fname.read_text()
if content is None:
return
if not before_text.strip():
# append to existing file, or start a new file
new_content = content + after_text
else:
new_content = replace_most_similar_chunk(content, before_text, after_text)
if not new_content:
return
if not dry_run:
fname.write_text(new_content)
return True
return new_content
ORIGINAL = "<<<<<<< ORIGINAL"

View file

@ -135,7 +135,10 @@ class EditBlockFunctionCoder(Coder):
full_path = self.allowed_to_edit(path)
if not full_path:
continue
if do_replace(full_path, original, updated, self.dry_run):
content = self.io.read_text(full_path)
content = do_replace(full_path, content, original, updated)
if content:
self.io.write_text(full_path, content)
edited.add(path)
continue
self.io.tool_error(f"Failed to apply edit to {path}")

View file

@ -90,8 +90,11 @@ class SingleWholeFileFunctionCoder(Coder):
# ending an existing block
full_path = os.path.abspath(os.path.join(self.root, fname))
with open(full_path, "r") as f:
orig_lines = f.readlines()
content = self.io.read_text(full_path)
if content is None:
orig_lines = []
else:
orig_lines = content.splitlines()
show_diff = diffs.diff_partial_update(
orig_lines,

View file

@ -53,7 +53,7 @@ class WholeFileCoder(Coder):
full_path = (Path(self.root) / fname).absolute()
if mode == "diff" and full_path.exists():
orig_lines = full_path.read_text().splitlines(keepends=True)
orig_lines = self.io.read_text(full_path).splitlines(keepends=True)
show_diff = diffs.diff_partial_update(
orig_lines,
@ -64,9 +64,8 @@ class WholeFileCoder(Coder):
else:
if self.allowed_to_edit(fname):
edited.add(fname)
if not self.dry_run:
new_lines = "".join(new_lines)
full_path.write_text(new_lines)
new_lines = "".join(new_lines)
self.io.write_text(full_path, new_lines)
fname = None
new_lines = []
@ -109,7 +108,7 @@ class WholeFileCoder(Coder):
full_path = (Path(self.root) / fname).absolute()
if mode == "diff" and full_path.exists():
orig_lines = full_path.read_text().splitlines(keepends=True)
orig_lines = self.io.read_text(full_path).splitlines(keepends=True)
show_diff = diffs.diff_partial_update(
orig_lines,
@ -123,8 +122,7 @@ class WholeFileCoder(Coder):
full_path = self.allowed_to_edit(fname)
if full_path:
edited.add(fname)
if not self.dry_run:
new_lines = "".join(new_lines)
Path(full_path).write_text(new_lines)
new_lines = "".join(new_lines)
self.io.write_text(full_path, new_lines)
return edited

View file

@ -101,8 +101,11 @@ class WholeFileFunctionCoder(Coder):
# ending an existing block
full_path = os.path.abspath(os.path.join(self.root, fname))
with open(full_path, "r") as f:
orig_lines = f.readlines()
content = self.io.read_text(full_path)
if content is None:
orig_lines = []
else:
orig_lines = content.splitlines()
show_diff = diffs.diff_partial_update(
orig_lines,

View file

@ -4,12 +4,13 @@ import os
import shlex
import subprocess
import sys
from pathlib import Path
import git
import tiktoken
from prompt_toolkit.completion import Completion
from aider import prompts, utils
from aider import prompts
class Commands:
@ -117,8 +118,10 @@ class Commands:
# files
for fname in self.coder.abs_fnames:
relative_fname = self.coder.get_rel_fname(fname)
quoted = utils.quoted_file(fname, relative_fname)
tokens = len(self.tokenizer.encode(quoted))
content = self.io.read_text(fname)
# approximate
content = f"{relative_fname}\n```\n" + content + "```\n"
tokens = len(self.tokenizer.encode(content))
res.append((tokens, f"{relative_fname}", "use /drop to drop from chat"))
self.io.tool_output("Approximate context window usage, in tokens:")
@ -231,8 +234,8 @@ class Commands:
if self.coder.repo is not None:
create_file = self.io.confirm_ask(
(
f"No files matched '{word}'. Do you want to create the file and add it"
" to git?"
f"No files matched '{word}'. Do you want to create the file and add"
" it to git?"
),
)
else:
@ -241,8 +244,7 @@ class Commands:
)
if create_file:
with open(os.path.join(self.coder.root, word), "w"):
pass
(Path(self.coder.root) / word).touch()
matched_files = [word]
if self.coder.repo is not None:
self.coder.repo.git.add(os.path.join(self.coder.root, word))
@ -254,9 +256,11 @@ class Commands:
for matched_file in matched_files:
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_output(f"Added {matched_file} to the chat")
added_fnames.append(matched_file)
content = self.io.read_text(abs_file_path)
if content is not None:
self.coder.abs_fnames.add(abs_file_path)
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")
@ -339,6 +343,10 @@ class Commands:
else:
other_files.append(file)
if not chat_files and not other_files:
self.io.tool_output("\nNo files in chat or git repo.")
return
if chat_files:
self.io.tool_output("Files in chat:\n")
for file in chat_files:

View file

@ -11,10 +11,10 @@ def main():
file_orig, file_updated = sys.argv[1], sys.argv[2]
with open(file_orig, "r") as f:
with open(file_orig, "r", encoding="utf-8") as f:
lines_orig = f.readlines()
with open(file_updated, "r") as f:
with open(file_updated, "r", encoding="utf-8") as f:
lines_updated = f.readlines()
for i in range(len(file_updated)):

View file

@ -100,6 +100,8 @@ class InputOutput:
user_input_color="blue",
tool_output_color=None,
tool_error_color="red",
encoding="utf-8",
dry_run=False,
):
no_color = os.environ.get("NO_COLOR")
if no_color is not None and no_color != "":
@ -124,6 +126,9 @@ class InputOutput:
else:
self.chat_history_file = None
self.encoding = encoding
self.dry_run = dry_run
if pretty:
self.console = Console()
else:
@ -132,6 +137,23 @@ class InputOutput:
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 read_text(self, filename):
try:
with open(str(filename), "r", encoding=self.encoding) as f:
return f.read()
except FileNotFoundError:
self.tool_error(f"{filename}: file not found error")
return
except UnicodeError as e:
self.tool_error(f"{filename}: {e}")
return
def write_text(self, filename, content):
if self.dry_run:
return
with open(str(filename), "w", encoding=self.encoding) as f:
f.write(content)
def get_input(self, root, rel_fnames, addable_rel_fnames, commands):
if self.pretty:
style = dict(style=self.user_input_color) if self.user_input_color else dict()

View file

@ -185,6 +185,11 @@ def main(args=None, input=None, output=None):
dest="dirty_commits",
help="Disable commits when repo is found dirty",
)
parser.add_argument(
"--encoding",
default="utf-8",
help="Specify the encoding to use when reading files (default: utf-8)",
)
parser.add_argument(
"--openai-api-key",
metavar="OPENAI_API_KEY",
@ -247,6 +252,7 @@ def main(args=None, input=None, output=None):
user_input_color=args.user_input_color,
tool_output_color=args.tool_output_color,
tool_error_color=args.tool_error_color,
dry_run=args.dry_run,
)
if args.verbose:
@ -294,8 +300,9 @@ def main(args=None, input=None, output=None):
coder.commit(ask=True, which="repo_files")
if args.apply:
with open(args.apply, "r") as f:
content = f.read()
content = io.read_text(args.apply)
if content is None:
return
coder.apply_updates(content)
return

View file

@ -40,6 +40,7 @@ class Model:
if tokens == 8:
self.prompt_price = 0.03
self.completion_price = 0.06
self.always_available = True
elif tokens == 32:
self.prompt_price = 0.06
self.completion_price = 0.12

View file

@ -74,8 +74,10 @@ class RepoMap:
main_model=models.GPT4,
io=None,
repo_content_prefix=None,
verbose=False,
):
self.io = io
self.verbose = verbose
if not root:
root = os.getcwd()
@ -130,7 +132,7 @@ class RepoMap:
files_listing = self.get_ranked_tags_map(chat_files, other_files)
if files_listing:
num_tokens = self.token_count(files_listing)
if self.io:
if self.verbose:
self.io.tool_output(f"ctags map: {num_tokens/1024:.1f} k-tokens")
ctags_msg = " with selected ctags info"
return files_listing, ctags_msg
@ -138,7 +140,7 @@ class RepoMap:
files_listing = self.get_simple_files_map(other_files)
ctags_msg = ""
num_tokens = self.token_count(files_listing)
if self.io:
if self.verbose:
self.io.tool_output(f"simple map: {num_tokens/1024:.1f} k-tokens")
if num_tokens < self.max_map_tokens:
return files_listing, ctags_msg
@ -198,7 +200,7 @@ class RepoMap:
with tempfile.TemporaryDirectory() as tempdir:
hello_py = os.path.join(tempdir, "hello.py")
with open(hello_py, "w") as f:
with open(hello_py, "w", encoding="utf-8") as f:
f.write("def hello():\n print('Hello, world!')\n")
self.run_ctags(hello_py)
except FileNotFoundError:
@ -237,10 +239,8 @@ class RepoMap:
return idents
def get_name_identifiers_uncached(self, fname):
try:
with open(fname, "r") as f:
content = f.read()
except UnicodeDecodeError:
content = self.io.read_text(fname)
if content is None:
return list()
try:

View file

@ -1,24 +1,6 @@
from pathlib import Path
from .dump import dump # noqa: F401
def quoted_file(fname, display_fname, fence=("```", "```"), number=False):
prompt = "\n"
prompt += display_fname
prompt += f"\n{fence[0]}\n"
file_content = Path(fname).read_text()
lines = file_content.splitlines()
for i, line in enumerate(lines, start=1):
if number:
prompt += f"{i:4d} "
prompt += line + "\n"
prompt += f"{fence[1]}\n"
return prompt
def show_messages(messages, title=None, functions=None):
if title:
print(title.upper(), "*" * 50)