mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-20 12:24:59 +00:00
144 lines
5 KiB
Python
144 lines
5 KiB
Python
from pathlib import Path
|
|
|
|
from aider import diffs
|
|
|
|
from ..dump import dump # noqa: F401
|
|
from .base_coder import Coder
|
|
from .wholefile_prompts import WholeFilePrompts
|
|
|
|
|
|
class WholeFileCoder(Coder):
|
|
"""A coder that operates on entire files for code modifications."""
|
|
|
|
edit_format = "whole"
|
|
gpt_prompts = WholeFilePrompts()
|
|
|
|
def render_incremental_response(self, final):
|
|
try:
|
|
return self.get_edits(mode="diff")
|
|
except ValueError:
|
|
return self.get_multi_response_content()
|
|
|
|
def get_edits(self, mode="update"):
|
|
content = self.get_multi_response_content()
|
|
|
|
chat_files = self.get_inchat_relative_files()
|
|
|
|
output = []
|
|
lines = content.splitlines(keepends=True)
|
|
|
|
edits = []
|
|
|
|
saw_fname = None
|
|
fname = None
|
|
fname_source = None
|
|
new_lines = []
|
|
for i, line in enumerate(lines):
|
|
if line.startswith(self.fence[0]) or line.startswith(self.fence[1]):
|
|
if fname is not None:
|
|
# ending an existing block
|
|
saw_fname = None
|
|
|
|
full_path = self.abs_root_path(fname)
|
|
|
|
if mode == "diff":
|
|
output += self.do_live_diff(full_path, new_lines, True)
|
|
else:
|
|
edits.append((fname, fname_source, new_lines))
|
|
|
|
fname = None
|
|
fname_source = None
|
|
new_lines = []
|
|
continue
|
|
|
|
# fname==None ... starting a new block
|
|
if i > 0:
|
|
fname_source = "block"
|
|
fname = lines[i - 1].strip()
|
|
fname = fname.strip("*") # handle **filename.py**
|
|
fname = fname.rstrip(":")
|
|
fname = fname.strip("`")
|
|
fname = fname.lstrip("#")
|
|
fname = fname.strip()
|
|
|
|
# Issue #1232
|
|
if len(fname) > 250:
|
|
fname = ""
|
|
|
|
# Did gpt prepend a bogus dir? It especially likes to
|
|
# include the path/to prefix from the one-shot example in
|
|
# the prompt.
|
|
if fname and fname not in chat_files and Path(fname).name in chat_files:
|
|
fname = Path(fname).name
|
|
if not fname: # blank line? or ``` was on first line i==0
|
|
if saw_fname:
|
|
fname = saw_fname
|
|
fname_source = "saw"
|
|
elif len(chat_files) == 1:
|
|
fname = chat_files[0]
|
|
fname_source = "chat"
|
|
else:
|
|
# TODO: sense which file it is by diff size
|
|
raise ValueError(
|
|
f"No filename provided before {self.fence[0]} in file listing"
|
|
)
|
|
|
|
elif fname is not None:
|
|
new_lines.append(line)
|
|
else:
|
|
for word in line.strip().split():
|
|
word = word.rstrip(".:,;!")
|
|
for chat_file in chat_files:
|
|
quoted_chat_file = f"`{chat_file}`"
|
|
if word == quoted_chat_file:
|
|
saw_fname = chat_file
|
|
|
|
output.append(line)
|
|
|
|
if mode == "diff":
|
|
if fname is not None:
|
|
# ending an existing block
|
|
full_path = (Path(self.root) / fname).absolute()
|
|
output += self.do_live_diff(full_path, new_lines, False)
|
|
return "\n".join(output)
|
|
|
|
if fname:
|
|
edits.append((fname, fname_source, new_lines))
|
|
|
|
seen = set()
|
|
refined_edits = []
|
|
# process from most reliable filename, to least reliable
|
|
for source in ("block", "saw", "chat"):
|
|
for fname, fname_source, new_lines in edits:
|
|
if fname_source != source:
|
|
continue
|
|
# if a higher priority source already edited the file, skip
|
|
if fname in seen:
|
|
continue
|
|
|
|
seen.add(fname)
|
|
refined_edits.append((fname, fname_source, new_lines))
|
|
|
|
return refined_edits
|
|
|
|
def apply_edits(self, edits):
|
|
for path, fname_source, new_lines in edits:
|
|
full_path = self.abs_root_path(path)
|
|
new_lines = "".join(new_lines)
|
|
self.io.write_text(full_path, new_lines)
|
|
|
|
def do_live_diff(self, full_path, new_lines, final):
|
|
if Path(full_path).exists():
|
|
orig_lines = self.io.read_text(full_path)
|
|
if orig_lines is not None:
|
|
orig_lines = orig_lines.splitlines(keepends=True)
|
|
|
|
show_diff = diffs.diff_partial_update(
|
|
orig_lines,
|
|
new_lines,
|
|
final=final,
|
|
).splitlines()
|
|
return show_diff
|
|
|
|
output = ["```"] + new_lines + ["```"]
|
|
return output
|