branch: elpa/aidermacs commit dfbe5ba5fe731faf28302017da3c41e1aa9e6e4b Author: Kang Tu <tninja@Pengs-MacBook-Pro-2.local> Commit: Kang Tu <tninja@Pengs-MacBook-Pro-2.local>
add aider.el, update comment --- aider.el | 230 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/aider.el b/aider.el new file mode 100644 index 0000000000..749c4ea95f --- /dev/null +++ b/aider.el @@ -0,0 +1,230 @@ +;;; aider.el --- Aider package for interactive conversation with OpenAI -*- lexical-binding: t; -*- + +;; Author: Kang Tu <tni...@gmail.com> +;; Version: 0.1.0 +;; Package-Requires: ((emacs "24.1") (transient "0.3.0")) +;; Keywords: convenience, tools +;; URL: https://github.com/tninja/aider.el + +;;; Commentary: +;; This package provides an interactive interface to communicate with https://github.com/paul-gauthier/aider. + +;;; Code: + +(require 'transient) + +(defgroup aider nil + "Customization group for the Aider package." + :prefix "aider-" + :group 'convenience) + + +(defcustom aider-deepseek-api-key deepseek-api-key + "DeepSeek API key for Aider." + :type 'string + :group 'aider) + +(defcustom aider-args '("--deepseek") + "Arguments to pass to the Aider command." + :type '(repeat string) + :group 'aider) + +;; (defcustom aider-openai-api-key chatgpt-shell-openai-key +;; "OpenAI API key for Aider." +;; :type 'string +;; :group 'aider) + +;; (defcustom aider-args '("--model" "gpt-4o-mini") +;; "Arguments to pass to the Aider command." +;; :type '(repeat string) +;; :group 'aider) + +;; Define the keymap for Aider commands +(defvar aider-global-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "a") 'run-aider) + (define-key map (kbd "z") 'aider-switch-to-buffer) + (define-key map (kbd "s") 'aider-add-current-file) + (define-key map (kbd "d") 'aider-drop-current-file) + (define-key map (kbd "c") 'aider-send-command) + (define-key map (kbd "d") 'aider-code-command) + (define-key map (kbd "q") 'aider-ask-question) + (define-key map (kbd "t") 'aider-architect-command) + (define-key map (kbd "e") 'aider-region-code-command) + (define-key map (kbd "u") 'aider-undo-last-change) + (define-key map (kbd "r") 'aider-reset-command) + (define-key map (kbd "m") 'aider-transient-menu) + map) + "Global keymap for Aider commands.") + +;; Activate the global keymap +(define-key global-map (kbd "C-c a") aider-global-map) + +;; Transient menu for Aider commands +(transient-define-prefix aider-transient-menu () + "Transient menu for Aider commands." + ["Aider Menu" + ["Aider process" + ("a" "Run Aider" run-aider) + ("s" "Add Current File" aider-add-current-file) + ("z" "Switch to Aider Buffer" aider-switch-to-buffer) + ("d" "Drop Current File" aider-drop-current-file) + ] + ["Code change" + ("d" "Code Change" aider-code-command) + ("e" "Region Code Change" aider-region-code-command) + ("u" "Undo Last Change" aider-undo-last-change) ;; Menu item for undo last change + ] + ["Discussion" + ("q" "Ask Question" aider-ask-question) + ("t" "Architect Discussion" aider-architect-command) + ] + ["Other" + ("r" "Reset Aider" aider-reset-command) ;; Menu item for reset command + ("c" "General Command" aider-send-command) + ] + ]) + + +(defun aider-buffer-name () + "Generate the Aider buffer name based on the path from the home folder to the git repo of the current active buffer using a git command." + (let* ((buffer-file-path (buffer-file-name)) + (git-repo-path (shell-command-to-string "git rev-parse --show-toplevel")) + (home-path (expand-file-name "~")) + (relative-path (substring git-repo-path (length home-path)))) + (format "*aider:%s*" (concat "~" (replace-regexp-in-string "\n" "" relative-path))))) + +(defun run-aider () + "Create a comint-based buffer and run 'aider' for interactive conversation." + (interactive) + (let* ((buffer-name (aider-buffer-name)) + (command "aider")) + ;; Check if the buffer already has a running process + (unless (comint-check-proc buffer-name) + ;; Create a new comint buffer and start the process + ;; (setenv "OPENAI_API_KEY" aider-openai-api-key) + (setenv "DEEPSEEK_API_KEY" aider-deepseek-api-key) + (apply 'make-comint-in-buffer "aider" buffer-name command nil aider-args) + ;; Optionally, you can set the mode or add hooks here + (with-current-buffer buffer-name + (comint-mode) + )) + ;; Switch to the buffer + (aider-add-current-file) + (pop-to-buffer buffer-name))) + +;; Function to switch to the Aider buffer +(defun aider-switch-to-buffer () + "Switch to the Aider buffer." + (interactive) + (let ((buffer (get-buffer (aider-buffer-name)))) + (if buffer + (pop-to-buffer buffer) + (message "Aider buffer '%s' does not exist." (aider-buffer-name))))) + +;; Function to reset the Aider buffer +(defun aider-reset-command () + "Send the command \"/reset\" to the Aider buffer." + (interactive) + (aider--send-command "/reset")) + +;; Shared helper function to send commands to *aider* buffer +(defun aider--send-command (command) + "Send COMMAND to the *aider* comint buffer after performing necessary checks. +COMMAND should be a string representing the command to send." + ;; Check if the *aider* buffer exists + (if-let ((aider-buffer (get-buffer (aider-buffer-name)))) + (let ((aider-process (get-buffer-process aider-buffer))) + ;; Check if the *aider* buffer has an active process + (if (and aider-process (comint-check-proc aider-buffer)) + (progn + ;; Ensure the command ends with a newline + (unless (string-suffix-p "\n" command) + (setq command (concat command "\n"))) + ;; Send the command to the aider process + (comint-send-string aider-buffer command) + ;; Provide feedback to the user + (message "Sent command to *aider*: %s" (string-trim command)) + (aider-switch-to-buffer)) + (message "No active process found in buffer %s." (aider-buffer-name)))) + (message "Buffer %s does not exist. Please start 'aider' first." (aider-buffer-name)))) + +;; Function to send "/add <current buffer file full path>" to *aider* buffer +(defun aider-add-current-file () + "Send the command \"/add <current buffer file full path>\" to the *aider* comint buffer." + (interactive) + ;; Ensure the current buffer is associated with a file + (if (not buffer-file-name) + (message "Current buffer is not associated with a file.") + (let ((file-path (expand-file-name buffer-file-name))) + ;; Construct the command + (let ((command (format "/add %s" file-path))) + ;; Use the shared helper function to send the command + (aider--send-command command))))) + +;; New function to send "/drop <current buffer file full path>" to *aider* buffer +(defun aider-drop-current-file () + "Send the command \"/drop <current buffer file full path>\" to the *aider* comint buffer." + (interactive) + ;; Ensure the current buffer is associated with a file + (if (not buffer-file-name) + (message "Current buffer is not associated with a file.") + (let ((file-path (expand-file-name buffer-file-name))) + ;; Construct the command + (let ((command (format "/drop %s" file-path))) + ;; Use the shared helper function to send the command + (aider--send-command command))))) + +;; Function to send a custom command to *aider* buffer +(defun aider-send-command (command) + "Prompt the user to input COMMAND and send it to the *aider* comint buffer. +COMMAND is a string representing the command to send." + (interactive + (list (read-string "Enter command to send to aider: "))) + ;; Use the shared helper function to send the command + (aider--send-command command)) + +;; New function to get command from user and send it prefixed with "/code " +(defun aider-code-command () + "Prompt the user for a command and send it to the *aider* comint buffer prefixed with \"/code \"." + (interactive) + (let ((command (read-string "Enter code command: "))) + (aider--send-command (concat "/code " command)))) + +;; New function to get command from user and send it prefixed with "/ask " +(defun aider-ask-question () + "Prompt the user for a command and send it to the *aider* comint buffer prefixed with \"/ask \"." + (interactive) + (let ((command (read-string "Enter ask command: "))) + (aider--send-command (concat "/ask " command)))) + +;; New function to get command from user and send it prefixed with "/architect " +(defun aider-architect-command () + "Prompt the user for a command and send it to the *aider* comint buffer prefixed with \"/architect \"." + (interactive) + (let ((command (read-string "Enter architect command: "))) + (aider--send-command (concat "/architect " command)))) + +;; Modified function to get command from user and send it based on selected region +(defun aider-undo-last-change () + "Undo the last change made by Aider." + (interactive) + (aider--send-command "/undo")) + +(defun aider-region-code-command () + "Get a command from the user and send it to the *aider* comint buffer based on the selected region. +The command will be formatted as \"/code \" followed by the user command and the text from the selected region." + (interactive) + (if (use-region-p) + (let* ((region-text (buffer-substring-no-properties (region-beginning) (region-end))) + (processed-region-text (replace-regexp-in-string "\n" "\\\\n" region-text)) + (user-command (read-string "Enter your command: ")) + (function-name (or (which-function) "unknown function")) + (command (format "/code \"in function %s, for the following code block, %s: %s\"" + function-name user-command processed-region-text))) + (aider--send-command command)) + (message "No region selected."))) + +(provide 'aider) + +;;; aider.el ends here