From bc60e7fd1c0ae1b79dae6feaf85f80ac80e46ac0 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 15:44:12 -0700 Subject: [PATCH 01/39] use Path.touch() to create new files --- aider/commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aider/commands.py b/aider/commands.py index 29f4bc63b..a262f9c8d 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -3,6 +3,7 @@ import os import shlex import subprocess import sys +from pathlib import Path import git import tiktoken @@ -238,8 +239,8 @@ 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)) From 5f9947a7279d6a1e7339f16fca4ac1ed2b56080d Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 19:33:59 -0700 Subject: [PATCH 02/39] aider: Add read_text function to InputOutput class and --encoding command-line switch. # Context: USER: Add a read_text function to the InputOutput class. It takes the file name as argument, and returns the text for that file. Internally, if we get a Unicode error or a FileNotFound error, it prints ToolErrorOutput to notify the user, and then returns None. Add a command-line switch, and dash-dash encoding, which defaults to UTF-8. That's the encoding which will be used by the read_text function. ASSISTANT: To implement the requested changes, we need to modify the following files: 1. `aider/io.py`: To add the `read_text` function to the `InputOutput` class. 2. `aider/main.py`: To add the `--encoding` command-line switch. Let's start with the changes to `aider/io.py`: ```python aider/io.py <<<<<<< ORIGINAL class InputOutput: num_error_outputs = 0 num_user_asks = 0 def __init__( self, pretty=True, yes=False, input_history_file=None, 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 if self.output: self.pretty = False self.yes = yes self.input_history_file = input_history_file if chat_history_file is not None: self.chat_history_file = Path(chat_history_file) else: self.chat_history_file = None if pretty: self.console = Console() else: self.console = Console(force_terminal=False, 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") ======= class InputOutput: num_error_outputs = 0 num_user_asks = 0 def __init__( self, pretty=True, yes=False, input_history_file=None, chat_history_file=None, input=None, output=None, user_input_color="blue", tool_output_color=None, tool_error_color="red", encoding="utf-8", ): 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 if self.output: self.pretty = False self.yes = yes self.input_history_file = input_history_file if chat_history_file is not None: self.chat_history_file = Path(chat_history_file) else: self.chat_history_file = None self.encoding = encoding if pretty: self.console = Console() else: self.console = Console(force_terminal=False, 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 read_text(self, filename): try: with open(filename, "r", encoding=self.encoding) as f: return f.read() except (FileNotFoundError, UnicodeError) as e: self.tool_error(str(e)) return None >>>>>>> UPDATED ``` Now, let's add the `--encoding` command-line switch to `aider/main.py`: ```python aider/main.py <<<<<<< ORIGINAL parser.add_argument( "--no-dirty-commits", action="store_false", dest="dirty_commits", help="Disable commits when repo is found dirty", ) parser.add_argument( "--openai-api-key", metavar="OPENAI_API_KEY", help="Specify the OpenAI API key", env_var="OPENAI_API_KEY", ) parser.add_argument( "--openai-api-base", metavar="OPENAI_API_BASE", default="https://api.openai.com/v1", help="Specify the OpenAI API base endpoint (default: https://api.openai.com/v1)", ) ======= parser.add_argument( "--no-dirty-commits", action="store_false", 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", help="Specify the OpenAI API key", env_var="OPENAI_API_KEY", ) parser.add_argument( "--openai-api-base", metavar="OPENAI_API_BASE", default="https://api.openai.com/v1", help="Specify the OpenAI API base endpoint (default: https://api.openai.com/v1)", ) >>>>>>> UPDATED ``` These changes should implement the requested functionality. Please review and let me know if any further changes are needed. --- aider/io.py | 11 +++++++++++ aider/main.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/aider/io.py b/aider/io.py index 1d275716e..ff3cab386 100644 --- a/aider/io.py +++ b/aider/io.py @@ -100,6 +100,7 @@ class InputOutput: user_input_color="blue", tool_output_color=None, tool_error_color="red", + encoding="utf-8", ): no_color = os.environ.get("NO_COLOR") if no_color is not None and no_color != "": @@ -124,6 +125,8 @@ class InputOutput: else: self.chat_history_file = None + self.encoding = encoding + if pretty: self.console = Console() else: @@ -132,6 +135,14 @@ 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(filename, "r", encoding=self.encoding) as f: + return f.read() + except (FileNotFoundError, UnicodeError) as e: + self.tool_error(str(e)) + return None + 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() diff --git a/aider/main.py b/aider/main.py index decf1514f..cb2039351 100644 --- a/aider/main.py +++ b/aider/main.py @@ -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", From 9cab570f87ab23a1d9d90b09209b3ba83be74302 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:21:32 -0700 Subject: [PATCH 03/39] refactor to use io.read_text --- aider/coders/base_coder.py | 23 +++++++++++++++++++---- aider/utils.py | 18 ------------------ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 8da1e6f9f..a2f8fc0cc 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -287,10 +287,20 @@ 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.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" + for _fname, content in self.get_abs_fnames_content(): + all_content += content + "\n" all_content = all_content.splitlines() @@ -317,9 +327,14 @@ 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) + prompt = "\n" + prompt += relative_fname + prompt += f"\n{self.fence[0]}\n" + prompt += content + prompt += f"{self.fence[1]}\n" + return prompt def recheck_abs_fnames(self): diff --git a/aider/utils.py b/aider/utils.py index cd805e7ab..15c3bfb71 100644 --- a/aider/utils.py +++ b/aider/utils.py @@ -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) From 4c220ff63fd411c230d64418054e84ee7bc05353 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:31:22 -0700 Subject: [PATCH 04/39] refactor to use io.read_text --- aider/coders/editblock_coder.py | 55 +++++++++++++++------------- aider/coders/editblock_func_coder.py | 3 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/aider/coders/editblock_coder.py b/aider/coders/editblock_coder.py index b63f577a2..eb7fc0c0e 100644 --- a/aider/coders/editblock_coder.py +++ b/aider/coders/editblock_coder.py @@ -26,7 +26,8 @@ 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) + if do_replace(full_path, content, original, updated, self.dry_run): edited.add(path) continue self.io.tool_error(f"Failed to apply edit to {path}") @@ -34,6 +35,33 @@ class EditBlockCoder(Coder): return edited +def do_replace(fname, content, before_text, after_text, dry_run=False): + before_text = strip_quoted_wrapping(before_text, fname) + after_text = strip_quoted_wrapping(after_text, fname) + fname = Path(fname) + + # does it want to make a new file? + if not fname.exists() and not before_text.strip(): + fname.touch() + content = "" + + 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 + + def try_dotdotdots(whole, part, replace): """ See if the edit block has ... lines. @@ -211,31 +239,6 @@ def strip_quoted_wrapping(res, fname=None): return res -def do_replace(fname, before_text, after_text, dry_run=False): - before_text = strip_quoted_wrapping(before_text, fname) - after_text = strip_quoted_wrapping(after_text, fname) - fname = Path(fname) - - # does it want to make a new file? - if not fname.exists() and not before_text.strip(): - fname.touch() - - content = fname.read_text() - - 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 - - ORIGINAL = "<<<<<<< ORIGINAL" DIVIDER = "=======" UPDATED = ">>>>>>> UPDATED" diff --git a/aider/coders/editblock_func_coder.py b/aider/coders/editblock_func_coder.py index 676952938..dfe4175a6 100644 --- a/aider/coders/editblock_func_coder.py +++ b/aider/coders/editblock_func_coder.py @@ -135,7 +135,8 @@ 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) + if do_replace(full_path, content, original, updated, self.dry_run): edited.add(path) continue self.io.tool_error(f"Failed to apply edit to {path}") From 413ddb84dfbbf2aebd5980f11d1d08b25b55a140 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:33:46 -0700 Subject: [PATCH 05/39] refactor to use io.read_text --- aider/coders/wholefile_coder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aider/coders/wholefile_coder.py b/aider/coders/wholefile_coder.py index c37f531bb..2c77d00fa 100644 --- a/aider/coders/wholefile_coder.py +++ b/aider/coders/wholefile_coder.py @@ -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, @@ -109,7 +109,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, From c8fb1ddaa75b3a097aad12f8ede07ae4987be0ab Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:46:15 -0700 Subject: [PATCH 06/39] refactor to use io.read_text --- aider/coders/base_coder.py | 4 ++-- aider/io.py | 2 +- aider/repomap.py | 14 +++++++------- tests/test_repomap.py | 13 ++++++++----- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index a2f8fc0cc..19bef991e 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -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: diff --git a/aider/io.py b/aider/io.py index ff3cab386..f001d63f5 100644 --- a/aider/io.py +++ b/aider/io.py @@ -141,7 +141,7 @@ class InputOutput: return f.read() except (FileNotFoundError, UnicodeError) as e: self.tool_error(str(e)) - return None + return def get_input(self, root, rel_fnames, addable_rel_fnames, commands): if self.pretty: diff --git a/aider/repomap.py b/aider/repomap.py index fab94ec27..df739c8f9 100644 --- a/aider/repomap.py +++ b/aider/repomap.py @@ -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: diff --git a/tests/test_repomap.py b/tests/test_repomap.py index cba69e2b1..03aa410f8 100644 --- a/tests/test_repomap.py +++ b/tests/test_repomap.py @@ -3,6 +3,7 @@ import tempfile import unittest from unittest.mock import patch +from aider.io import InputOutput from aider.repomap import RepoMap @@ -21,7 +22,8 @@ class TestRepoMap(unittest.TestCase): with open(os.path.join(temp_dir, file), "w") as f: f.write("") - repo_map = RepoMap(root=temp_dir) + io = InputOutput() + repo_map = RepoMap(root=temp_dir, io=io) other_files = [os.path.join(temp_dir, file) for file in test_files] result = repo_map.get_repo_map([], other_files) @@ -65,7 +67,8 @@ print(my_function(3, 4)) with open(os.path.join(temp_dir, test_file3), "w") as f: f.write(file_content3) - repo_map = RepoMap(root=temp_dir) + io = InputOutput() + repo_map = RepoMap(root=temp_dir, io=io) other_files = [ os.path.join(temp_dir, test_file1), os.path.join(temp_dir, test_file2), @@ -83,7 +86,7 @@ print(my_function(3, 4)) def test_check_for_ctags_failure(self): with patch("subprocess.run") as mock_run: mock_run.side_effect = Exception("ctags not found") - repo_map = RepoMap() + repo_map = RepoMap(io=InputOutput()) self.assertFalse(repo_map.has_ctags) def test_check_for_ctags_success(self): @@ -100,7 +103,7 @@ print(my_function(3, 4)) b' status = main()$/", "kind": "variable"}' ), ] - repo_map = RepoMap() + repo_map = RepoMap(io=InputOutput()) self.assertTrue(repo_map.has_ctags) def test_get_repo_map_without_ctags(self): @@ -120,7 +123,7 @@ print(my_function(3, 4)) with open(os.path.join(temp_dir, file), "w") as f: f.write("") - repo_map = RepoMap(root=temp_dir) + repo_map = RepoMap(root=temp_dir, io=InputOutput()) repo_map.has_ctags = False # force it off other_files = [os.path.join(temp_dir, file) for file in test_files] From 60619f263caf4f099c40420c1452b761321c9962 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:47:44 -0700 Subject: [PATCH 07/39] refactor to use io.read_text --- aider/coders/single_wholefile_func_coder.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aider/coders/single_wholefile_func_coder.py b/aider/coders/single_wholefile_func_coder.py index b6368311a..a9dd2494a 100644 --- a/aider/coders/single_wholefile_func_coder.py +++ b/aider/coders/single_wholefile_func_coder.py @@ -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, From d30fb94c72d4fd1d2512a6a40020629c6ae15a2b Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:48:09 -0700 Subject: [PATCH 08/39] refactor to use io.read_text --- aider/coders/wholefile_func_coder.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aider/coders/wholefile_func_coder.py b/aider/coders/wholefile_func_coder.py index f45ef48a7..5c7cbd6ae 100644 --- a/aider/coders/wholefile_func_coder.py +++ b/aider/coders/wholefile_func_coder.py @@ -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, From 7542449629eb785ff917dbde0afb48eb0505bb42 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:49:04 -0700 Subject: [PATCH 09/39] refactor to use io.read_text --- aider/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aider/main.py b/aider/main.py index cb2039351..2028389b6 100644 --- a/aider/main.py +++ b/aider/main.py @@ -299,8 +299,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 From ca45db289858d86781af72a4fdd604b8c81f8e18 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:51:35 -0700 Subject: [PATCH 10/39] use utf-8 --- aider/diffs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aider/diffs.py b/aider/diffs.py index 1330d01dc..784745688 100644 --- a/aider/diffs.py +++ b/aider/diffs.py @@ -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)): From 388f603ef9e205209b22a5f416132287de1fd48d Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:57:21 -0700 Subject: [PATCH 11/39] aider: Created a file 'foo.bad' with non-utf8 characters before calling the 'cmd_add' method. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Context: USER: do the todo ASSISTANT: To complete the TODO, we need to create a file named 'foo.bad' with a non-utf8 encoding. We can use the Python 'codecs' module to create a file with a specific encoding. In this case, we'll use 'iso-8859-15' which is a common encoding that includes characters not present in utf-8. Here's how to modify the 'test_cmd_add_bad_encoding' method in the 'tests/test_commands.py' file: ```python tests/test_commands.py <<<<<<< ORIGINAL def test_cmd_add_bad_encoding(self): # Initialize the Commands and InputOutput objects io = InputOutput(pretty=False, yes=True) from aider.coders import Coder coder = Coder.create(models.GPT35, None, io, openai_api_key="deadbeef") commands = Commands(io, coder) # TODO: make a new file foo.bad which will fail to decode as utf-8 commands.cmd_add("foo.bad") self.assertEqual(coder.abs_fnames, set()) ======= import codecs def test_cmd_add_bad_encoding(self): # Initialize the Commands and InputOutput objects io = InputOutput(pretty=False, yes=True) from aider.coders import Coder coder = Coder.create(models.GPT35, None, io, openai_api_key="deadbeef") commands = Commands(io, coder) # Create a new file foo.bad which will fail to decode as utf-8 with codecs.open('foo.bad', 'w', encoding='iso-8859-15') as f: f.write('ÆØÅ') # Characters not present in utf-8 commands.cmd_add("foo.bad") self.assertEqual(coder.abs_fnames, set()) >>>>>>> UPDATED ``` This will create a file 'foo.bad' with non-utf8 characters before calling the 'cmd_add' method. --- tests/test_commands.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_commands.py b/tests/test_commands.py index 7bd186ad6..47872d279 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -32,3 +32,20 @@ class TestCommands(TestCase): # Check if both files have been created in the temporary directory self.assertTrue(os.path.exists("foo.txt")) self.assertTrue(os.path.exists("bar.txt")) + + import codecs + def test_cmd_add_bad_encoding(self): + # Initialize the Commands and InputOutput objects + io = InputOutput(pretty=False, yes=True) + from aider.coders import Coder + + coder = Coder.create(models.GPT35, None, io, openai_api_key="deadbeef") + commands = Commands(io, coder) + + # Create a new file foo.bad which will fail to decode as utf-8 + with codecs.open('foo.bad', 'w', encoding='iso-8859-15') as f: + f.write('ÆØÅ') # Characters not present in utf-8 + + commands.cmd_add("foo.bad") + + self.assertEqual(coder.abs_fnames, set()) From c6e71b443b0a5a95857fc094e4ef44b748acb69e Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 20:57:59 -0700 Subject: [PATCH 12/39] move import --- tests/test_commands.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_commands.py b/tests/test_commands.py index 47872d279..7759e38bb 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,3 +1,4 @@ +import codecs import os import shutil import tempfile @@ -33,7 +34,6 @@ class TestCommands(TestCase): self.assertTrue(os.path.exists("foo.txt")) self.assertTrue(os.path.exists("bar.txt")) - import codecs def test_cmd_add_bad_encoding(self): # Initialize the Commands and InputOutput objects io = InputOutput(pretty=False, yes=True) @@ -43,8 +43,8 @@ class TestCommands(TestCase): commands = Commands(io, coder) # Create a new file foo.bad which will fail to decode as utf-8 - with codecs.open('foo.bad', 'w', encoding='iso-8859-15') as f: - f.write('ÆØÅ') # Characters not present in utf-8 + with codecs.open("foo.bad", "w", encoding="iso-8859-15") as f: + f.write("ÆØÅ") # Characters not present in utf-8 commands.cmd_add("foo.bad") From c2c80bbd3cef8db0a81e2a6249b4bbfc76a4b443 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:00:10 -0700 Subject: [PATCH 13/39] do not /add files that fail to decode --- aider/commands.py | 8 +++++--- aider/io.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/aider/commands.py b/aider/commands.py index a262f9c8d..e3b3ee441 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -252,9 +252,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") diff --git a/aider/io.py b/aider/io.py index f001d63f5..5f045ad4d 100644 --- a/aider/io.py +++ b/aider/io.py @@ -140,7 +140,7 @@ class InputOutput: with open(filename, "r", encoding=self.encoding) as f: return f.read() except (FileNotFoundError, UnicodeError) as e: - self.tool_error(str(e)) + self.tool_error(f"{filename}: {e}") return def get_input(self, root, rel_fnames, addable_rel_fnames, commands): From 3897d0a665ffdd3f43375a35725542c39675bbb5 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:01:26 -0700 Subject: [PATCH 14/39] removed recheck_abs_fnames --- aider/coders/base_coder.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 19bef991e..db3de98cf 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -337,11 +337,6 @@ class Coder: return prompt - 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() - ) - def get_files_messages(self): all_content = "" if self.abs_fnames: @@ -469,10 +464,6 @@ class Coder: ] messages += self.done_messages - - # notice if files disappear - self.recheck_abs_fnames() - messages += self.get_files_messages() messages += self.cur_messages From 7c56dcb16f45de9330c2bb99a330e96e146af697 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:02:40 -0700 Subject: [PATCH 15/39] noop --- aider/coders/editblock_coder.py | 54 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/aider/coders/editblock_coder.py b/aider/coders/editblock_coder.py index eb7fc0c0e..148aeffd2 100644 --- a/aider/coders/editblock_coder.py +++ b/aider/coders/editblock_coder.py @@ -35,33 +35,6 @@ class EditBlockCoder(Coder): return edited -def do_replace(fname, content, before_text, after_text, dry_run=False): - before_text = strip_quoted_wrapping(before_text, fname) - after_text = strip_quoted_wrapping(after_text, fname) - fname = Path(fname) - - # does it want to make a new file? - if not fname.exists() and not before_text.strip(): - fname.touch() - content = "" - - 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 - - def try_dotdotdots(whole, part, replace): """ See if the edit block has ... lines. @@ -239,6 +212,33 @@ def strip_quoted_wrapping(res, fname=None): return res +def do_replace(fname, content, before_text, after_text, dry_run=False): + before_text = strip_quoted_wrapping(before_text, fname) + after_text = strip_quoted_wrapping(after_text, fname) + fname = Path(fname) + + # does it want to make a new file? + if not fname.exists() and not before_text.strip(): + fname.touch() + content = "" + + 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 + + ORIGINAL = "<<<<<<< ORIGINAL" DIVIDER = "=======" UPDATED = ">>>>>>> UPDATED" From 4126875425b169b5e87dd4c60aca430b4133c177 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:08:12 -0700 Subject: [PATCH 16/39] added test_cmd_tokens --- tests/test_commands.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_commands.py b/tests/test_commands.py index 7759e38bb..98a7c2808 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -49,3 +49,14 @@ class TestCommands(TestCase): commands.cmd_add("foo.bad") self.assertEqual(coder.abs_fnames, set()) + + def test_cmd_tokens(self): + # Initialize the Commands and InputOutput objects + io = InputOutput(pretty=False, yes=True) + from aider.coders import Coder + + coder = Coder.create(models.GPT35, None, io, openai_api_key="deadbeef") + commands = Commands(io, coder) + + commands.cmd_add("foo.txt bar.txt") + commands.cmd_tokens("") From de02038a7e25f4edeb3d2992a53be6debd193664 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:08:49 -0700 Subject: [PATCH 17/39] remove ref to quoted_file --- aider/commands.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aider/commands.py b/aider/commands.py index e3b3ee441..dfa54509f 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -9,7 +9,7 @@ import git import tiktoken from prompt_toolkit.completion import Completion -from aider import prompts, utils +from aider import prompts class Commands: @@ -117,8 +117,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 = "```\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:") From 1808c47d96603816ff8019b41e60550c72ceb03d Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:11:30 -0700 Subject: [PATCH 18/39] aider: Added write_text function to InputOutput class in aider/io.py. # Context: USER: add a write_text func ASSISTANT: To add a `write_text` function to the `InputOutput` class in the `aider/io.py` file, we need to modify the `aider/io.py` file. Here is the change: ```python aider/io.py <<<<<<< ORIGINAL def read_text(self, filename): try: with open(filename, "r", encoding=self.encoding) as f: return f.read() except (FileNotFoundError, UnicodeError) as e: self.tool_error(f"{filename}: {e}") return ======= def read_text(self, filename): try: with open(filename, "r", encoding=self.encoding) as f: return f.read() except (FileNotFoundError, UnicodeError) as e: self.tool_error(f"{filename}: {e}") return def write_text(self, filename, content): try: with open(filename, "w", encoding=self.encoding) as f: f.write(content) except (FileNotFoundError, UnicodeError) as e: self.tool_error(f"{filename}: {e}") return >>>>>>> UPDATED ``` This `write_text` function takes a filename and content as arguments, opens the file in write mode, and writes the content to the file. If there is a `FileNotFoundError` or `UnicodeError`, it calls the `tool_error` method with the error message. --- aider/io.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/aider/io.py b/aider/io.py index 5f045ad4d..eb4f15f32 100644 --- a/aider/io.py +++ b/aider/io.py @@ -134,7 +134,7 @@ 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(filename, "r", encoding=self.encoding) as f: @@ -143,6 +143,14 @@ class InputOutput: self.tool_error(f"{filename}: {e}") return + def write_text(self, filename, content): + try: + with open(filename, "w", encoding=self.encoding) as f: + f.write(content) + except (FileNotFoundError, UnicodeError) as e: + self.tool_error(f"{filename}: {e}") + return + 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() From 0ea52b412f55cd1ae3419efb3f0b2cbffe7f8fa7 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:11:46 -0700 Subject: [PATCH 19/39] cleanup --- aider/io.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/aider/io.py b/aider/io.py index eb4f15f32..0631e82ed 100644 --- a/aider/io.py +++ b/aider/io.py @@ -134,7 +134,7 @@ 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(filename, "r", encoding=self.encoding) as f: @@ -144,12 +144,8 @@ class InputOutput: return def write_text(self, filename, content): - try: - with open(filename, "w", encoding=self.encoding) as f: - f.write(content) - except (FileNotFoundError, UnicodeError) as e: - self.tool_error(f"{filename}: {e}") - return + with open(filename, "w", encoding=self.encoding) as f: + f.write(content) def get_input(self, root, rel_fnames, addable_rel_fnames, commands): if self.pretty: From b46dffbb201b119f3a3bdbf8a3fcc3967e1f9ee6 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:18:19 -0700 Subject: [PATCH 20/39] refactor to use io.write_text --- aider/coders/base_coder.py | 4 ++-- aider/coders/editblock_coder.py | 14 ++++++-------- aider/coders/editblock_func_coder.py | 5 ++++- aider/coders/wholefile_coder.py | 4 ++-- aider/io.py | 4 ++-- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index db3de98cf..e29731b16 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -924,7 +924,7 @@ class Coder: if full_path in self.abs_fnames: if not self.dry_run and write_content: - Path(full_path).write_text(write_content) + self.io.write_text(full_path, write_content) return full_path if not Path(full_path).exists(): @@ -950,7 +950,7 @@ class Coder: self.repo.git.add(full_path) if not self.dry_run and write_content: - Path(full_path).write_text(write_content) + self.io.write_text(full_path, write_content) return full_path diff --git a/aider/coders/editblock_coder.py b/aider/coders/editblock_coder.py index 148aeffd2..8384671e3 100644 --- a/aider/coders/editblock_coder.py +++ b/aider/coders/editblock_coder.py @@ -27,7 +27,10 @@ class EditBlockCoder(Coder): if not full_path: continue content = self.io.read_text(full_path) - if do_replace(full_path, content, original, updated, self.dry_run): + content = do_replace(full_path, content, original, updated) + if content: + if not self.dry_run: + self.io.write_text(full_path, content) edited.add(path) continue self.io.tool_error(f"Failed to apply edit to {path}") @@ -212,7 +215,7 @@ def strip_quoted_wrapping(res, fname=None): return res -def do_replace(fname, content, 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) @@ -230,13 +233,8 @@ def do_replace(fname, content, before_text, after_text, dry_run=False): 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" diff --git a/aider/coders/editblock_func_coder.py b/aider/coders/editblock_func_coder.py index dfe4175a6..62ac34050 100644 --- a/aider/coders/editblock_func_coder.py +++ b/aider/coders/editblock_func_coder.py @@ -136,7 +136,10 @@ class EditBlockFunctionCoder(Coder): if not full_path: continue content = self.io.read_text(full_path) - if do_replace(full_path, content, original, updated, self.dry_run): + content = do_replace(full_path, content, original, updated) + if content: + if not self.dry_run: + self.io.write_text(full_path, content) edited.add(path) continue self.io.tool_error(f"Failed to apply edit to {path}") diff --git a/aider/coders/wholefile_coder.py b/aider/coders/wholefile_coder.py index 2c77d00fa..7cca9cb5e 100644 --- a/aider/coders/wholefile_coder.py +++ b/aider/coders/wholefile_coder.py @@ -66,7 +66,7 @@ class WholeFileCoder(Coder): edited.add(fname) if not self.dry_run: new_lines = "".join(new_lines) - full_path.write_text(new_lines) + self.io.write_text(full_path, new_lines) fname = None new_lines = [] @@ -125,6 +125,6 @@ class WholeFileCoder(Coder): edited.add(fname) if not self.dry_run: new_lines = "".join(new_lines) - Path(full_path).write_text(new_lines) + self.io.write_text(full_path, new_lines) return edited diff --git a/aider/io.py b/aider/io.py index 0631e82ed..495f80cca 100644 --- a/aider/io.py +++ b/aider/io.py @@ -137,14 +137,14 @@ class InputOutput: def read_text(self, filename): try: - with open(filename, "r", encoding=self.encoding) as f: + with open(str(filename), "r", encoding=self.encoding) as f: return f.read() except (FileNotFoundError, UnicodeError) as e: self.tool_error(f"{filename}: {e}") return def write_text(self, filename, content): - with open(filename, "w", encoding=self.encoding) as f: + with open(str(filename), "w", encoding=self.encoding) as f: f.write(content) def get_input(self, root, rel_fnames, addable_rel_fnames, commands): From 4ee5c49a0420447060f0b30b14e4c500c9e62da8 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:20:22 -0700 Subject: [PATCH 21/39] have io.write_text handle dry_run --- aider/io.py | 3 +++ aider/main.py | 1 + 2 files changed, 4 insertions(+) diff --git a/aider/io.py b/aider/io.py index 495f80cca..b5f876188 100644 --- a/aider/io.py +++ b/aider/io.py @@ -101,6 +101,7 @@ class InputOutput: 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 != "": @@ -144,6 +145,8 @@ class InputOutput: 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) diff --git a/aider/main.py b/aider/main.py index 2028389b6..9e614ecee 100644 --- a/aider/main.py +++ b/aider/main.py @@ -252,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: From 85c3bb8f12ce402f9bd017c8245acca8af99ae21 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:21:37 -0700 Subject: [PATCH 22/39] rely on io.dry_run --- aider/coders/base_coder.py | 4 ++-- aider/coders/editblock_coder.py | 3 +-- aider/coders/editblock_func_coder.py | 3 +-- aider/coders/wholefile_coder.py | 10 ++++------ 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index e29731b16..7bf4a0437 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -923,7 +923,7 @@ 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: + if write_content: self.io.write_text(full_path, write_content) return full_path @@ -949,7 +949,7 @@ class Coder: if not self.dry_run: self.repo.git.add(full_path) - if not self.dry_run and write_content: + if write_content: self.io.write_text(full_path, write_content) return full_path diff --git a/aider/coders/editblock_coder.py b/aider/coders/editblock_coder.py index 8384671e3..49ae63674 100644 --- a/aider/coders/editblock_coder.py +++ b/aider/coders/editblock_coder.py @@ -29,8 +29,7 @@ class EditBlockCoder(Coder): content = self.io.read_text(full_path) content = do_replace(full_path, content, original, updated) if content: - if not self.dry_run: - self.io.write_text(full_path, content) + self.io.write_text(full_path, content) edited.add(path) continue self.io.tool_error(f"Failed to apply edit to {path}") diff --git a/aider/coders/editblock_func_coder.py b/aider/coders/editblock_func_coder.py index 62ac34050..2c834255e 100644 --- a/aider/coders/editblock_func_coder.py +++ b/aider/coders/editblock_func_coder.py @@ -138,8 +138,7 @@ class EditBlockFunctionCoder(Coder): content = self.io.read_text(full_path) content = do_replace(full_path, content, original, updated) if content: - if not self.dry_run: - self.io.write_text(full_path, content) + self.io.write_text(full_path, content) edited.add(path) continue self.io.tool_error(f"Failed to apply edit to {path}") diff --git a/aider/coders/wholefile_coder.py b/aider/coders/wholefile_coder.py index 7cca9cb5e..8c8536f04 100644 --- a/aider/coders/wholefile_coder.py +++ b/aider/coders/wholefile_coder.py @@ -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) - self.io.write_text(full_path, new_lines) + new_lines = "".join(new_lines) + self.io.write_text(full_path, new_lines) fname = None new_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) - self.io.write_text(full_path, new_lines) + new_lines = "".join(new_lines) + self.io.write_text(full_path, new_lines) return edited From 391088fd141e2bcc2730f43039da260cf380d269 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Wed, 5 Jul 2023 21:22:12 -0700 Subject: [PATCH 23/39] add self.dry_run --- aider/io.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/io.py b/aider/io.py index b5f876188..67f846bee 100644 --- a/aider/io.py +++ b/aider/io.py @@ -127,6 +127,7 @@ class InputOutput: self.chat_history_file = None self.encoding = encoding + self.dry_run = dry_run if pretty: self.console = Console() From 3c443d68d0eaade9797e300ca58e93221a6ac5fb Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 10:55:38 -0700 Subject: [PATCH 24/39] working test --- aider/coders/base_coder.py | 3 ++- aider/io.py | 5 ++++- tests/test_coder.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 7bf4a0437..7255e91df 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -290,9 +290,10 @@ class Coder: def get_abs_fnames_content(self): for fname in list(self.abs_fnames): content = self.io.read_text(fname) + dump(fname, content) if content is None: relative_fname = self.get_rel_fname(fname) - self.tool_error(f"Dropping {relative_fname} from the chat.") + self.io.tool_error(f"Dropping {relative_fname} from the chat.") self.abs_fnames.remove(fname) else: yield fname, content diff --git a/aider/io.py b/aider/io.py index 67f846bee..98a8a62f3 100644 --- a/aider/io.py +++ b/aider/io.py @@ -141,7 +141,10 @@ class InputOutput: try: with open(str(filename), "r", encoding=self.encoding) as f: return f.read() - except (FileNotFoundError, UnicodeError) as e: + except FileNotFoundError: + self.tool_error(f"{filename}: file not found error") + return + except UnicodeError as e: self.tool_error(f"{filename}: {e}") return diff --git a/tests/test_coder.py b/tests/test_coder.py index 41ba7c768..b9bc9ad23 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -1,4 +1,5 @@ import os +import tempfile import unittest from unittest.mock import MagicMock, patch @@ -7,6 +8,8 @@ import requests from aider import models from aider.coders import Coder +from aider.dump import dump # noqa: F401 +from aider.io import InputOutput class TestCoder(unittest.TestCase): @@ -177,6 +180,35 @@ class TestCoder(unittest.TestCase): # Assert that print was called once mock_print.assert_called_once() + def test_run_with_file_deletion(self): + # Create a few temporary files + _, file1 = tempfile.mkstemp() + _, file2 = tempfile.mkstemp() + + files = [file1, file2] + + # Initialize the Coder object with the mocked IO and mocked repo + coder = Coder.create( + models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=files + ) + + def mock_send(*args, **kwargs): + coder.partial_response_content = "ok" + coder.partial_response_function_call = dict() + + coder.send = MagicMock(side_effect=mock_send) + + # Call the run method with a message + coder.run(with_message="hi") + self.assertEqual(len(coder.abs_fnames), 2) + + # Delete one of the files + os.remove(file1) + + # Call the run method again with a message + coder.run(with_message="hi") + self.assertEqual(len(coder.abs_fnames), 1) + if __name__ == "__main__": unittest.main() From 564724020760e615f1dd4096b813f0555a9895b1 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 10:58:36 -0700 Subject: [PATCH 25/39] aider: Added a new test `test_run_with_file_unicode_error` that expects a `UnicodeDecodeError` to be raised when the `run` method of the `Coder` object is called with a file containing non-UTF8 text. # Context: USER: I did `git reset --hard HEAD~1` to discard the last edits. ASSISTANT: Okay, your last commit has been discarded and your repository is now at the state before the last commit. If you need to make any further changes or need assistance with anything else, feel free to ask. USER: try again, but don't elide code with ... ASSISTANT: Understood. Here's the updated *edit block* without eliding any code: ```python tests/test_coder.py <<<<<<< ORIGINAL def test_run_with_file_deletion(self): # Create a few temporary files _, file1 = tempfile.mkstemp() _, file2 = tempfile.mkstemp() files = [file1, file2] # Initialize the Coder object with the mocked IO and mocked repo coder = Coder.create( models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=files ) def mock_send(*args, **kwargs): coder.partial_response_content = "ok" coder.partial_response_function_call = dict() coder.send = MagicMock(side_effect=mock_send) # Call the run method with a message coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 2) # Delete one of the files os.remove(file1) # Call the run method again with a message coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 1) if __name__ == "__main__": unittest.main() ======= def test_run_with_file_deletion(self): # Create a few temporary files _, file1 = tempfile.mkstemp() _, file2 = tempfile.mkstemp() files = [file1, file2] # Initialize the Coder object with the mocked IO and mocked repo coder = Coder.create( models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=files ) def mock_send(*args, **kwargs): coder.partial_response_content = "ok" coder.partial_response_function_call = dict() coder.send = MagicMock(side_effect=mock_send) # Call the run method with a message coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 2) # Delete one of the files os.remove(file1) # Call the run method again with a message coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 1) def test_run_with_file_unicode_error(self): # Create a temporary file _, file = tempfile.mkstemp() # Write some non-UTF8 text into the file with open(file, "wb") as f: f.write(b"\x80abc") # Initialize the Coder object with the temporary file coder = Coder.create( models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=[file] ) # Expect a UnicodeDecodeError to be raised when the run method is called with self.assertRaises(UnicodeDecodeError): coder.run(with_message="hi") if __name__ == "__main__": unittest.main() >>>>>>> UPDATED ``` This new test `test_run_with_file_unicode_error` will write non-UTF8 text into a temporary file and expect a `UnicodeDecodeError` to be raised when the `run` method of the `Coder` object is called. --- tests/test_coder.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/test_coder.py b/tests/test_coder.py index b9bc9ad23..a28ba49f5 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -209,6 +209,22 @@ class TestCoder(unittest.TestCase): coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 1) + def test_run_with_file_unicode_error(self): + # Create a temporary file + _, file = tempfile.mkstemp() -if __name__ == "__main__": - unittest.main() + # Write some non-UTF8 text into the file + with open(file, "wb") as f: + f.write(b"\x80abc") + + # Initialize the Coder object with the temporary file + coder = Coder.create( + models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=[file] + ) + + # Expect a UnicodeDecodeError to be raised when the run method is called + with self.assertRaises(UnicodeDecodeError): + coder.run(with_message="hi") + + if __name__ == "__main__": + unittest.main() From ffee2c951102d0bb63a813fe8d45e4f989aeb272 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 10:59:25 -0700 Subject: [PATCH 26/39] test for unicode errors --- tests/test_coder.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/tests/test_coder.py b/tests/test_coder.py index a28ba49f5..cf5819a1d 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -210,21 +210,34 @@ class TestCoder(unittest.TestCase): self.assertEqual(len(coder.abs_fnames), 1) def test_run_with_file_unicode_error(self): - # Create a temporary file - _, file = tempfile.mkstemp() + # Create a few temporary files + _, file1 = tempfile.mkstemp() + _, file2 = tempfile.mkstemp() - # Write some non-UTF8 text into the file - with open(file, "wb") as f: - f.write(b"\x80abc") + files = [file1, file2] - # Initialize the Coder object with the temporary file + # Initialize the Coder object with the mocked IO and mocked repo coder = Coder.create( - models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=[file] + models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=files ) - # Expect a UnicodeDecodeError to be raised when the run method is called - with self.assertRaises(UnicodeDecodeError): - coder.run(with_message="hi") + def mock_send(*args, **kwargs): + coder.partial_response_content = "ok" + coder.partial_response_function_call = dict() + + coder.send = MagicMock(side_effect=mock_send) + + # Call the run method with a message + coder.run(with_message="hi") + self.assertEqual(len(coder.abs_fnames), 2) + + # Write some non-UTF8 text into the file + with open(file1, "wb") as f: + f.write(b"\x80abc") + + # Call the run method again with a message + coder.run(with_message="hi") + self.assertEqual(len(coder.abs_fnames), 1) if __name__ == "__main__": unittest.main() From 610dd223b50d77942ac5cbd72d4e85f234de50f7 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:08:13 -0700 Subject: [PATCH 27/39] added test for choose_fence --- aider/coders/base_coder.py | 4 +--- tests/test_coder.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 7255e91df..fb9f8c9ac 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -290,7 +290,7 @@ class Coder: def get_abs_fnames_content(self): for fname in list(self.abs_fnames): content = self.io.read_text(fname) - dump(fname, content) + if content is None: relative_fname = self.get_rel_fname(fname) self.io.tool_error(f"Dropping {relative_fname} from the chat.") @@ -303,8 +303,6 @@ class Coder: for _fname, content in self.get_abs_fnames_content(): all_content += content + "\n" - all_content = all_content.splitlines() - good = False for fence_open, fence_close in self.fences: if fence_open in all_content or fence_close in all_content: diff --git a/tests/test_coder.py b/tests/test_coder.py index cf5819a1d..5138d59d8 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -239,5 +239,30 @@ class TestCoder(unittest.TestCase): coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 1) + def test_choose_fence(self): + # Create a few temporary files + _, file1 = tempfile.mkstemp() + + with open(file1, "wb") as f: + f.write(b"this contains ``` backticks") + + files = [file1] + + # Initialize the Coder object with the mocked IO and mocked repo + coder = Coder.create( + models.GPT4, None, io=InputOutput(), openai_api_key="fake_key", fnames=files + ) + + def mock_send(*args, **kwargs): + coder.partial_response_content = "ok" + coder.partial_response_function_call = dict() + + coder.send = MagicMock(side_effect=mock_send) + + # Call the run method with a message + coder.run(with_message="hi") + + self.assertNotEqual(coder.fence[0], "```") + if __name__ == "__main__": unittest.main() From 7bbf0c08703cad1e44541d04f889ffc6a9aa4e3e Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:32:31 -0700 Subject: [PATCH 28/39] test full editblock edit --- tests/test_editblock.py | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/test_editblock.py b/tests/test_editblock.py index bed2a5f18..6aea04124 100644 --- a/tests/test_editblock.py +++ b/tests/test_editblock.py @@ -1,11 +1,26 @@ # flake8: noqa: E501 +import tempfile import unittest +from pathlib import Path +from unittest.mock import MagicMock, patch +from aider import models +from aider.coders import Coder from aider.coders import editblock_coder as eb +from aider.dump import dump # noqa: F401 +from aider.io import InputOutput class TestUtils(unittest.TestCase): + def setUp(self): + self.patcher = patch("aider.coders.base_coder.check_model_availability") + self.mock_check = self.patcher.start() + self.mock_check.return_value = True + + def tearDown(self): + self.patcher.stop() + def test_replace_most_similar_chunk(self): whole = "This is a sample text.\nAnother line of text.\nYet another line.\n" part = "This is a sample text" @@ -223,6 +238,42 @@ These changes replace the `subprocess.run` patches with `subprocess.check_output result = eb.replace_part_with_missing_leading_whitespace(whole, part, replace) self.assertEqual(result, expected_output) + def test_choose_fence(self): + # Create a few temporary files + _, file1 = tempfile.mkstemp() + + with open(file1, "w", encoding="utf-8") as f: + f.write("one\ntwo\nthree\n") + + files = [file1] + + # Initialize the Coder object with the mocked IO and mocked repo + coder = Coder.create( + models.GPT4, "diff", io=InputOutput(), openai_api_key="fake_key", fnames=files + ) + + def mock_send(*args, **kwargs): + coder.partial_response_content = f""" +Do this: + +{Path(file1).name} +<<<<<<< ORIGINAL +two +======= +new +>>>>>>> UPDATED + +""" + coder.partial_response_function_call = dict() + + coder.send = MagicMock(side_effect=mock_send) + + # Call the run method with a message + coder.run(with_message="hi") + + content = open(file1, encoding="utf-8").read() + self.assertEqual(content, "one\nnew\nthree\n") + if __name__ == "__main__": unittest.main() From 7d3c40ea21055502610aae92e0dbebd033330c1f Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:34:19 -0700 Subject: [PATCH 29/39] test dry run --- tests/test_editblock.py | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/test_editblock.py b/tests/test_editblock.py index 6aea04124..84d061767 100644 --- a/tests/test_editblock.py +++ b/tests/test_editblock.py @@ -238,7 +238,7 @@ These changes replace the `subprocess.run` patches with `subprocess.check_output result = eb.replace_part_with_missing_leading_whitespace(whole, part, replace) self.assertEqual(result, expected_output) - def test_choose_fence(self): + def test_full_edit(self): # Create a few temporary files _, file1 = tempfile.mkstemp() @@ -274,6 +274,49 @@ new content = open(file1, encoding="utf-8").read() self.assertEqual(content, "one\nnew\nthree\n") + def test_full_edit_dry_run(self): + # Create a few temporary files + _, file1 = tempfile.mkstemp() + + orig_content = "one\ntwo\nthree\n" + + with open(file1, "w", encoding="utf-8") as f: + f.write(orig_content) + + files = [file1] + + # Initialize the Coder object with the mocked IO and mocked repo + coder = Coder.create( + models.GPT4, + "diff", + io=InputOutput(dry_run=True), + openai_api_key="fake_key", + fnames=files, + dry_run=True, + ) + + def mock_send(*args, **kwargs): + coder.partial_response_content = f""" +Do this: + +{Path(file1).name} +<<<<<<< ORIGINAL +two +======= +new +>>>>>>> UPDATED + +""" + coder.partial_response_function_call = dict() + + coder.send = MagicMock(side_effect=mock_send) + + # Call the run method with a message + coder.run(with_message="hi") + + content = open(file1, encoding="utf-8").read() + self.assertEqual(content, orig_content) + if __name__ == "__main__": unittest.main() From 089fa57edebfbce5d2f37a132a846d5257e23eb3 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:43:20 -0700 Subject: [PATCH 30/39] full edit for wholefile --- tests/test_wholefile.py | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_wholefile.py b/tests/test_wholefile.py index 0237515cf..7f2dfe385 100644 --- a/tests/test_wholefile.py +++ b/tests/test_wholefile.py @@ -3,8 +3,10 @@ import shutil import tempfile import unittest from pathlib import Path +from unittest.mock import MagicMock, patch from aider import models +from aider.coders import Coder from aider.coders.wholefile_coder import WholeFileCoder from aider.io import InputOutput @@ -15,10 +17,16 @@ class TestWholeFileCoder(unittest.TestCase): self.tempdir = tempfile.mkdtemp() os.chdir(self.tempdir) + self.patcher = patch("aider.coders.base_coder.check_model_availability") + self.mock_check = self.patcher.start() + self.mock_check.return_value = True + def tearDown(self): os.chdir(self.original_cwd) shutil.rmtree(self.tempdir, ignore_errors=True) + self.patcher.stop() + def test_update_files(self): # Create a sample file in the temporary directory sample_file = "sample.txt" @@ -198,6 +206,45 @@ after b self.assertEqual(fname_a.read_text(), "after a\n") self.assertEqual(fname_b.read_text(), "after b\n") + def test_full_edit(self): + # Create a few temporary files + _, file1 = tempfile.mkstemp() + + with open(file1, "w", encoding="utf-8") as f: + f.write("one\ntwo\nthree\n") + + files = [file1] + + # Initialize the Coder object with the mocked IO and mocked repo + coder = Coder.create( + models.GPT4, "whole", io=InputOutput(), openai_api_key="fake_key", fnames=files + ) + + # no trailing newline so the response content below doesn't add ANOTHER newline + new_content = "new\ntwo\nthree" + + def mock_send(*args, **kwargs): + coder.partial_response_content = f""" +Do this: + +{Path(file1).name} +``` +{new_content} +``` + +""" + coder.partial_response_function_call = dict() + + coder.send = MagicMock(side_effect=mock_send) + + # Call the run method with a message + coder.run(with_message="hi") + + content = open(file1, encoding="utf-8").read() + + # check for one trailing newline + self.assertEqual(content, new_content + "\n") + if __name__ == "__main__": unittest.main() From eb6ac6841f8981f1f38b58e165831a700c87827e Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:46:20 -0700 Subject: [PATCH 31/39] aider: Redirected the standard output to an instance of `io.StringIO` and captured the console output of `cmd_tokens`. # Context: USER: how can i capture the console output of `cmd_tokens`? ASSISTANT: To capture the console output of `cmd_tokens`, you can modify the `test_cmd_tokens` method in the `tests/test_commands.py` file. You can use the `io.StringIO` class to capture the standard output. Here's how you can do it: 1. Import the `io` module at the top of the file. 2. In the `test_cmd_tokens` method, redirect the standard output to an instance of `io.StringIO` before calling `commands.cmd_tokens("")`. 3. After calling `commands.cmd_tokens("")`, you can get the console output from the `io.StringIO` instance. Here is the *edit block* for the changes: ```python tests/test_commands.py <<<<<<< ORIGINAL import codecs import os import shutil import tempfile from unittest import TestCase from aider import models from aider.commands import Commands from aider.io import InputOutput ======= import codecs import os import shutil import tempfile from unittest import TestCase import io from aider import models from aider.commands import Commands from aider.io import InputOutput >>>>>>> UPDATED tests/test_commands.py <<<<<<< ORIGINAL def test_cmd_tokens(self): # Initialize the Commands and InputOutput objects io = InputOutput(pretty=False, yes=True) from aider.coders import Coder coder = Coder.create(models.GPT35, None, io, openai_api_key="deadbeef") commands = Commands(io, coder) commands.cmd_add("foo.txt bar.txt") commands.cmd_tokens("") ======= def test_cmd_tokens(self): # Initialize the Commands and InputOutput objects io = InputOutput(pretty=False, yes=True) from aider.coders import Coder coder = Coder.create(models.GPT35, None, io, openai_api_key="deadbeef") commands = Commands(io, coder) commands.cmd_add("foo.txt bar.txt") # Redirect the standard output to an instance of io.StringIO stdout = io.StringIO() sys.stdout = stdout commands.cmd_tokens("") # Reset the standard output sys.stdout = sys.__stdout__ # Get the console output console_output = stdout.getvalue() print(console_output) >>>>>>> UPDATED ``` Please note that this will only capture the output of `print` statements. If the `cmd_tokens` method writes to the console in a different way (for example, by writing directly to `sys.stdout`), this might not capture that output. --- tests/test_commands.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_commands.py b/tests/test_commands.py index 98a7c2808..88e292eb4 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -3,6 +3,7 @@ import os import shutil import tempfile from unittest import TestCase +import io from aider import models from aider.commands import Commands @@ -59,4 +60,16 @@ class TestCommands(TestCase): commands = Commands(io, coder) commands.cmd_add("foo.txt bar.txt") + + # Redirect the standard output to an instance of io.StringIO + stdout = io.StringIO() + sys.stdout = stdout + commands.cmd_tokens("") + + # Reset the standard output + sys.stdout = sys.__stdout__ + + # Get the console output + console_output = stdout.getvalue() + print(console_output) From 5ec5b60660d33bda30d633a7089f1070c546d768 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:50:13 -0700 Subject: [PATCH 32/39] better /tokens test --- tests/test_commands.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_commands.py b/tests/test_commands.py index 88e292eb4..7645fef13 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,12 +1,15 @@ import codecs import os import shutil +import sys import tempfile +from io import StringIO from unittest import TestCase -import io from aider import models +from aider.coders import Coder from aider.commands import Commands +from aider.dump import dump # noqa: F401 from aider.io import InputOutput @@ -54,7 +57,6 @@ class TestCommands(TestCase): def test_cmd_tokens(self): # Initialize the Commands and InputOutput objects io = InputOutput(pretty=False, yes=True) - from aider.coders import Coder coder = Coder.create(models.GPT35, None, io, openai_api_key="deadbeef") commands = Commands(io, coder) @@ -62,7 +64,7 @@ class TestCommands(TestCase): commands.cmd_add("foo.txt bar.txt") # Redirect the standard output to an instance of io.StringIO - stdout = io.StringIO() + stdout = StringIO() sys.stdout = stdout commands.cmd_tokens("") @@ -72,4 +74,6 @@ class TestCommands(TestCase): # Get the console output console_output = stdout.getvalue() - print(console_output) + + self.assertIn("foo.txt", console_output) + self.assertIn("bar.txt", console_output) From 7cadfd9b4c3d87ccf602cb7bcbcb160d4484a6d4 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:56:15 -0700 Subject: [PATCH 33/39] aider: Changed encoding to 'latin-1' and provided content that will cause an error when read with 'utf-8' encoding in the `test_run_with_file_utf_unicode_error` method. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Context: USER: do the TODOs ASSISTANT: I see two TODOs in the provided code. 1. In the `test_run_with_file_utf_unicode_error` method, you need to specify an encoding other than 'utf-8'. 2. In the same method, you need to provide content that will cause an error when read with 'utf-8' encoding. Here are the proposed changes: ```python tests/test_coder.py <<<<<<< ORIGINAL encoding = 'pick something other than utf-8' # TODO ======= encoding = 'latin-1' >>>>>>> UPDATED ``` ```python tests/test_coder.py <<<<<<< ORIGINAL some_content_which_will_error_if_read_with_encoding_utf8 = '' # TODO ======= some_content_which_will_error_if_read_with_encoding_utf8 = 'ÅÍÎÏ'.encode('utf-16') >>>>>>> UPDATED ``` In the first change, I've chosen 'latin-1' as the encoding. It's a common encoding that's different from 'utf-8'. In the second change, I've provided a string of characters that are not valid 'utf-8' when encoded with 'utf-16'. This will cause an error when trying to read the file with 'utf-8' encoding. --- tests/test_coder.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test_coder.py b/tests/test_coder.py index 5138d59d8..17ad349e8 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -264,5 +264,38 @@ class TestCoder(unittest.TestCase): self.assertNotEqual(coder.fence[0], "```") + def test_run_with_file_utf_unicode_error(self): + # Create a few temporary files + _, file1 = tempfile.mkstemp() + _, file2 = tempfile.mkstemp() + + files = [file1, file2] + + encoding = 'latin-1' + + # Initialize the Coder object with the mocked IO and mocked repo + coder = Coder.create( + models.GPT4, None, io=InputOutput(encoding=encoding), openai_api_key="fake_key", fnames=files + ) + + def mock_send(*args, **kwargs): + coder.partial_response_content = "ok" + coder.partial_response_function_call = dict() + + coder.send = MagicMock(side_effect=mock_send) + + # Call the run method with a message + coder.run(with_message="hi") + self.assertEqual(len(coder.abs_fnames), 2) + + some_content_which_will_error_if_read_with_encoding_utf8 = 'ÅÍÎÏ'.encode('utf-16') + with open(file1, "wb") as f: + f.write(some_content_which_will_error_if_read_with_encoding_utf8) + + coder.run(with_message="hi") + + # both files should still be here + self.assertEqual(len(coder.abs_fnames), 2) + if __name__ == "__main__": unittest.main() From 4a15b6c01076293161c5e2d2777fdc5e7a6f438c Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 11:57:44 -0700 Subject: [PATCH 34/39] test_run_with_file_utf_unicode_error --- tests/test_coder.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_coder.py b/tests/test_coder.py index 17ad349e8..2b38223f0 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -265,17 +265,22 @@ class TestCoder(unittest.TestCase): self.assertNotEqual(coder.fence[0], "```") def test_run_with_file_utf_unicode_error(self): + "make sure that we honor InputOutput(encoding) and don't just assume utf-8" # Create a few temporary files _, file1 = tempfile.mkstemp() _, file2 = tempfile.mkstemp() files = [file1, file2] - encoding = 'latin-1' + encoding = "utf-16" # Initialize the Coder object with the mocked IO and mocked repo coder = Coder.create( - models.GPT4, None, io=InputOutput(encoding=encoding), openai_api_key="fake_key", fnames=files + models.GPT4, + None, + io=InputOutput(encoding=encoding), + openai_api_key="fake_key", + fnames=files, ) def mock_send(*args, **kwargs): @@ -288,7 +293,7 @@ class TestCoder(unittest.TestCase): coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 2) - some_content_which_will_error_if_read_with_encoding_utf8 = 'ÅÍÎÏ'.encode('utf-16') + some_content_which_will_error_if_read_with_encoding_utf8 = "ÅÍÎÏ".encode(encoding) with open(file1, "wb") as f: f.write(some_content_which_will_error_if_read_with_encoding_utf8) From 2b69c11672bd4b77e78cfee356a8f970eaee039b Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 12:08:07 -0700 Subject: [PATCH 35/39] Try renaming to avoid windows PermErr --- tests/test_coder.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_coder.py b/tests/test_coder.py index 2b38223f0..2df9447de 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -202,8 +202,11 @@ class TestCoder(unittest.TestCase): coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 2) - # Delete one of the files - os.remove(file1) + # move the file + # os.remove() causes PermErr on Windows! + # PermissionError: [WinError 32] The process cannot access the file because + # it is being used by another process + os.rename(file1, file1 + ".renamed") # Call the run method again with a message coder.run(with_message="hi") From 656af5cd83a842e949cb451f7965f54b294404a6 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 12:26:29 -0700 Subject: [PATCH 36/39] rename did not help --- tests/test_coder.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_coder.py b/tests/test_coder.py index 2df9447de..d34e52d4e 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -202,11 +202,7 @@ class TestCoder(unittest.TestCase): coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 2) - # move the file - # os.remove() causes PermErr on Windows! - # PermissionError: [WinError 32] The process cannot access the file because - # it is being used by another process - os.rename(file1, file1 + ".renamed") + os.remove(file1) # Call the run method again with a message coder.run(with_message="hi") From 0f1af1d991399e4725fd8227561b0de12f0a87f3 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 12:29:49 -0700 Subject: [PATCH 37/39] improve filehandle hygene --- tests/test_editblock.py | 4 ++-- tests/test_wholefile.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_editblock.py b/tests/test_editblock.py index 84d061767..82940e3ef 100644 --- a/tests/test_editblock.py +++ b/tests/test_editblock.py @@ -271,7 +271,7 @@ new # Call the run method with a message coder.run(with_message="hi") - content = open(file1, encoding="utf-8").read() + content = Path(file1).read_text(encoding="utf-8") self.assertEqual(content, "one\nnew\nthree\n") def test_full_edit_dry_run(self): @@ -314,7 +314,7 @@ new # Call the run method with a message coder.run(with_message="hi") - content = open(file1, encoding="utf-8").read() + content = Path(file1).read_text(encoding="utf-8") self.assertEqual(content, orig_content) diff --git a/tests/test_wholefile.py b/tests/test_wholefile.py index 7f2dfe385..de85e04c8 100644 --- a/tests/test_wholefile.py +++ b/tests/test_wholefile.py @@ -240,7 +240,7 @@ Do this: # Call the run method with a message coder.run(with_message="hi") - content = open(file1, encoding="utf-8").read() + content = Path(file1).read_text(encoding="utf-8") # check for one trailing newline self.assertEqual(content, new_content + "\n") From 09f79e5a42e61259e8f547b1a98a363ef55efa4b Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 12:37:32 -0700 Subject: [PATCH 38/39] fix PermErr in test on windows --- tests/test_coder.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_coder.py b/tests/test_coder.py index d34e52d4e..b1be19eea 100644 --- a/tests/test_coder.py +++ b/tests/test_coder.py @@ -1,6 +1,7 @@ import os import tempfile import unittest +from pathlib import Path from unittest.mock import MagicMock, patch import openai @@ -182,8 +183,14 @@ class TestCoder(unittest.TestCase): def test_run_with_file_deletion(self): # Create a few temporary files - _, file1 = tempfile.mkstemp() - _, file2 = tempfile.mkstemp() + + tempdir = Path(tempfile.mkdtemp()) + + file1 = tempdir / "file1.txt" + file2 = tempdir / "file2.txt" + + file1.touch() + file2.touch() files = [file1, file2] @@ -202,7 +209,7 @@ class TestCoder(unittest.TestCase): coder.run(with_message="hi") self.assertEqual(len(coder.abs_fnames), 2) - os.remove(file1) + file1.unlink() # Call the run method again with a message coder.run(with_message="hi") From 95a777a48ce3d2e332ea53a17e1fe222725b29d2 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 6 Jul 2023 13:35:17 -0700 Subject: [PATCH 39/39] More accurate /tokens --- aider/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/commands.py b/aider/commands.py index cec382df3..51e51ef4b 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -119,7 +119,7 @@ class Commands: relative_fname = self.coder.get_rel_fname(fname) content = self.io.read_text(fname) # approximate - content = "```\n" + content + "```\n" + 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"))