mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-28 16:25:00 +00:00
add /editor command
Opens an editor for constructing a user prompt, using the currently defined chat mode. The editor is determined as follows: Look for the following environment variables, in order: 1. AIDER_EDITOR 2. VISUAL 3. EDITOR If none of these are defined, use the following defaults: Windows: notepad OS X: vim *nix: vi If an editor is not found, a RuntimeError is raised. Any arguments passed after the /editor command are inserted as content. The temporary file used for editing has an .md extension, which can be leveraged for syntax highlighting. NOTE: The editor used MUST block the process until the editor is closed -- the default editors all do this.
This commit is contained in:
parent
0022c1a67e
commit
d8e9da35d6
3 changed files with 162 additions and 6 deletions
|
@ -1357,6 +1357,13 @@ class Commands:
|
||||||
|
|
||||||
report_github_issue(issue_text, title=title, confirm=False)
|
report_github_issue(issue_text, title=title, confirm=False)
|
||||||
|
|
||||||
|
def cmd_editor(self, initial_content=""):
|
||||||
|
"Open an editor to write a prompt"
|
||||||
|
from aider.editor import pipe_editor
|
||||||
|
user_input = pipe_editor(initial_content, suffix="md")
|
||||||
|
self.io.display_user_input(user_input)
|
||||||
|
self._generic_chat_command(user_input, self.coder.edit_format)
|
||||||
|
|
||||||
|
|
||||||
def expand_subdir(file_path):
|
def expand_subdir(file_path):
|
||||||
if file_path.is_file():
|
if file_path.is_file():
|
||||||
|
|
146
aider/editor.py
Normal file
146
aider/editor.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
"""
|
||||||
|
Editor module for handling system text editor interactions.
|
||||||
|
|
||||||
|
This module provides functionality to:
|
||||||
|
- Discover and launch the system's configured text editor
|
||||||
|
- Create and manage temporary files for editing
|
||||||
|
- Handle editor preferences from environment variables
|
||||||
|
- Support cross-platform editor operations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
import shlex
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
SYSTEM = platform.system()
|
||||||
|
|
||||||
|
DEFAULT_EDITOR_NIX = "vi"
|
||||||
|
DEFAULT_EDITOR_OS_X = "vim"
|
||||||
|
DEFAULT_EDITOR_WINDOWS = "notepad"
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def print_status_message(success: bool, message: str, style: str | None = None) -> None:
|
||||||
|
"""
|
||||||
|
Print a status message with appropriate styling.
|
||||||
|
|
||||||
|
:param success: Whether the operation was successful
|
||||||
|
:param message: The message to display
|
||||||
|
:param style: Optional style override. If None, uses green for success and red for failure
|
||||||
|
"""
|
||||||
|
if style is None:
|
||||||
|
style = "bold green" if success else "bold red"
|
||||||
|
console.print(message, style=style)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
def write_temp_file(input_data: str = "", suffix: str | None = None, prefix: str | None = None, dir: str | None = None) -> str:
|
||||||
|
"""
|
||||||
|
Create a temporary file with the given input data.
|
||||||
|
|
||||||
|
:param input_data: Content to write to the temporary file
|
||||||
|
:param suffix: Optional file extension (without the dot)
|
||||||
|
:param prefix: Optional prefix for the temporary filename
|
||||||
|
:param dir: Optional directory to create the file in
|
||||||
|
:return: Path to the created temporary file
|
||||||
|
:raises: OSError if file creation or writing fails
|
||||||
|
"""
|
||||||
|
kwargs = {"prefix": prefix, "dir": dir}
|
||||||
|
if suffix:
|
||||||
|
kwargs["suffix"] = f".{suffix}"
|
||||||
|
fd, filepath = tempfile.mkstemp(**kwargs)
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, 'w') as f:
|
||||||
|
f.write(input_data)
|
||||||
|
except Exception:
|
||||||
|
os.close(fd)
|
||||||
|
raise
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
def get_environment_editor(default: str | None = None) -> str | None:
|
||||||
|
"""
|
||||||
|
Fetches the preferred editor from the environment variables.
|
||||||
|
|
||||||
|
This function checks the following environment variables in order to
|
||||||
|
determine the user's preferred editor:
|
||||||
|
|
||||||
|
- AIDER_EDITOR
|
||||||
|
- VISUAL
|
||||||
|
- EDITOR
|
||||||
|
|
||||||
|
:param default: The default editor to return if no environment variable is set.
|
||||||
|
:type default: str or None
|
||||||
|
:return: The preferred editor as specified by environment variables or the default value.
|
||||||
|
:rtype: str or None
|
||||||
|
"""
|
||||||
|
editor = os.environ.get("AIDER_EDITOR", os.environ.get("VISUAL", os.environ.get("EDITOR", default)))
|
||||||
|
return editor
|
||||||
|
|
||||||
|
|
||||||
|
def discover_editor() -> list[str]:
|
||||||
|
"""
|
||||||
|
Discovers and returns the appropriate editor command as a list of arguments.
|
||||||
|
|
||||||
|
Handles cases where the editor command includes arguments, including quoted arguments
|
||||||
|
with spaces (e.g. 'vim -c "set noswapfile"').
|
||||||
|
|
||||||
|
:return: A list of command parts ready for subprocess execution
|
||||||
|
:rtype: list[str]
|
||||||
|
"""
|
||||||
|
if SYSTEM == "Windows":
|
||||||
|
default_editor = DEFAULT_EDITOR_WINDOWS
|
||||||
|
elif SYSTEM == "Darwin":
|
||||||
|
default_editor = DEFAULT_EDITOR_OS_X
|
||||||
|
else:
|
||||||
|
default_editor = DEFAULT_EDITOR_NIX
|
||||||
|
editor = get_environment_editor(default_editor)
|
||||||
|
try:
|
||||||
|
return shlex.split(editor)
|
||||||
|
except ValueError as e:
|
||||||
|
raise RuntimeError(f"Invalid editor command format '{editor}': {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def file_editor(filepath: str) -> None:
|
||||||
|
"""
|
||||||
|
Open the specified file in the system's configured editor.
|
||||||
|
|
||||||
|
This function blocks until the editor is closed.
|
||||||
|
|
||||||
|
:param filepath: Path to the file to edit
|
||||||
|
:type filepath: str
|
||||||
|
:raises RuntimeError: If the editor command is invalid
|
||||||
|
"""
|
||||||
|
command_parts = discover_editor()
|
||||||
|
command_parts.append(filepath)
|
||||||
|
subprocess.call(command_parts)
|
||||||
|
|
||||||
|
|
||||||
|
def pipe_editor(input_data: str = "", suffix: str | None = None) -> str:
|
||||||
|
"""
|
||||||
|
Opens the system editor with optional input data and returns the edited content.
|
||||||
|
|
||||||
|
This function creates a temporary file with the provided input data, opens it in
|
||||||
|
the system editor, waits for the user to make changes and close the editor, then
|
||||||
|
reads and returns the modified content. The temporary file is deleted afterwards.
|
||||||
|
|
||||||
|
:param input_data: Initial content to populate the editor with
|
||||||
|
:type input_data: str
|
||||||
|
:param suffix: Optional file extension for the temporary file (e.g. '.txt', '.md')
|
||||||
|
:type suffix: str or None
|
||||||
|
:return: The edited content after the editor is closed
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
filepath = write_temp_file(input_data, suffix)
|
||||||
|
file_editor(filepath)
|
||||||
|
with open(filepath, "r") as f:
|
||||||
|
output_data = f.read()
|
||||||
|
try:
|
||||||
|
os.remove(filepath)
|
||||||
|
except PermissionError:
|
||||||
|
print_status_message(False, f"WARNING: Unable to delete temporary file {filepath!r}. You may need to delete it manually.")
|
||||||
|
return output_data
|
15
aider/io.py
15
aider/io.py
|
@ -457,14 +457,17 @@ class InputOutput:
|
||||||
log_file.write(f"{role.upper()} {timestamp}\n")
|
log_file.write(f"{role.upper()} {timestamp}\n")
|
||||||
log_file.write(content + "\n")
|
log_file.write(content + "\n")
|
||||||
|
|
||||||
|
def display_user_input(self, inp):
|
||||||
|
if self.pretty and self.user_input_color:
|
||||||
|
style = dict(style=self.user_input_color)
|
||||||
|
else:
|
||||||
|
style = dict()
|
||||||
|
|
||||||
|
self.console.print(Text(inp), **style)
|
||||||
|
|
||||||
def user_input(self, inp, log_only=True):
|
def user_input(self, inp, log_only=True):
|
||||||
if not log_only:
|
if not log_only:
|
||||||
if self.pretty and self.user_input_color:
|
self.display_user_input(inp)
|
||||||
style = dict(style=self.user_input_color)
|
|
||||||
else:
|
|
||||||
style = dict()
|
|
||||||
|
|
||||||
self.console.print(Text(inp), **style)
|
|
||||||
|
|
||||||
prefix = "####"
|
prefix = "####"
|
||||||
if inp:
|
if inp:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue