mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-25 23:05: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)
|
||||
|
||||
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):
|
||||
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(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):
|
||||
if not log_only:
|
||||
if self.pretty and self.user_input_color:
|
||||
style = dict(style=self.user_input_color)
|
||||
else:
|
||||
style = dict()
|
||||
|
||||
self.console.print(Text(inp), **style)
|
||||
self.display_user_input(inp)
|
||||
|
||||
prefix = "####"
|
||||
if inp:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue