branch: elpa/aidermacs
commit 07f3d319b13333745e56f613fa85593d3eb850c0
Author: Mingde (Matthew) Zeng <[email protected]>
Commit: Mingde (Matthew) Zeng <[email protected]>
Preliminary implementation of ediff capture diffs
This will fix #43
---
aidermacs-backends.el | 1 -
aidermacs.el | 225 +++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 204 insertions(+), 22 deletions(-)
diff --git a/aidermacs-backends.el b/aidermacs-backends.el
index 86a99dffc1..4d564e82e5 100644
--- a/aidermacs-backends.el
+++ b/aidermacs-backends.el
@@ -188,7 +188,6 @@ BUFFER is the target buffer. COMMAND is the text to send.
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)
diff --git a/aidermacs.el b/aidermacs.el
index 51d739924c..c3d20e335e 100644
--- a/aidermacs.el
+++ b/aidermacs.el
@@ -28,6 +28,7 @@
(require 'comint)
(require 'dired)
+(require 'ediff)
(require 'project)
(require 'transient)
(require 'vc-git)
@@ -101,6 +102,13 @@ This is the file name without path."
(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.
@@ -292,27 +300,195 @@ 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--get-files-in-session (callback)
- "Get list of files in current session and call CALLBACK with the result."
- (aidermacs--send-command-redirect
- "/ls"
+(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--prepare-for-code-edit ()
+ "Prepare for Aider code edits by capturing current file states."
+ (let ((files (when (boundp 'aidermacs--tracked-files)
+ aidermacs--tracked-files)))
+ (when files
+ ;; Initialize the tracking variables if they don't exist
+ (unless (boundp 'aidermacs--pre-edit-files)
+ (setq-local aidermacs--pre-edit-files nil))
+ (unless (boundp 'aidermacs--pre-edit-buffers)
+ (setq-local aidermacs--pre-edit-buffers nil))
+
+ (setq aidermacs--pre-edit-files
+ (mapcar (lambda (file)
+ (let ((clean-file (replace-regexp-in-string "
(read-only)$" "" file)))
+ (aidermacs--capture-file-state
+ (expand-file-name clean-file
(aidermacs-project-root)))))
+ files)))))
+
+(defun aidermacs-cleanup-all-ediff-sessions ()
+ "Quit all active aidermacs ediff sessions and clean up all resources.
+This function ensures a complete cleanup of all ediff-related resources."
+ (interactive)
+ ;; Find and quit all active aidermacs ediff sessions
+ (dolist (buf (buffer-list))
+ (when (string-match-p "\\*ediff-aidermacs:" (buffer-name buf))
+ (with-current-buffer buf
+ ;; This will trigger the normal ediff cleanup process
+ (when (fboundp 'ediff-quit)
+ (ediff-quit)))))
+
+ ;; Force cleanup of any remaining resources
+ (aidermacs--cleanup-stale-pre-edit-buffers))
+
+(defun aidermacs--cleanup-stale-pre-edit-buffers ()
+ "Clean up any stale pre-edit buffers that weren't properly disposed of.
+This function should only handle truly orphaned resources that weren't
+cleaned up by the normal ediff quit process."
+ ;; Clean up any orphaned pre-edit buffers
+ (when (boundp 'aidermacs--pre-edit-buffers)
+ (dolist (pair aidermacs--pre-edit-buffers)
+ (let ((file (car pair))
+ (buffer (cdr pair)))
+ (when (and buffer (buffer-live-p buffer))
+ (message "Cleaning up orphaned pre-edit buffer for %s" file)
+ (kill-buffer buffer)))))
+
+ ;; Clean up any orphaned temp files
+ (when (boundp 'aidermacs--pre-edit-files)
+ (dolist (pair aidermacs--pre-edit-files)
+ (let ((file (car pair))
+ (temp-file (cdr pair)))
+ (when (and temp-file (stringp temp-file) (file-exists-p temp-file))
+ (message "Cleaning up orphaned temp file for %s" file)
+ (delete-file temp-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--cleanup-stale-pre-edit-buffers)
+ ;; Also add to kill-emacs-hook to ensure cleanup on Emacs exit
+ (add-hook 'kill-emacs-hook #'aidermacs-cleanup-all-ediff-sessions))
+
+
+(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))
+ (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)))
+
+(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 ", "))
+
+ (let ((file (car edited-files))
+ (remaining (cdr edited-files)))
+
+ ;; Find the pre-edit temp file for this file
+ (let* ((full-path (expand-file-name file (aidermacs-project-root)))
+ (pre-edit-pair (when (boundp 'aidermacs--pre-edit-files)
+ (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)))
+ ;; 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)
+
+ ;; Start ediff session
+ (ediff-buffers
+ pre-edit-buffer
+ (or (get-file-buffer full-path)
+ (find-file-noselect full-path))
+ ;; Setup callback for when this ediff session ends
+ (lambda ()
+ ;; Clean up resources immediately and directly
+ ;; 1. Kill the pre-edit buffer immediately
+ (when (and pre-edit-buffer (buffer-live-p pre-edit-buffer))
+ (kill-buffer pre-edit-buffer))
+ ;; 2. Remove this file from the buffer tracking list
+ (when (boundp 'aidermacs--pre-edit-buffers)
+ (setq aidermacs--pre-edit-buffers
+ (assoc-delete-all full-path
aidermacs--pre-edit-buffers)))
+ ;; 3. Delete the temp file immediately
+ (when (and temp-file (stringp temp-file) (file-exists-p
temp-file))
+ (delete-file temp-file))
+ ;; 4. Remove this file from the temp file tracking list
+ (when (boundp 'aidermacs--pre-edit-files)
+ (setq aidermacs--pre-edit-files
+ (assoc-delete-all full-path
aidermacs--pre-edit-files)))
+ ;; Process remaining files
+ (when remaining
+ (aidermacs--show-ediff-for-edited-files remaining)))))
+
+ ;; Customize ediff session
+ (when (boundp 'ediff-buffer)
+ (with-current-buffer ediff-buffer
+ (rename-buffer (format "*ediff-aidermacs: %s*" file)))))
+
+ ;; If no pre-edit temp file found, continue with remaining files
+ (when remaining
+ (aidermacs--show-ediff-for-edited-files remaining)))))))
+
+(defun aidermacs--send-command-with-edit-tracking (buffer command)
+ "Send COMMAND to aidermacs with edit tracking to the BUFFER."
+ ;; Prepare for edits
+ (aidermacs--prepare-for-code-edit)
+ ;; Send command and track output
+ (aidermacs--send-command-backend
+ buffer command nil
(lambda ()
- (let ((files (aidermacs--parse-ls-output aidermacs--current-output)))
- (funcall callback files)))))
-
-(defun aidermacs--send-command (command &optional switch-to-buffer
use-existing)
- "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."
- (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))))
+ ;; Check if this was a code edit command and show ediff if needed
+ (when (aidermacs--is-edit-command-p command buffer)
+ (let ((edited-files (aidermacs--detect-edited-files)))
+ (when edited-files
+ (aidermacs--show-ediff-for-edited-files edited-files)))))))
(defun aidermacs--is-edit-command-p (command buffer)
"Determine if COMMAND might result in code edits based on current mode in
BUFFER.
@@ -340,8 +516,12 @@ If CALLBACK is non-nil it will be called after the command
finishes."
(get-buffer buffer-name))))
(processed-command (aidermacs--process-message-if-multi-line
command)))
+ ;; Reset current output before sending new command
+ (with-current-buffer buffer
+ (setq-local aidermacs--current-output nil))
+
(if (aidermacs--is-edit-command-p command buffer)
- (aidermacs--send-command-with-edit-tracking processed-command
no-switch-to-buffer use-existing)
+ (aidermacs--send-command-with-edit-tracking buffer processed-command)
(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))))
@@ -1039,5 +1219,8 @@ troubleshooting, etc."
(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