Skip to content

Commit

Permalink
Merge pull request #128 from devchat-ai/auto_apply_fix_issue
Browse files Browse the repository at this point in the history
feat: Enhance issue fixing and add aider integration
  • Loading branch information
kagami-l authored Jul 19, 2024
2 parents d218bf7 + 4a3b29c commit a4005b6
Show file tree
Hide file tree
Showing 21 changed files with 1,208 additions and 32 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ custom/*
!custom/config.yml.example

user_settings.yml
.aider*
24 changes: 23 additions & 1 deletion lib/ide_service/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def ide_name(self) -> str:
return self._result

@rpc_method
def diff_apply(self, filepath, content) -> bool:
def diff_apply(self, filepath, content, autoedit: bool = False) -> bool:
"""
Applies a given diff to a file.
Expand Down Expand Up @@ -182,3 +182,25 @@ def get_extension_tools_path(self) -> str:
The extension tools path.
"""
return self._result

@rpc_method
def select_range(
self, fileName: str, startLine: int, startColumn: int, endLine: int, endColumn: int
) -> bool:
"""
Selects a range of text in the specified file.
Args:
fileName: The name of the file.
startLine: The starting line of the selection (0-based).
startColumn: The starting column of the selection (0-based).
endLine: The ending line of the selection (0-based).
endColumn: The ending column of the selection (0-based).
Returns:
A boolean indicating whether the selection was successful.
Note:
If startLine is -1, it cancels the current selection.
"""
return self._result
7 changes: 1 addition & 6 deletions lib/ide_service/vscode_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ def run_code(code: str):


@rpc_call
def diff_apply(filepath, content):
pass


@rpc_call
def get_symbol_defines_in_selected_code():
def diff_apply(filepath, content, autoedit=False):
pass


Expand Down
19 changes: 19 additions & 0 deletions merico/aider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### 操作指南

aider工作流命令使用步骤如下:

1. 确保已经使用 `/aider.files.add` 命令添加了需要处理的文件。
2. 输入 `/aider <message>` 命令,其中 `<message>` 是你想要aider执行的任务描述。
3. 等待aider生成建议的更改。
4. 系统会自动显示每个文件的Diff View,你可以选择是否接受修改。
5. 对于多个文件的更改,系统会在每个文件之后询问是否继续查看下一个文件的更改。

注意事项:
- 如果没有添加任何文件到aider,命令将会提示你先使用 'aider.files.add' 命令添加文件。
- 你可以使用 `aider.files.remove` 命令从aider中移除文件。
- 所有的更改都会在IDE中以Diff View的形式展示,你可以在查看后决定是否应用这些更改。

使用示例:
/aider 重构这段代码以提高性能

这个命令会让aider分析当前添加的文件,并提供重构建议以提高代码性能。
214 changes: 214 additions & 0 deletions merico/aider/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import json
import os
import subprocess
import sys

from devchat.ide import IDEService

from lib.chatmark import Button

GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json")


def save_config(config_path, item, value):
if os.path.exists(config_path):
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
else:
config = {}

config[item] = value
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=4)


def write_python_path_to_config():
"""
Write the current system Python path to the configuration.
"""
python_path = sys.executable
save_config(GLOBAL_CONFIG_PATH, "aider_python", python_path)
print(f"Python path '{python_path}' has been written to the configuration.")


def get_aider_files():
"""
从.chat/.aider_files文件中读取aider文件列表
"""
aider_files_path = os.path.join(".chat", ".aider_files")
if not os.path.exists(aider_files_path):
return []

with open(aider_files_path, "r") as f:
return [line.strip() for line in f if line.strip()]


def run_aider(message, files):
"""
运行aider命令
"""
python = sys.executable
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")

cmd = [
python,
"-m",
"aider",
"--model",
f"openai/{model}",
"--yes",
"--no-auto-commits",
"--dry-run",
"--no-pretty",
"--message",
message,
] + files

process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

has_started = False
aider_output = ""
for line in process.stdout:
if "run with --help" in line or 'run "aider --help"' in line:
has_started = True
continue
if has_started:
aider_output += line
print(line, end="", flush=True)

return_code = process.wait()

if return_code != 0:
for line in process.stderr:
print(f"Error: {line.strip()}", file=sys.stderr)
sys.exit(return_code)

return aider_output


def apply_changes(changes, files):
"""
应用aider生成的更改
"""
changes_file = ".chat/changes.txt"
os.makedirs(os.path.dirname(changes_file), exist_ok=True)
with open(changes_file, "w") as f:
f.write(changes)

python = sys.executable
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")

cmd = [
python,
"-m",
"aider",
"--model",
f"openai/{model}",
"--yes",
"--no-auto-commits",
"--apply",
changes_file,
] + files

process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

has_started = False
for line in process.stdout:
if "Model:" in line:
has_started = True
continue
if has_started:
print(line, end="", flush=True)

return_code = process.wait()

if return_code != 0:
for line in process.stderr:
print(f"Error: {line.strip()}", file=sys.stderr)
sys.exit(return_code)

os.remove(changes_file)


def main():
"""
Main function to run the aider command.
This function performs the following tasks:
1. Checks for correct command-line usage
2. Writes the current Python path to the configuration
3. Retrieves the list of files to be processed
4. Runs the aider command with the given message
5. Applies the suggested changes
6. Displays the differences in the IDE
Usage: python command.py <message>
"""
if len(sys.argv) < 2:
print("Usage: python command.py <message>", file=sys.stderr)
sys.exit(1)

write_python_path_to_config()

message = sys.argv[1]
files = get_aider_files()

if not files:
print(
"No files added to aider. Please add files using 'aider.files.add' command.",
file=sys.stderr,
)
sys.exit(1)

print("Running aider...\n", flush=True)
changes = run_aider(message, files)

if not changes:
print("No changes suggested by aider.")
sys.exit(0)

print("\nApplying changes...\n", flush=True)

# 保存原始文件内容
original_contents = {}
for file in files:
with open(file, "r") as f:
original_contents[file] = f.read()

# 应用更改
apply_changes(changes, files)

# 读取更新后的文件内容
updated_contents = {}
for file in files:
with open(file, "r") as f:
updated_contents[file] = f.read()

# 还原原始文件内容
for file in files:
with open(file, "w") as f:
f.write(original_contents[file])

# 使用 IDEService 展示差异
ide_service = IDEService()
for index, file in enumerate(files):
ide_service.diff_apply(file, updated_contents[file])
if index < len(files) - 1:
# 等待用户确认
button = Button(
["Show Next Changes", "Cancel"],
)
button.render()

idx = button.clicked
print("click button:", idx)
if idx == 0:
continue
else:
break

print("Changes have been displayed in the IDE.")


if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions merico/aider/command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: "aider command"
workflow_python:
env_name: devchat-aider-env
version: 3.11.0
dependencies: requirements.txt
input: required
help: README.md
steps:
- run: $workflow_python $command_path/command.py "$input"
20 changes: 20 additions & 0 deletions merico/aider/files/add/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
### aider.files.add

添加文件到aider处理列表中。

用法:
/aider.files.add <file_path>

参数:
- <file_path>: 要添加的文件路径(必需)

描述:
这个命令将指定的文件添加到aider的处理列表中。添加后,该文件将被包含在后续的aider操作中。

注意:
- 文件路径必须是有效的格式。
- 如果文件已经在列表中,它不会被重复添加。
- 添加成功后,会显示当前aider文件列表。

示例:
/aider.files.add src/main.py
66 changes: 66 additions & 0 deletions merico/aider/files/add/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import os
import sys


def is_valid_path(path):
"""
检查路径是否为有效的文件路径形式
"""
try:
# 尝试规范化路径
normalized_path = os.path.normpath(path)
# 检查路径是否是绝对路径或相对路径
return (
os.path.isabs(normalized_path)
or not os.path.dirname(normalized_path) == normalized_path
)
except Exception:
return False


def add_file(file_path):
# 1. 检查是否为有效的文件路径形式
if not is_valid_path(file_path):
print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr)
sys.exit(1)

# 获取绝对路径
abs_file_path = file_path.strip()

# 2. 将新增文件路径存储到.chat/.aider_files文件中
aider_files_path = os.path.join(".chat", ".aider_files")

# 确保.chat目录存在
os.makedirs(os.path.dirname(aider_files_path), exist_ok=True)

# 读取现有文件列表
existing_files = set()
if os.path.exists(aider_files_path):
with open(aider_files_path, "r") as f:
existing_files = set(line.strip() for line in f)

# 添加新文件
existing_files.add(abs_file_path)

# 写入更新后的文件列表
with open(aider_files_path, "w") as f:
for file in sorted(existing_files):
f.write(f"{file}\n")

print(f"Added '{abs_file_path}' to aider files.")
print("\nCurrent aider files:")
for file in sorted(existing_files):
print(f"- {file}")


def main():
if len(sys.argv) != 2 or sys.argv[1].strip() == "":
print("Usage: /aider.files.add <file_path>", file=sys.stderr)
sys.exit(1)

file_path = sys.argv[1]
add_file(file_path)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions merico/aider/files/add/command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: "add files to aider"
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"
Loading

0 comments on commit a4005b6

Please sign in to comment.