branch: elpa/aidermacs commit 1a47d246d0a99bfafd3ef7df31c8e47890e976c1 Merge: ba8508d7d8 9db897d312 Author: Matthew Zeng <matthew...@posteo.net> Commit: GitHub <nore...@github.com>
Merge pull request #45 from MatthewZMD/ediff Use Ediff to Show Better Code Changes --- README.md | 52 ++++--- aidermacs-backend-comint.el | 43 +++++- aidermacs-backend-vterm.el | 76 +++++++--- aidermacs-backends.el | 135 ++++++++++++++--- aidermacs-models.el | 17 +-- aidermacs.el | 356 ++++++++++++++++++++++++++++++++++---------- 6 files changed, 522 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 83a0f0ee1a..b9a970cb26 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,9 @@ # Aidermacs: AI Pair Programming in Emacs -Miss using [Cursor](https://cursor.sh) but prefer living in Emacs? Aidermacs brings Cursor-like AI-powered development to your Emacs workflow by integrating [Aider](https://github.com/paul-gauthier/aider), one of the most powerful open-source AI pair programming tools available. As a community-driven project, Aidermacs prioritizes the needs and preferences of Emacs users. It provides the same powerful features you'd find in Cursor: - -Key features: +Missing [Cursor](https://cursor.sh) but prefer living in Emacs? Aidermacs brings Cursor-like AI-powered development to your Emacs workflow by integrating [Aider](https://github.com/paul-gauthier/aider), one of the most powerful open-source AI pair programming tools. As a community-driven project, Aidermacs prioritizes Emacs users' needs and preferences while providing the same powerful features found in Cursor! +- Built-in **Ediff** integration for reviewing AI-generated changes - Top performance on the SWE Bench, solving real GitHub issues in major open source projects - Support for multi-file edits in complex codebases - Real-time file synchronization for true pair programming @@ -22,14 +21,14 @@ Key features: ### Community-Driven Development -Aidermacs thrives on community involvement. We believe that the best software is built collaboratively, with input from users and contributors. We encourage you to: +Aidermacs thrives on community involvement. We believe collaborative development with user and contributor input creates the best software. We encourage you to: - Contribute Code: Submit pull requests with bug fixes, new features, or improvements to existing functionality. - Report Issues: Let us know about any bugs, unexpected behavior, or feature requests through GitHub Issues. - Share Ideas: Participate in discussions and propose new ideas for making Aidermacs even better. - Improve Documentation: Help us make the documentation clearer, more comprehensive, and easier to use. -Your contributions are essential to making Aidermacs the best AI pair programming tool in Emacs! +Your contributions are essential for making Aidermacs the best AI pair programming tool in Emacs! <a href = "https://github.com/MatthewZMD/aidermacs/graphs/contributors"> <img src = "https://contrib.rocks/image?repo=MatthewZMD/aidermacs"/> @@ -68,13 +67,13 @@ You can customize the default AI model used by Aidermacs by setting the `aiderma (setq aidermacs-default-model "sonnet") ``` -This allows you to easily switch between different AI models without modifying the `aidermacs-extra-args` variable. +This enables easy switching between different AI models without modifying the `aidermacs-extra-args` variable. *Note: This configuration will be overwritten by the existence of an `.aider.conf.yml` file (see [details](#Overwrite-Configuration-with-Configuration-File)).* ### Dynamic Model Selection -Aidermacs provides intelligent model selection for the solo (non-Architect) mode that automatically detects and integrates with multiple AI providers: +Aidermacs offers intelligent model selection for solo (non-Architect) mode, automatically detecting and integrating with multiple AI providers: - Automatically fetches available models from supported providers (OpenAI, Anthropic, DeepSeek, Google Gemini, OpenRouter) - Caches model lists for quick access @@ -97,7 +96,7 @@ The system will automatically filter models to only show ones that are: ### Architect Mode - Separating Code Reasoning and Editing Models -Aidermacs supports an experimental mode that leverages two models for each coding task: an Architect model for reasoning and an Editor model for generating code edits. This approach has **achieved state-of-the-art (SOTA) results on aider's code editing benchmark**, as detailed in [this blog post](https://aider.chat/2024/09/26/architect.html). +Aidermacs features an experimental mode using two specialized models for each coding task: an Architect model for reasoning and an Editor model for code generation. This approach has **achieved state-of-the-art (SOTA) results on aider's code editing benchmark**, as detailed in [this blog post](https://aider.chat/2024/09/26/architect.html). To enable this mode, set `aidermacs-use-architect-mode` to `t`. You must also configure the `aidermacs-architect-model` variable to specify the model to use for the Architect role. @@ -117,7 +116,7 @@ When Architect mode is enabled, the `aidermacs-default-model` setting is ignored Choose your preferred terminal backend by setting `aidermacs-backend`: -`vterm` provides better terminal compatibility, while `comint` is a simple, built-in option that's still fully compatible with aidermacs. +`vterm` offers better terminal compatibility, while `comint` provides a simple, built-in option that remains fully compatible with Aidermacs. ```emacs-lisp ;; Use vterm backend (default is comint) @@ -142,20 +141,20 @@ You can customize keybindings for multiline input, this key allows you to enter ### Re-Enable Auto-Commits -Aider by default automatically commits changes made by the AI. We find this behavior *very* intrusive, so we disabled it for you. You can re-enable auto-commits by setting `aidermacs-auto-commits` to `t`: +Aider automatically commits AI-generated changes by default. We consider this behavior *very* intrusive, so we've disabled it. You can re-enable auto-commits by setting `aidermacs-auto-commits` to `t`: ```emacs-lisp ;; Enable auto-commits (setq aidermacs-auto-commits t) ``` -With auto-commits disabled, you'll need to manually commit changes using your preferred Git workflow. +With auto-commits disabled, you must manually commit changes using your preferred Git workflow. *Note: This configuration will be overwritten by the existence of an `.aider.conf.yml` file (see [details](#Overwrite-Configuration-with-Configuration-File)).* ### Customizing Aider Options with `aidermacs-extra-args` -If the above configurations aren't enough already, the `aidermacs-extra-args` variable allows you to pass any command-line options supported by Aider. +If these configurations aren't sufficient, the `aidermacs-extra-args` variable enables passing any Aider-supported command-line options. See the [Aider configuration documentation](https://aider.chat/docs/config/options.html) for a full list of available options. @@ -328,29 +327,36 @@ While `aider.el` strictly mirrors Aider's CLI behavior, `Aidermacs` is built aro With `Aidermacs`, you get: -1. Intelligent Model Selection +1. Built-in Ediff Integration for AI-Generated Changes + - Seamless Code Review: Automatically shows diffs for all AI-modified files using Emacs' powerful `ediff` interface + - Familiar Interface: Uses Emacs' native `ediff` workflow for reviewing changes + - Interactive Workflow: Accept or reject changes with standard `ediff` commands + - Syntax Highlighting: Maintains proper syntax highlighting during comparisons + - Safe Change Management: Preserves original file states for easy comparison and rollback + +2. Intelligent Model Selection - Automatic discovery of available models from multiple providers - Real-time model compatibility checking - Seamless integration with your configured API keys - Caching for quick access to frequently used models - Support for both popular pre-configured models and dynamically discovered ones -2. Flexible Terminal Backend Support +3. Flexible Terminal Backend Support - `Aidermacs` supports multiple terminal backends (comint and vterm) for better compatibility and performance - Easy configuration to choose your preferred terminal emulation - Extensible architecture for adding new backends -3. Smarter Syntax Highlighting +4. Smarter Syntax Highlighting - AI-generated code appears with proper syntax highlighting in major languages. - Ensures clarity and readability without additional configuration. -4. Better Support for Multiline Input +5. Better Support for Multiline Input - `aider` is primarily designed as a command-line program, where multiline input is restricted by terminal limitations. - Terminal-based tools require special syntax or manual formatting to handle multiline input, which can be cumbersome and unintuitive. - `Aidermacs` eliminates these restrictions by handling multiline prompts natively within Emacs, allowing you to compose complex AI requests just like any other text input. - Whether you're pasting blocks of code or refining AI-generated responses, multiline interactions in `Aidermacs` feel natural and seamless. -5. Enhanced File Management from Emacs +6. Enhanced File Management from Emacs - List files currently in chat with `M-x aidermacs-list-added-files` - Drop specific files from chat with `M-x aidermacs-drop-file` - View output history with `M-x aidermacs-show-output-history` @@ -359,17 +365,17 @@ With `Aidermacs`, you get: - Create a temporary file for adding code snippets or notes to the Aider session with `M-x aidermacs-create-session-scratchpad` - and more -6. Greater Configurability +7. Greater Configurability - `Aidermacs` offers more customization options to tailor the experience to your preferences. -7. Streamlined Transient Menu Selection +8. Streamlined Transient Menu Selection - The transient menus have been completely redesigned to encompass functionality and ergonomics, prioritizing user experience. -8. Flexible Ways to Add Content +9. Flexible Ways to Add Content - `Aidermacs` provides multiple ways to add content to the Aider session, including adding files, creating temporary scratchpad files, and more. -9. Community-Driven Development - - `Aidermacs` is actively developed and maintained by the community, incorporating user feedback and contributions. - - We prioritize features and improvements that directly benefit Emacs users, ensuring a tool that evolves with your needs. +10. Community-Driven Development + - `Aidermacs` is actively developed and maintained by the community, incorporating user feedback and contributions. + - We prioritize features and improvements that directly benefit Emacs users, ensuring a tool that evolves with your needs. ... and more to come 🚀 diff --git a/aidermacs-backend-comint.el b/aidermacs-backend-comint.el index 2965fa612e..e548a3a32c 100644 --- a/aidermacs-backend-comint.el +++ b/aidermacs-backend-comint.el @@ -24,6 +24,11 @@ (require 'comint) +;; Forward declarations +(declare-function aidermacs--prepare-for-code-edit "aidermacs") +(declare-function aidermacs--cleanup-all-temp-files "aidermacs") +(declare-function aidermacs--show-ediff-for-edited-files "aidermacs") +(declare-function aidermacs--detect-edited-files "aidermacs") (declare-function aidermacs--process-message-if-multi-line "aidermacs" (str)) (defcustom aidermacs-language-name-map '(("elisp" . "emacs-lisp") @@ -65,7 +70,6 @@ This allows for multi-line input without sending the command." "Face for search/replace block content." :group 'aidermacs) - (defvar aidermacs-font-lock-keywords '(("^\x2500+\n?" 0 '(face aidermacs-command-separator) t) ("^\x2500+" 0 '(face nil display (space :width 2))) @@ -97,6 +101,24 @@ This allows for multi-line input without sending the command." This variable holds the actual marker text (e.g., <<<<<<< SEARCH, =======, etc.) that was matched at the start of the current syntax block.") +(defvar-local aidermacs--comint-output-temp "" + "Temporary output variable storing the raw output string.") + +(defun aidermacs--comint-output-filter (output) + "Accumulate OUTPUT string until a prompt is detected, then store it." + (when (and (aidermacs--is-aidermacs-buffer-p) (not (string-empty-p output))) + (setq aidermacs--comint-output-temp + (concat aidermacs--comint-output-temp (substring-no-properties output))) + ;; Check if the output contains a prompt + (when (string-match-p "\n[^[:space:]]*>[[:space:]]$" aidermacs--comint-output-temp) + (aidermacs--store-output aidermacs--comint-output-temp) + ;; Check if any files were edited and show ediff if needed + (let ((edited-files (aidermacs--detect-edited-files))) + (if edited-files + (aidermacs--show-ediff-for-edited-files edited-files) + (aidermacs--cleanup-all-temp-files))) + (setq aidermacs--comint-output-temp "")))) + (defun aidermacs-reset-font-lock-state () "Reset font lock state to default for processing a new source block." (setq aidermacs--syntax-block-delimiter nil @@ -239,7 +261,7 @@ _OUTPUT is the text to be processed." (cdr (cl-assoc-if (lambda (re) (string-match re file)) auto-mode-alist)))) 'fundamental-mode)) -(defun aidermacs-kill-buffer () +(defun aidermacs--comint-cleanup-hook () "Clean up the fontify buffer." (when (bufferp aidermacs--syntax-work-buffer) (kill-buffer aidermacs--syntax-work-buffer))) @@ -248,6 +270,12 @@ _OUTPUT is the text to be processed." "Reset font-lock state before executing a command. PROC is the process to send to. STRING is the command to send." (aidermacs-reset-font-lock-state) + ;; Store the command for tracking in the correct buffer + (with-current-buffer (process-buffer proc) + (unless (member string '("" "y" "n" "d" "yes" "no")) + (setq aidermacs--last-command string) + ;; Always prepare for potential edits + (aidermacs--prepare-for-code-edit))) (comint-simple-send proc (aidermacs--process-message-if-multi-line string))) (defun aidermacs-run-comint (program args buffer-name) @@ -264,8 +292,10 @@ BUFFER-NAME is the name for the aidermacs buffer." (setq-local comint-input-sender 'aidermacs-input-sender) (setq aidermacs--syntax-work-buffer (get-buffer-create (concat " *aidermacs-syntax" buffer-name))) - (add-hook 'kill-buffer-hook #'aidermacs-kill-buffer nil t) + (add-hook 'kill-buffer-hook #'aidermacs--comint-cleanup-hook nil t) (add-hook 'comint-output-filter-functions #'aidermacs-fontify-blocks 100 t) + (add-hook 'comint-output-filter-functions #'aidermacs--comint-output-filter) + (advice-add 'comint-interrupt-subjob :around #'aidermacs--cleanup-temp-files-on-interrupt-comint) (let ((local-map (make-sparse-keymap))) (set-keymap-parent local-map comint-mode-map) (define-key local-map (kbd aidermacs-comint-multiline-newline-key) #'comint-accumulate) @@ -306,6 +336,13 @@ The output is collected and passed to the current callback." (aidermacs--store-output (with-current-buffer output-buffer (buffer-string)))))) +(defun aidermacs--cleanup-temp-files-on-interrupt-comint (orig-fun &rest args) + "Run `aidermacs--cleanup-all-temp-files' after interrupting a comint subjob. +ORIG-FUN is the original function being advised. ARGS are its arguments." + (apply orig-fun args) + (when (aidermacs--is-aidermacs-buffer-p) + (aidermacs--cleanup-all-temp-files))) + (provide 'aidermacs-backend-comint) ;;; aidermacs-backend-comint.el ends here diff --git a/aidermacs-backend-vterm.el b/aidermacs-backend-vterm.el index 70bfb832b6..38f4345fe7 100644 --- a/aidermacs-backend-vterm.el +++ b/aidermacs-backend-vterm.el @@ -38,6 +38,12 @@ (declare-function vterm-send-return "vterm") (declare-function vterm-insert "vterm") +(declare-function aidermacs--prepare-for-code-edit "aidermacs") +(declare-function aidermacs--cleanup-all-temp-files "aidermacs") +(declare-function aidermacs--show-ediff-for-edited-files "aidermacs") +(declare-function aidermacs--detect-edited-files "aidermacs") +(declare-function aidermacs--store-output "aidermacs") +(declare-function aidermacs--is-aidermacs-buffer-p "aidermacs") (defvar-local aidermacs--vterm-active-timer nil "Store the active timer for vterm output processing.") @@ -55,19 +61,11 @@ :type 'string :group 'aidermacs) -(defun aidermacs--is-aidermacs-vterm-buffer-p (&optional buffer) - "Check if BUFFER is an aidermacs vterm buffer. -If BUFFER is nil, check the current buffer. -Returns non-nil if the buffer name matches the aidermacs buffer pattern." - (let ((buf (or buffer (current-buffer)))) - (and (derived-mode-p 'vterm-mode) - (string-match-p "^\\*aidermacs:" (buffer-name buf))))) - -(defun aidermacs--vterm-check-finish-sequence-repeated (proc orig-filter start-point expected) +(defun aidermacs--vterm-check-finish-sequence-repeated (proc orig-filter start-point) "Check for the finish sequence in PROC's buffer. PROC is the process to check. ORIG-FILTER is the original process filter. -START-POINT is the starting position for output capture. EXPECTED is the -pattern to match. If the finish sequence is detected, store the output via +START-POINT is the starting position for output capture. +If the finish sequence is detected, store the output via `aidermacs--store-output`, restore ORIG-FILTER, and return t." (when (buffer-live-p (process-buffer proc)) (with-current-buffer (process-buffer proc) @@ -82,7 +80,9 @@ pattern to match. If the finish sequence is detected, store the output via (error (point-max)))) ;; Only check if we have a new prompt or haven't checked this position yet (last-check (or aidermacs--vterm-last-check-point start-point)) - (should-check (> prompt-point last-check))) + (should-check (> prompt-point last-check)) + ;; Simplified pattern that just looks for a shell prompt + (expected "^[^[:space:]]*>[[:space:]]")) ;; Update the last check point (setq aidermacs--vterm-last-check-point prompt-point) @@ -100,8 +100,13 @@ pattern to match. If the finish sequence is detected, store the output via ;; If we found a shell prompt (when (string-match-p expected prompt-line) - (let ((output (buffer-substring-no-properties start-point seq-start))) - (aidermacs--store-output (string-trim output))) + (let ((output (buffer-substring-no-properties start-point seq-start)) + (edited-files (aidermacs--detect-edited-files))) + (aidermacs--store-output (string-trim output)) + ;; Check if any files were edited and show ediff if needed + (if edited-files + (aidermacs--show-ediff-for-edited-files edited-files) + (aidermacs--cleanup-all-temp-files))) (set-process-filter proc orig-filter)))))))) (defun aidermacs--vterm-output-advice (orig-fun &rest args) @@ -109,15 +114,18 @@ pattern to match. If the finish sequence is detected, store the output via ORIG-FUN is the original function being advised. ARGS are its arguments. This sets a temporary process filter that checks for the finish sequence after each output chunk, reducing the need for timers." - (if (aidermacs--is-aidermacs-vterm-buffer-p) + (if (aidermacs--is-aidermacs-buffer-p) (let* ((start-point (condition-case nil (vterm--get-prompt-point) (error (point-min)))) (proc (get-buffer-process (current-buffer))) - ;; Simplified pattern that just looks for a shell prompt - (expected "^[^[:space:]]*>[[:space:]]") (orig-filter (process-filter proc))) + ;; Store the command for tracking in the correct buffer + (with-current-buffer (process-buffer proc) + (when (and args (car args) (stringp (car args))) + (setq-local aidermacs--last-command (car args)))) + ;; Initialize tracking variables (setq-local aidermacs--vterm-last-check-point nil) @@ -137,7 +145,7 @@ after each output chunk, reducing the need for timers." aidermacs-vterm-check-interval (lambda () (when (aidermacs--vterm-check-finish-sequence-repeated - proc orig-filter start-point expected) + proc orig-filter start-point) (when (timerp aidermacs--vterm-active-timer) (cancel-timer aidermacs--vterm-active-timer) (setq aidermacs--vterm-active-timer nil)) @@ -163,6 +171,8 @@ BUFFER-NAME is the name for the vterm buffer." aidermacs--vterm-active-timer nil aidermacs--vterm-last-check-point nil) (advice-add 'vterm-send-return :around #'aidermacs--vterm-output-advice) + (advice-add 'vterm-send-return :before #'aidermacs--vterm-capture-keyboard-input) + (advice-add 'vterm--self-insert :after #'aidermacs--cleanup-temp-files-on-interrupt-vterm) ;; Set up multi-line key binding (let ((map (make-sparse-keymap))) (set-keymap-parent map (current-local-map)) @@ -188,12 +198,40 @@ BUFFER is the target buffer to send to. COMMAND is the text to send." (interactive) (vterm-insert "\n")) +(defun aidermacs--vterm-capture-keyboard-input (orig-fun &rest args) + "Capture keyboard input in vterm. +ORIG-FUN is the original function being advised. ARGS are its arguments." + (when (and (aidermacs--is-aidermacs-buffer-p) + (eq this-command 'vterm-send-return)) + ;; Get the current line content which should be the command + ;; TODO: current line may not be enough + (save-excursion + (let* ((prompt-point (condition-case nil + (vterm--get-prompt-point) + (error (point-min)))) + (command (buffer-substring-no-properties + prompt-point + (line-end-position)))) + (when (not (string-empty-p command)) + (setq-local aidermacs--last-command command) + ;; Always prepare for potential edits + (aidermacs--prepare-for-code-edit))))) + (apply orig-fun args)) + (defun aidermacs--vterm-cleanup () "Clean up vterm resources when buffer is killed." (when aidermacs--vterm-active-timer (cancel-timer aidermacs--vterm-active-timer) (setq-local aidermacs--vterm-active-timer nil)) - (setq-local aidermacs--vterm-last-check-point nil)) + (setq-local aidermacs--vterm-last-check-point nil) + (advice-remove 'vterm-send-return #'aidermacs--vterm-capture-keyboard-input)) + +(defun aidermacs--cleanup-temp-files-on-interrupt-vterm (&rest args) + "Run `aidermacs--cleanup-all-temp-files' after interrupting a vterm subjob. +ARGS are the arguments." + (when (and (aidermacs--is-aidermacs-buffer-p) + (equal (this-command-keys) "\C-c\C-c")) + (aidermacs--cleanup-all-temp-files))) (provide 'aidermacs-backend-vterm) diff --git a/aidermacs-backends.el b/aidermacs-backends.el index 0e7fc3bf87..fed3f98883 100644 --- a/aidermacs-backends.el +++ b/aidermacs-backends.el @@ -26,10 +26,10 @@ (when (commandp 'vterm) (require 'aidermacs-backend-vterm)) -(declare-function aidermacs-run-vterm "aidermacs-backend-vterm" - (program args buffer-name)) -(declare-function aidermacs--send-command-vterm "aidermacs-backend-vterm" - (buffer command)) +(declare-function aidermacs-run-vterm "aidermacs-backend-vterm" (program args buffer-name)) +(declare-function aidermacs--send-command-vterm "aidermacs-backend-vterm" (buffer command)) +(declare-function aidermacs-project-root "aidermacs" ()) +(declare-function aidermacs--get-files-in-session "aidermacs" (callback)) (defgroup aidermacs-backends nil "Backend customization for aidermacs." @@ -37,7 +37,7 @@ (defcustom aidermacs-backend 'comint "Backend to use for the aidermacs process. -Options are `'comint' (the default) or `'vterm'. When set to `'vterm', +Options are `comint' (the default) or `vterm'. When set to `vterm', aidermacs launches a fully functional vterm buffer instead of using a comint process." :type '(choice (const :tag "Comint" comint) @@ -54,14 +54,14 @@ of using a comint process." :type 'integer :group 'aidermacs-output) -(defvar aidermacs--output-history nil +(defvar-local aidermacs--output-history nil "List to store aidermacs output history. Each entry is a cons cell (timestamp . output-text).") -(defvar aidermacs--last-command nil +(defvar-local aidermacs--last-command nil "Store the last command sent to aidermacs.") -(defvar aidermacs--current-output "" +(defvar-local aidermacs--current-output "" "Accumulator for current output being captured as a string.") (defun aidermacs-get-output-history (&optional limit) @@ -78,12 +78,92 @@ Returns a list of (timestamp . output-text) pairs, most recent first." (interactive) (setq aidermacs--output-history nil)) -(defvar aidermacs--current-callback nil +(defvar-local aidermacs--current-callback nil "Store the callback function for the current command.") -(defvar aidermacs--in-callback nil +(defvar-local aidermacs--in-callback nil "Flag to prevent recursive callbacks.") +(defvar-local aidermacs--tracked-files nil + "List of files that have been mentioned in the aidermacs output. +This is used to avoid having to run /ls repeatedly.") + +(defun aidermacs--verify-tracked-files () + "Verify files in `aidermacs--tracked-files` exist. +Remove any files that don't exist." + (let ((project-root (aidermacs-project-root)) + (valid-files nil)) + (dolist (file aidermacs--tracked-files) + (let* ((is-readonly (string-match-p " (read-only)$" file)) + (actual-file (if is-readonly + (substring file 0 (- (length file) 12)) + file)) + (full-path (expand-file-name actual-file project-root))) + (when (file-exists-p full-path) + (push file valid-files)))) + (setq aidermacs--tracked-files valid-files))) + +(defun aidermacs--parse-output-for-files (output) + "Parse OUTPUT for files and add them to `aidermacs--tracked-files'." + (when output + (let ((lines (split-string output "\n")) + (last-line "")) + (dolist (line lines) + (cond + ;; Applied edit to <filename> + ((string-match "Applied edit to \\(\\./\\)?\\(.+\\)" line) + (when-let ((file (match-string 2 line))) + (add-to-list 'aidermacs--tracked-files file))) + + ;; Added <filename> to the chat. + ((string-match "Added \\(\\./\\)?\\(.+\\) to the chat" line) + (when-let ((file (match-string 2 line))) + (add-to-list 'aidermacs--tracked-files file))) + + ;; Removed <filename> from the chat (with or without ./ prefix) + ((string-match "Removed \\(\\./\\)?\\(.+\\) from the chat" line) + (when-let ((file (match-string 2 line))) + (setq aidermacs--tracked-files (delete file aidermacs--tracked-files)))) + + ;; Added <filename> to read-only files. + ((string-match "Added \\(\\./\\)?\\(.+\\) to read-only files" line) + (when-let ((file (match-string 2 line))) + (add-to-list 'aidermacs--tracked-files (concat file " (read-only)")))) + + ;; Moved <file> from editable to read-only files in the chat + ((string-match "Moved \\(\\./\\)?\\(.+\\) from editable to read-only files in the chat" line) + (when-let ((file (match-string 2 line))) + (let ((editable-file (replace-regexp-in-string " (read-only)$" "" file))) + (setq aidermacs--tracked-files (delete editable-file aidermacs--tracked-files)) + (add-to-list 'aidermacs--tracked-files (concat file " (read-only)"))))) + + ;; Moved <file> from read-only to editable files in the chat + ((string-match "Moved \\(\\./\\)?\\(.+\\) from read-only to editable files in the chat" line) + (when-let ((file (match-string 2 line))) + (let ((read-only-file (concat file " (read-only)"))) + (setq aidermacs--tracked-files (delete read-only-file aidermacs--tracked-files)) + (add-to-list 'aidermacs--tracked-files file)))) + + ;; <file>\nAdd file to the chat? + ((string-match "Add file to the chat?" line) + (add-to-list 'aidermacs--tracked-files last-line)) + + ;; <file> is already in the chat as an editable file + ((string-match "\\(\\./\\)?\\(.+\\) is already in the chat as an editable file" line) + (when-let ((file (match-string 2 line))) + (add-to-list 'aidermacs--tracked-files file)))) + (setq last-line line)) + + ;; Verify all tracked files exist + (aidermacs--verify-tracked-files)))) + +(defun aidermacs-reset-tracked-files () + "Reset the list of tracked files and force a refresh." + (interactive) + (setq aidermacs--tracked-files nil) + (aidermacs--get-files-in-session (lambda (files) + (message "Refreshed file list: %s" files)))) + (defun aidermacs--store-output (output) "Store output string in the history with timestamp. OUTPUT is the string to store. @@ -93,10 +173,12 @@ If there's a callback function, call it with the output." (when (> (length aidermacs--output-history) aidermacs-output-limit) (setq aidermacs--output-history (seq-take aidermacs--output-history aidermacs-output-limit))) + ;; Parse output for file mentions + (aidermacs--parse-output-for-files output) (unless aidermacs--in-callback (when aidermacs--current-callback (let ((aidermacs--in-callback t)) - (funcall aidermacs--current-callback output) + (funcall aidermacs--current-callback) (setq aidermacs--current-callback nil))))) ;; Backend dispatcher functions @@ -111,25 +193,30 @@ BUFFER-NAME is the name for the aidermacs buffer." (t (aidermacs-run-comint program args buffer-name)))) -(defun aidermacs--send-command-backend (buffer command) - "Send command to buffer using the appropriate backend. -BUFFER is the target buffer. COMMAND is the text to send." - (setq aidermacs--last-command command - aidermacs--current-output nil) - (if (eq aidermacs-backend 'vterm) - (aidermacs--send-command-vterm buffer command) - (aidermacs--send-command-comint buffer command))) - -(defun aidermacs--send-command-redirect-backend (buffer command &optional callback) +(defun aidermacs--is-aidermacs-buffer-p (&optional buffer) + "Check if BUFFER is any type of aidermacs buffer. +If BUFFER is nil, check the current buffer. +Returns non-nil if the buffer name matches the aidermacs buffer pattern +and is using either comint or vterm mode." + (let ((buf (or buffer (current-buffer)))) + (with-current-buffer buf + (and (string-match-p "^\\*aidermacs:" (buffer-name buf)) + (or (derived-mode-p 'comint-mode) + (and (fboundp 'vterm-mode) + (derived-mode-p 'vterm-mode))))))) + +(defun aidermacs--send-command-backend (buffer command &optional redirect callback) "Send command to buffer using the appropriate backend. BUFFER is the target buffer. COMMAND is the text to send. -CALLBACK if provided will be called with the command output when available." +If REDIRECT is non-nil it redirects the output (hidden) for comint backend. +If CALLBACK is non-nil it will be called after the command finishes." (setq aidermacs--last-command command - aidermacs--current-output nil aidermacs--current-callback callback) (if (eq aidermacs-backend 'vterm) (aidermacs--send-command-vterm buffer command) - (aidermacs--send-command-redirect-comint buffer command))) + (if redirect + (aidermacs--send-command-redirect-comint buffer command) + (aidermacs--send-command-comint buffer command)))) (provide 'aidermacs-backends) diff --git a/aidermacs-models.el b/aidermacs-models.el index 0078a57a43..0f981d4cba 100644 --- a/aidermacs-models.el +++ b/aidermacs-models.el @@ -26,8 +26,7 @@ (require 'json) (require 'url) -(declare-function aidermacs--send-command "aidermacs" (command &optional switch-to-buffer)) -(declare-function aidermacs--send-command-redirect "aidermacs" (command callback)) +(declare-function aidermacs--send-command "aidermacs" (command &optional no-switch-to-buffer use-existing redirect callback)) (declare-function aidermacs-buffer-name "aidermacs" ()) (declare-function aidermacs-exit "aidermacs" ()) @@ -126,20 +125,20 @@ This is a private function used internally." (condition-case nil (let ((model (completing-read "Select AI model: " aidermacs--cached-models nil t))) (when model - (aidermacs--send-command (format "/model %s" model) t))) + (aidermacs--send-command (format "/model %s" model)))) (quit (message "Model selection cancelled")))) (defun aidermacs--get-available-models () "Get list of models supported by aider using the /models command. This fetches models from various API providers and caches them." - (aidermacs--send-command-redirect - "/models /" - (lambda (output) + (aidermacs--send-command + "/models /" nil nil t + (lambda () (let* ((supported-models (seq-filter (lambda (line) (string-prefix-p "- " line)) - (split-string output "\n" t))) + (split-string aidermacs--current-output "\n" t))) (models nil)) (setq supported-models (mapcar (lambda (line) @@ -176,8 +175,8 @@ This is useful when available models have changed." (interactive) (when (and aidermacs--cached-models (equal aidermacs--cached-models aidermacs-popular-models) - (fboundp 'aidermacs-buffer-name) - (get-buffer (aidermacs-buffer-name))) + (fboundp 'aidermacs-get-buffer-name) + (get-buffer (aidermacs-get-buffer-name))) (setq aidermacs--cached-models nil)) (if aidermacs--cached-models diff --git a/aidermacs.el b/aidermacs.el index e5d39dee78..55dbb6991a 100644 --- a/aidermacs.el +++ b/aidermacs.el @@ -28,6 +28,7 @@ (require 'comint) (require 'dired) +(require 'ediff) (require 'project) (require 'transient) (require 'vc-git) @@ -38,6 +39,10 @@ (require 'aidermacs-backends) (require 'aidermacs-models) +(defvar-local aidermacs--current-mode nil + "Buffer-local variable to track the current aidermacs mode. +Possible values: `code', `ask', `architect', `help'.") + (declare-function magit-show-commit "magit-diff" (rev &optional noselect module)) (defgroup aidermacs nil @@ -94,9 +99,16 @@ This is the file name without path." :type 'string :group 'aidermacs) -(defvar aidermacs-read-string-history nil +(defvar-local aidermacs-read-string-history nil "History list for aidermacs read string inputs.") +(defvar-local aidermacs--pre-edit-files nil + "Alist of (filename . temp-filename) pairs storing file state before Aider edits.") + +(defvar-local aidermacs--pre-edit-buffers nil + "Alist of (filename . temp-buffer) pairs for active ediff sessions.") + + ;;;###autoload (defun aidermacs-plain-read-string (prompt &optional initial-input) "Read a string from the user with PROMPT and optional INITIAL-INPUT. @@ -274,6 +286,9 @@ This function sets up the appropriate arguments and launches the process." (if (get-buffer buffer-name) (aidermacs-switch-to-buffer buffer-name) (aidermacs-run-backend aidermacs-program final-args buffer-name) + (with-current-buffer (get-buffer buffer-name) + ;; Set initial mode based on startup configuration + (setq-local aidermacs--current-mode (if aidermacs-use-architect-mode 'architect 'code))) (aidermacs-switch-to-buffer buffer-name)))) ;;;###autoload @@ -285,31 +300,186 @@ This is useful for working in monorepos where you want to limit aider's scope." (default-directory (file-truename default-directory))) (aidermacs-run))) -(defun aidermacs--send-command (command &optional switch-to-buffer use-existing) +(defun aidermacs--capture-file-state (filename) + "Store the current state of FILENAME in a temporary file." + (when (and filename (file-exists-p filename)) + (let ((temp-file (make-temp-file + (concat "aidermacs-" + (file-name-nondirectory filename) "-")))) + (condition-case err + (progn + (copy-file filename temp-file t) + (cons filename temp-file)) + (error + (message "Error capturing file state for %s: %s" + filename (error-message-string err)) + (when (file-exists-p temp-file) + (delete-file temp-file)) + nil))))) + +(defun aidermacs--cleanup-all-temp-files () + "Clean up all temporary files created for ediff sessions. +This is called when all ediff sessions are complete." + (interactive) + (with-current-buffer (get-buffer (aidermacs-get-buffer-name)) + (dolist (file-pair aidermacs--pre-edit-files) + (let ((temp-file (cdr file-pair))) + (when (and temp-file (stringp temp-file) (file-exists-p temp-file)) + (message "Deleting %s" temp-file) + (delete-file temp-file)))) + ;; Clear the list after cleanup + (setq aidermacs--pre-edit-files nil) + (setq aidermacs--pre-edit-buffers nil))) + +(defun aidermacs--prepare-for-code-edit () + "Prepare for Aider code edits by capturing current file states." + (let ((files aidermacs--tracked-files)) + (when files + (setq aidermacs--pre-edit-files + (mapcar (lambda (file) + (let* ((clean-file (replace-regexp-in-string " (read-only)$" "" file)) + (full-path (expand-file-name clean-file (aidermacs-project-root)))) + ;; Check if a pre-edit file already exists for this file + (unless (assoc full-path aidermacs--pre-edit-files) + (aidermacs--capture-file-state full-path)))) + files)) + ;; Remove nil entries from the list (where capture failed or was skipped) + (setq aidermacs--pre-edit-files (delq nil aidermacs--pre-edit-files)) + (message "Updated Temp Files: %s" aidermacs--pre-edit-files)))) + +(defun aidermacs--ediff-quit-handler () + "Handle ediff session cleanup and process next files in queue. +This function is called when an ediff session is quit and performs two tasks: +1. Cleans up resources (buffers and temp files) for the current ediff session +2. Processes the next file in the ediff queue if any remain" + ;; Clean up any pre-edit buffers + (dolist (buf (buffer-list)) + (when (string-match "\\*aidermacs-pre-edit:\\(.*\\)\\*" (buffer-name buf)) + (when (buffer-live-p buf) + (kill-buffer buf)))) + (aidermacs--process-next-ediff-file)) + +(defun aidermacs--setup-ediff-cleanup-hooks () + "Set up hooks to ensure proper cleanup of temporary buffers after ediff." + (add-hook 'ediff-quit-hook #'aidermacs--ediff-quit-handler)) + +(defun aidermacs--detect-edited-files () + "Parse current output to find files edited by Aider." + (let ((edited-files nil) + (output aidermacs--current-output)) + (with-temp-buffer + (insert output) + (goto-char (point-min)) + (while (re-search-forward "Applied edit to \\(.+\\)" nil t) + (push (match-string 1) edited-files))) + (nreverse edited-files))) + +(defun aidermacs--create-pre-edit-buffer (filename temp-file) + "Create a buffer for FILENAME using content from TEMP-FILE for ediff." + (condition-case err + (let ((buffer (generate-new-buffer (format "*aidermacs-pre-edit:%s*" + (file-name-nondirectory filename))))) + (with-current-buffer buffer + (condition-case err2 + (progn + (insert-file-contents temp-file) + (set-buffer-modified-p nil) + ;; Use same major mode as the original file would have + (let ((buffer-file-name filename)) + (set-auto-mode)) + ;; Ensure syntax highlighting is applied + (font-lock-ensure) + ;; Make sure buffer is read-only + (setq buffer-read-only t)) + (error + (message "Error setting up pre-edit buffer: %s" (error-message-string err2)) + (kill-buffer buffer) + nil))) + buffer) + (error + (message "Failed to create pre-edit buffer: %s" (error-message-string err)) + nil))) + +(defvar-local aidermacs--ediff-queue nil + "Buffer-local queue of files waiting to be processed by ediff.") + +(defun aidermacs--process-next-ediff-file () + "Process the next file in the ediff queue for the current buffer." + (with-current-buffer (get-buffer (aidermacs-get-buffer-name)) + (if aidermacs--ediff-queue + (let ((file (pop aidermacs--ediff-queue))) + (aidermacs--show-ediff-for-file file)) + (aidermacs--cleanup-all-temp-files)))) + +(defun aidermacs--show-ediff-for-file (file) + "Show ediff for FILE." + (let* ((full-path (expand-file-name file (aidermacs-project-root))) + (pre-edit-pair (assoc full-path aidermacs--pre-edit-files)) + (temp-file (and pre-edit-pair (cdr pre-edit-pair)))) + (if (and temp-file + (stringp temp-file) + (file-exists-p temp-file)) + (progn + ;; Create buffer from temp file only when needed + (let* ((pre-edit-buffer (aidermacs--create-pre-edit-buffer full-path temp-file)) + (current-buffer (or (get-file-buffer full-path) + (find-file-noselect full-path)))) + (with-current-buffer current-buffer + (revert-buffer t t t)) + ;; Store buffer for cleanup + (unless (boundp 'aidermacs--pre-edit-buffers) + (setq-local aidermacs--pre-edit-buffers nil)) + (push (cons full-path pre-edit-buffer) aidermacs--pre-edit-buffers) + ;; Debug info + (message "Comparing %s with %s" temp-file full-path) + ;; Give Emacs a moment to finish buffer setup + ;; Start ediff session + (ediff-buffers pre-edit-buffer current-buffer))) + ;; If no pre-edit temp file found, continue with next file + (message "No pre-edit file found for %s in %s out of %s, skipping" file pre-edit-pair aidermacs--pre-edit-files) + (aidermacs--process-next-ediff-file)))) + +(defun aidermacs--show-ediff-for-edited-files (edited-files) + "Show ediff for each file in EDITED-FILES." + (when edited-files + ;; Display a message about which files were changed + (message "Modified %d file(s): %s" + (length edited-files) + (mapconcat #'identity edited-files ", ")) + + ;; Set up the queue in the current buffer + (setq-local aidermacs--ediff-queue edited-files) + + ;; Process the first file + (aidermacs--process-next-ediff-file))) + +;; Function removed as its functionality is now integrated directly in the output filters + + +(defun aidermacs--send-command (command &optional no-switch-to-buffer use-existing redirect callback) "Send command to the corresponding aidermacs process. COMMAND is the text to send. -If SWITCH-TO-BUFFER is non-nil, switch to the aidermacs buffer. -If USE-EXISTING is non-nil, use an existing buffer instead of creating new." +If NO-SWITCH-TO-BUFFER is non-nil, don't switch to the aidermacs buffer. +If USE-EXISTING is non-nil, use an existing buffer instead of creating new. +If REDIRECT is non-nil it redirects the output (hidden) for comint backend. +If CALLBACK is non-nil it will be called after the command finishes." (let* ((buffer-name (aidermacs-get-buffer-name use-existing)) (buffer (or (get-buffer buffer-name) (progn (aidermacs-run) (get-buffer buffer-name)))) (processed-command (aidermacs--process-message-if-multi-line command))) - (aidermacs--send-command-backend buffer processed-command) - (when (and switch-to-buffer (not (string= (buffer-name) buffer-name))) - (aidermacs-switch-to-buffer buffer-name)))) -(defun aidermacs--send-command-redirect (command callback &optional use-existing) - "Send command to the corresponding aidermacs process in the background. -COMMAND is the text to send. -CALLBACK will be called with the command output when available. -If USE-EXISTING is non-nil, use an existing buffer instead of creating new." - (let* ((buffer-name (aidermacs-get-buffer-name use-existing)) - (buffer (or (get-buffer buffer-name) - (progn (aidermacs-run) - (get-buffer buffer-name)))) - (processed-command (aidermacs--process-message-if-multi-line command))) - (aidermacs--send-command-redirect-backend buffer processed-command callback))) + ;; Reset current output before sending new command + (with-current-buffer buffer + (setq-local aidermacs--current-output nil) + (setq-local aidermacs--last-command processed-command) + ;; Always prepare for potential edits + (aidermacs--cleanup-all-temp-files) + (aidermacs--prepare-for-code-edit)) + + (aidermacs--send-command-backend buffer processed-command redirect callback) + (when (and (not no-switch-to-buffer) (not (string= (buffer-name) buffer-name))) + (aidermacs-switch-to-buffer buffer-name)))) ;;;###autoload (defun aidermacs-switch-to-buffer (&optional buffer-name) @@ -333,19 +503,21 @@ If the current buffer is already the aidermacs buffer, do nothing." (defun aidermacs-clear-chat-history () "Send the command \"/clear\" to the aidermacs buffer." (interactive) + (setq aidermacs--tracked-files nil) (aidermacs--send-command "/clear")) ;;;###autoload (defun aidermacs-reset () "Send the command \"/reset\" to the aidermacs buffer." (interactive) + (setq aidermacs--tracked-files nil) (aidermacs--send-command "/reset")) ;;;###autoload (defun aidermacs-exit () "Send the command \"/exit\" to the aidermacs buffer." (interactive) - (aidermacs--send-command "/exit")) + (aidermacs--send-command "/exit" t)) (defun aidermacs--process-message-if-multi-line (str) @@ -388,14 +560,14 @@ The full command will be \"COMMAND-PREFIX <current buffer file full path>\"." (interactive) (let ((command (aidermacs-read-string "Enter general aider command: "))) ;; Use the shared helper function to send the command - (aidermacs--send-command command t))) + (aidermacs--send-command command))) ;;;###autoload (defun aidermacs-direct-change () "Prompt the user for an input and send it to aidemracs prefixed with \"/code \"." (interactive) (when-let ((command (aidermacs--form-prompt "/code" nil t "empty to change to code mode"))) - (aidermacs--send-command command t))) + (aidermacs--send-command command))) (defun aidermacs--parse-ls-output (output) "Parse the /ls command output to extract files in chat. @@ -435,32 +607,37 @@ Returns a deduplicated list of such file names." (forward-line 1))) ;; Remove duplicates and return - (delete-dups (nreverse files)))))) + (setq aidermacs--tracked-files (delete-dups (nreverse files))) + aidermacs--tracked-files)))) + +(defun aidermacs--get-files-in-session (callback) + "Get list of files in current session and call CALLBACK with the result." + (aidermacs--send-command + "/ls" nil nil t + (lambda () + (let ((files (aidermacs--parse-ls-output aidermacs--current-output))) + (funcall callback files))))) ;;;###autoload (defun aidermacs-list-added-files () "List all files currently added to the chat session. Sends the \"/ls\" command and returns the list of files via callback." (interactive) - (aidermacs--send-command-redirect - "/ls" - (lambda (output) - (let ((files (aidermacs--parse-ls-output output))) - (message "%s" (prin1-to-string files)) - files)))) + (aidermacs--get-files-in-session + (lambda (files) + (message "%s" (prin1-to-string files)) + files))) ;;;###autoload (defun aidermacs-drop-file () "Drop a file from the chat session by selecting from currently added files." (interactive) - (aidermacs--send-command-redirect - "/ls" - (lambda (output) - (if-let* ((files (aidermacs--parse-ls-output output)) - (file (completing-read "Select file to drop: " files nil t)) + (aidermacs--get-files-in-session + (lambda (files) + (if-let* ((file (completing-read "Select file to drop: " files nil t)) (clean-file (replace-regexp-in-string " (read-only)$" "" file))) - (aidermacs--send-command (format "/drop ./%s" clean-file))) - (message "No files available to drop")))) + (aidermacs--send-command (format "/drop ./%s" clean-file)) + (message "No files available to drop"))))) ;;;###autoload @@ -512,41 +689,41 @@ If called from the aidermacs buffer, use general question instead." (call-interactively #'aidermacs-general-question) (when-let ((command (aidermacs--form-prompt "/ask" "Question"))) (aidermacs-add-current-file) - (aidermacs--send-command command t)))) + (aidermacs--send-command command)))) ;;;###autoload (defun aidermacs-general-question () "Prompt the user for a general question without code context." (interactive) (when-let ((command (aidermacs--form-prompt "/ask" nil t "empty to change to ask mode"))) - (aidermacs--send-command command t))) + (aidermacs--send-command command))) ;;;###autoload (defun aidermacs-help () "Prompt the user for an input prefixed with \"/help \"." (interactive) (when-let ((command (aidermacs--form-prompt "/help" nil t "empty for general /help"))) - (aidermacs--send-command command t))) + (aidermacs--send-command command))) ;;;###autoload (defun aidermacs-general-architect () "Prompt the user for an input prefixed with \"/architect \"." (interactive) (when-let ((command (aidermacs--form-prompt "/architect" nil t "empty to change to architect mode"))) - (aidermacs--send-command command t))) + (aidermacs--send-command command))) ;;;###autoload (defun aidermacs-debug-exception () "Prompt the user for an input and send it to aidemracs prefixed with \"/debug \"." (interactive) (when-let ((command (aidermacs--form-prompt "/ask" "Debug exception"))) - (aidermacs--send-command command t))) + (aidermacs--send-command command))) ;;;###autoload (defun aidermacs-accept-change () "Send the command \"go ahead\" to the aidemracs." (interactive) - (aidermacs--send-command "/code go ahead" t)) + (aidermacs--send-command "/code go ahead")) ;;;###autoload @@ -594,7 +771,7 @@ If point is in a function, inspect that function." (call-interactively #'aidermacs-general-architect) (when-let ((command (aidermacs--form-prompt "/architect" "Architect"))) (aidermacs-add-current-file) - (aidermacs--send-command command t)))) + (aidermacs--send-command command)))) ;;;###autoload (defun aidermacs-question-this-symbol () @@ -609,14 +786,14 @@ If point is in a function, inspect that function." (if symbol (progn (aidermacs-add-current-file) - (aidermacs--send-command prompt t)) + (aidermacs--send-command prompt)) (error "No symbol under point!")))) (defun aidermacs-send-command-with-prefix (prefix command) "Send COMMAND to the aidermacs buffer with PREFIX. PREFIX is the text to prepend. COMMAND is the text to send." (aidermacs-add-current-file) - (aidermacs--send-command (concat prefix command) t)) + (aidermacs--send-command (concat prefix command))) (defun aidermacs--add-files-helper (files read-only &optional message) "Helper function to add files with read-only flag. @@ -626,8 +803,9 @@ as read-only. Optional MESSAGE can override the default success message." (files (delq nil files))) (if files (progn - (aidermacs--send-command (format "%s %s" cmd - (mapconcat #'identity files " ")) t) + (aidermacs--send-command + (format "%s %s" cmd + (mapconcat #'identity files " "))) (message (or message (format "Added %d files as %s" (length files) @@ -741,7 +919,7 @@ Otherwise: function-name)) (command (aidermacs--form-prompt "/architect" initial-input))) (aidermacs-add-current-file) - (aidermacs--send-command command t)) + (aidermacs--send-command command)) (message "Current function '%s' does not appear to be a test function." function-name)) (message "Please place cursor inside a test function to implement."))) ;; Non-test file case @@ -755,7 +933,7 @@ Otherwise: (file-name-nondirectory buffer-file-name) common-instructions))) (command (aidermacs--form-prompt "/architect" initial-input))) (aidermacs-add-current-file) - (aidermacs--send-command command t))))))) + (aidermacs--send-command command))))))) ;;;###autoload (defun aidermacs-fix-failing-test-under-cursor () @@ -767,13 +945,14 @@ This function assumes the cursor is on or inside a test function." test-function-name)) (command (aidermacs--form-prompt "/architect" initial-input))) (aidermacs-add-current-file) - (aidermacs--send-command command t)) + (aidermacs--send-command command)) (message "No test function found at cursor position."))) (defun aidermacs-create-session-scratchpad () "Create a new temporary file for adding content to the aider session. -The file will be created in the system's temp directory with a timestamped name. -Use this to add functions, code snippets, or other content to the session." +The file will be created in the system's temp directory +with a timestamped name. Use this to add functions, code +snippets, or other content to the session." (interactive) (let* ((temp-dir (file-name-as-directory (temporary-file-directory))) (filename (expand-file-name @@ -785,14 +964,15 @@ Use this to add functions, code snippets, or other content to the session." (insert ";; Add your code snippets, functions, or other content here\n") (insert ";; Just edit and save - changes will be available to aider\n\n") (write-file filename)) - (aidermacs--send-command (format "/read %s" filename) t) + (aidermacs--send-command (format "/read %s" filename)) (find-file-other-window filename) (message "Created and added scratchpad to session: %s" filename))) ;;;###autoload (defun aidermacs-add-file-to-session () "Interactively add a file to an existing aidermacs session using /read. -This allows you to add the file's content to a specific session." +This allows you to add the file's content to a +specific session." (interactive) (let* ((initial (when buffer-file-name (file-name-nondirectory buffer-file-name))) @@ -801,12 +981,12 @@ This allows you to add the file's content to a specific session." nil nil t initial)))) (if (not (file-exists-p file)) (message "File does not exist: %s" file) - (aidermacs--send-command (format "/read %s" file) t t)))) + (aidermacs--send-command (format "/read %s" file) nil t)))) (defun aidermacs--is-comment-line (line) "Check if LINE is a comment line based on current buffer's comment syntax. -Returns non-nil if LINE starts with one or more comment characters, -ignoring leading whitespace." +Returns non-nil if LINE starts with one or more +comment characters, ignoring leading whitespace." (when comment-start (let ((comment-str (string-trim-right comment-start))) (string-match-p (concat "^[ \t]*" @@ -833,7 +1013,7 @@ Otherwise implement TODOs for the entire current file." (format " on this comment: `%s`." current-line)) " Keep existing code structure")))) (aidermacs-add-current-file) - (aidermacs--send-command command t))))) + (aidermacs--send-command command))))) ;;;###autoload (defun aidermacs-send-line-or-region () @@ -842,44 +1022,50 @@ If region is active, send the selected region. Otherwise, send the line under cursor." (interactive) (let ((text (if (use-region-p) - (buffer-substring-no-properties (region-beginning) (region-end)) + (buffer-substring-no-properties + (region-beginning) (region-end)) (string-trim (thing-at-point 'line t))))) (when text - (aidermacs--send-command text t)))) + (aidermacs--send-command text)))) ;;;###autoload (defun aidermacs-send-region-by-line () "Send the text of the current selected region, split into lines." (interactive) (if (use-region-p) - (let* ((text (buffer-substring-no-properties (region-beginning) (region-end))) + (let* ((text (buffer-substring-no-properties + (region-beginning) (region-end))) (lines (split-string text "\n" t))) (mapc (lambda (line) (let ((trimmed (string-trim line))) (when (not (string-empty-p trimmed)) - (aidermacs--send-command trimmed t)))) + (aidermacs--send-command trimmed)))) lines)) (message "No region selected."))) ;;;###autoload (defun aidermacs-send-block-or-region () "Send the current active region text or current paragraph content. -When sending paragraph content, preserve cursor position." +When sending paragraph content, preserve cursor +position." (interactive) (let ((text (if (use-region-p) - (buffer-substring-no-properties (region-beginning) (region-end)) + (buffer-substring-no-properties + (region-beginning) (region-end)) (save-excursion (mark-paragraph) (prog1 - (buffer-substring-no-properties (region-beginning) (region-end)) + (buffer-substring-no-properties + (region-beginning) (region-end)) (deactivate-mark)))))) (when text - (aidermacs--send-command text t)))) + (aidermacs--send-command text)))) ;;;###autoload (defun aidermacs-open-prompt-file () "Open aidermacs prompt file under git repo root. -If file doesn't exist, create it with command binding help and sample prompt." +If file doesn't exist, create it with command binding help and +sample prompt." (interactive) (let* ((git-root (vc-git-root default-directory)) (prompt-file (when git-root @@ -934,9 +1120,8 @@ These are exact filename matches (including the dot prefix)." (defun aidermacs--maybe-enable-minor-mode () "Determines whether to enable `aidermacs-minor-mode'." (when (and buffer-file-name - (when buffer-file-name - (let ((base-name (file-name-nondirectory buffer-file-name))) - (member base-name aidermacs-auto-mode-files)))) + (member (file-name-nondirectory buffer-file-name) + aidermacs-auto-mode-files)) (aidermacs-minor-mode 1))) ;;;###autoload @@ -957,36 +1142,49 @@ prompt files and other Aider-related files: ;;;###autoload (defun aidermacs-switch-to-code-mode () "Switch aider to code mode. -In code mode, aider will make changes to your code to satisfy your requests." +In code mode, aider will make changes to your code to satisfy +your requests." (interactive) - (aidermacs--send-command "/chat-mode code" t) + (aidermacs--send-command "/chat-mode code") + (with-current-buffer (get-buffer (aidermacs-get-buffer-name)) + (setq-local aidermacs--current-mode 'code)) (message "Switched to code mode <default> - aider will make changes to your code")) ;;;###autoload (defun aidermacs-switch-to-ask-mode () "Switch aider to ask mode. -In ask mode, aider will answer questions about your code, but never edit it." +In ask mode, aider will answer questions about your code, but +never edit it." (interactive) - (aidermacs--send-command "/chat-mode ask" t) + (aidermacs--send-command "/chat-mode ask") + (with-current-buffer (get-buffer (aidermacs-get-buffer-name)) + (setq-local aidermacs--current-mode 'ask)) (message "Switched to ask mode - you can chat freely, aider will not edit your code")) ;;;###autoload (defun aidermacs-switch-to-architect-mode () "Switch aider to architect mode. -In architect mode, aider will first propose a solution, then ask if you want -it to turn that proposal into edits to your files." +In architect mode, aider will first propose a solution, then ask +if you want it to turn that proposal into edits to your files." (interactive) - (aidermacs--send-command "/chat-mode architect" t) + (aidermacs--send-command "/chat-mode architect") + (with-current-buffer (get-buffer (aidermacs-get-buffer-name)) + (setq-local aidermacs--current-mode 'architect)) (message "Switched to architect mode - aider will propose solutions before making changes")) ;;;###autoload (defun aidermacs-switch-to-help-mode () "Switch aider to help mode. -In help mode, aider will answer questions about using aider, configuring, -troubleshooting, etc." +In help mode, aider will answer questions about using aider, +configuring, troubleshooting, etc." (interactive) - (aidermacs--send-command "/chat-mode help" t) + (aidermacs--send-command "/chat-mode help") + (with-current-buffer (get-buffer (aidermacs-get-buffer-name)) + (setq-local aidermacs--current-mode 'help)) (message "Switched to help mode - aider will answer questions about using aider")) +;; Initialize the cleanup mechanisms +(aidermacs--setup-ediff-cleanup-hooks) + (provide 'aidermacs) ;;; aidermacs.el ends here