branch: elpa/aidermacs commit e2c81ef3196249a7e68fde48d4e780b565765324 Author: Mingde (Matthew) Zeng <matthew...@posteo.net> Commit: Mingde (Matthew) Zeng <matthew...@posteo.net>
Fix vterm advice #15 Signed-off-by: Mingde (Matthew) Zeng <matthew...@posteo.net> --- README.org | 2 +- aidermacs-backend-comint.el | 3 -- aidermacs-backend-vterm.el | 97 ++++++++++++++++++++++++--------------------- aidermacs-backends.el | 24 +++++++++-- aidermacs.el | 60 +++++++++++++++------------- 5 files changed, 104 insertions(+), 82 deletions(-) diff --git a/README.org b/README.org index e6bf8f31a8..6fa5ddfd1f 100644 --- a/README.org +++ b/README.org @@ -48,7 +48,7 @@ With =aidermacs=, you get: 4. 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 complete chat history with =M-x aidermacs-show-output-history= + - View output history with =M-x aidermacs-show-output-history= - and more 5. Community-Driven Development diff --git a/aidermacs-backend-comint.el b/aidermacs-backend-comint.el index d170ddcabc..f9b5e34a60 100644 --- a/aidermacs-backend-comint.el +++ b/aidermacs-backend-comint.el @@ -257,9 +257,6 @@ This allows for multi-line input without sending the command." (with-current-buffer buffer (let ((process (get-buffer-process buffer)) (inhibit-read-only t)) - ;; Store command before sending - (setq aidermacs--last-command command - aidermacs--current-output nil) (goto-char (process-mark process)) (aidermacs-reset-font-lock-state) (insert (propertize command diff --git a/aidermacs-backend-vterm.el b/aidermacs-backend-vterm.el index d4f2c56039..ce96cda889 100644 --- a/aidermacs-backend-vterm.el +++ b/aidermacs-backend-vterm.el @@ -11,54 +11,62 @@ (defvar vterm-shell) (defvar vterm-buffer-name) -(defun aidermacs--vterm-output-advice (orig-fun &rest args) - "Capture output before and after executing `vterm-send-return'. -This advice records the current prompt position as START-POINT, -calls ORIG-FUN (with ARGS) and then waits until the expected finish -sequence appears. The expected finish sequence is defined as the -substring from the newline before `(vterm--get-prompt-point)` to the -newline after `(vterm--get-prompt-point)`, matching a regex of `\\n>.*` -or `\\ndiff>.*`. - -Once that is detected, the output from START-POINT up to the beginning -of the finish sequence is captured and stored via `aidermacs--store-output`. +(defun aidermacs--vterm-check-finish-sequence-repeated (proc orig-filter start-point expected) + "Check for the finish sequence repeatedly in PROC’s buffer. +Force a vterm render and redisplay. 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) + ;; Force vterm to update its display. + (when (fboundp 'vterm--render) + (vterm--render)) + (force-window-update (selected-window)) + (redisplay t) + (let* ((prompt-point (vterm--get-prompt-point)) + (seq-start (or (save-excursion + (goto-char prompt-point) + (search-backward "\n" nil t)) + (point-min))) + (seq-end (or (save-excursion + (goto-char prompt-point) + (search-forward "\n" nil t)) + (point-max))) + (finish-seq (buffer-substring-no-properties seq-start seq-end))) + (when (and (string-match-p expected finish-seq) + (< start-point prompt-point)) + (let ((output (buffer-substring-no-properties start-point seq-start))) + (aidermacs--store-output (string-trim output))) + (set-process-filter proc orig-filter) + t))))) -This is a covoluted HACK of capturing aider output until someone comes up with a better idea." - (when (and (bound-and-true-p aidermacs-minor-mode) - (eq major-mode 'vterm-mode)) - (let* ((start-point (vterm--get-prompt-point)) - (proc (get-buffer-process (current-buffer))) - (expected "\n[^[:space:]]*>[[:space:]].*\n")) - ;; Save the original process filter. - (let ((orig-filter (process-filter proc))) - ;; Set up our temporary filter. +(defun aidermacs--vterm-output-advice (orig-fun &rest args) + "Advice for vterm output: capture output until the finish sequence appears. +This sets a temporary process filter and installs a repeating timer +to force vterm to update until the expected finish sequence is detected." + (if (and (bound-and-true-p aidermacs-minor-mode) + (eq major-mode 'vterm-mode)) + (let* ((start-point (vterm--get-prompt-point)) + (proc (get-buffer-process (current-buffer))) + (expected "\n[^[:space:]]*>[[:space:]].*\n") + (orig-filter (process-filter proc)) + (timer nil)) + ;; Set our temporary process filter. (set-process-filter proc (lambda (proc string) - ;; Call the original filter first. + ;; Call the original filter. (funcall orig-filter proc string) - ;; Then check for our finish sequence. - (let ((buffer (process-buffer proc))) - (with-current-buffer buffer - (let* ((prompt-point (vterm--get-prompt-point)) - (seq-start (or (save-excursion - (goto-char prompt-point) - (search-backward "\n" nil t)) - (point-min))) - (seq-end (or (save-excursion - (goto-char prompt-point) - (search-forward "\n" nil t)) - (point-max))) - (finish-seq (buffer-substring-no-properties seq-start seq-end))) - (when (and (string-match-p expected finish-seq) - (< start-point prompt-point)) - ;; Capture the output from the original start-point up to - ;; the beginning of the finish sequence. - (let ((output (buffer-substring-no-properties start-point seq-start))) - (aidermacs--store-output (string-trim output)) - ;; Restore the original filter. - (set-process-filter proc orig-filter))))))))))) - (apply orig-fun args)) + ;; If we haven't yet started our repeating timer, do so. + (unless timer + (setq timer (run-with-timer + 0.05 0.05 + (lambda () + (when (aidermacs--vterm-check-finish-sequence-repeated + proc orig-filter start-point expected) + (cancel-timer timer) + (setq timer nil)))))))) + (apply orig-fun args)) + (apply orig-fun args))) (defun aidermacs-run-aidermacs-vterm (program args buffer-name) "Create a vterm-based buffer and run aidermacs PROGRAM with ARGS in BUFFER-NAME. @@ -82,9 +90,6 @@ and BUFFER-NAME is the name of the vterm buffer." (defun aidermacs--send-command-vterm (buffer command) "Send COMMAND to the aidermacs vterm BUFFER." (with-current-buffer buffer - ;; Store command before sending - (setq aidermacs--last-command command - aidermacs--current-output nil) (vterm-send-string command) (vterm-send-return))) diff --git a/aidermacs-backends.el b/aidermacs-backends.el index 3914c07a78..b583b55902 100644 --- a/aidermacs-backends.el +++ b/aidermacs-backends.el @@ -55,13 +55,25 @@ Returns a list of (timestamp . output-text) pairs, most recent first." (interactive) (setq aidermacs--output-history nil)) +(defvar aidermacs--current-callback nil + "Store the callback function for the current command.") + +(defvar aidermacs--in-callback nil + "Flag to prevent recursive callbacks.") + (defun aidermacs--store-output (output) - "Store OUTPUT string in the history with timestamp." + "Store OUTPUT string in the history with timestamp. +If there's a callback function, call it with the output." (setq aidermacs--current-output (substring-no-properties output)) (push (cons (current-time) (substring-no-properties output)) aidermacs--output-history) (when (> (length aidermacs--output-history) aidermacs-output-limit) (setq aidermacs--output-history - (seq-take aidermacs--output-history aidermacs-output-limit)))) + (seq-take aidermacs--output-history aidermacs-output-limit))) + (unless aidermacs--in-callback + (when aidermacs--current-callback + (let ((aidermacs--in-callback t)) + (funcall aidermacs--current-callback output) + (setq aidermacs--current-callback nil))))) ;; Backend dispatcher functions (defun aidermacs-run-aidermacs-backend (program args buffer-name) @@ -74,8 +86,12 @@ and BUFFER-NAME is the name for the aidermacs buffer." (t (aidermacs-run-aidermacs-comint program args buffer-name)))) -(defun aidermacs--send-command-backend (buffer command) - "Send COMMAND to BUFFER using the appropriate backend." +(defun aidermacs--send-command-backend (buffer command &optional callback) + "Send COMMAND to BUFFER using the appropriate backend. +CALLBACK if provided will be called with the command output when available." + (setq aidermacs--last-command command + aidermacs--current-output nil + aidermacs--current-callback callback) (cond ((eq aidermacs-backend 'vterm) (aidermacs--send-command-vterm buffer command)) diff --git a/aidermacs.el b/aidermacs.el index d9af242e4d..68bc2fdcde 100644 --- a/aidermacs.el +++ b/aidermacs.el @@ -206,15 +206,17 @@ With the universal argument, prompt to edit aidermacs-args before running." (aidermacs-switch-to-buffer) (aidermacs-run-aidermacs-backend aidermacs-program current-args buffer-name)))) -(defun aidermacs--send-command (command &optional switch-to-buffer) - "Send COMMAND to the corresponding aidermacs process after performing necessary checks. -Dispatches to the appropriate backend." +(defun aidermacs--send-command (command &optional switch-to-buffer callback) + "Send COMMAND to the corresponding aidermacs process. +If SWITCH-TO-BUFFER is non-nil, switch to the aidermacs buffer. +If CALLBACK is provided, it will be called with the command output when available." (if-let ((aidermacs-buffer (get-buffer (aidermacs-buffer-name)))) (let ((processed-command (aidermacs--process-message-if-multi-line command))) - (aidermacs--send-command-backend aidermacs-buffer processed-command) - (when switch-to-buffer + (when (and switch-to-buffer aidermacs-buffer) (aidermacs-switch-to-buffer)) - (sleep-for 0.2)) + (aidermacs--send-command-backend aidermacs-buffer processed-command callback) + (when (and switch-to-buffer (not (string= (buffer-name) (aidermacs-buffer-name)))) + (aidermacs-switch-to-buffer))) (message "Buffer %s does not exist. Please start aidermacs with 'M-x aidermacs-run-aidermacs'." aidermacs-buffer-name))) @@ -228,8 +230,7 @@ If the current buffer is already the aidermacs buffer, do nothing." (interactive) (let ((buffer (get-buffer (aidermacs-buffer-name)))) (cond - ((string= (buffer-name) (aidermacs-buffer-name)) - (message "Already in aidermacs buffer")) + ((string= (buffer-name) (aidermacs-buffer-name)) t) ((and buffer (get-buffer-window buffer)) (select-window (get-buffer-window buffer))) ;; Switch to existing window (buffer @@ -332,7 +333,6 @@ wrap it in {aidermacs\nstr\naidermacs}. Otherwise return STR unchanged." (defun aidermacs--parse-ls-output (output) "Parse the /ls command OUTPUT to extract files in chat. - After the \"Files in chat:\" header, each subsequent line that begins with whitespace is processed. The first non-whitespace token is taken as the file name. Relative paths are resolved using the repository root (if available) or @@ -351,11 +351,12 @@ Returns a deduplicated list of such file names." (let ((base (or (vc-git-root default-directory) default-directory)) files) - ;; Process lines that start with whitespace. + ;; Process each line that begins with whitespace. (while (and (not (eobp)) (string-match-p "^[[:space:]]" (thing-at-point 'line t))) (let* ((line (string-trim (thing-at-point 'line t))) - (file (car (split-string line)))) + (parts (split-string line)) + (file (if parts (car parts) ""))) (unless (string-empty-p file) (let ((file-abs (if (file-name-absolute-p file) file @@ -367,28 +368,31 @@ Returns a deduplicated list of such file names." ;;;###autoload (defun aidermacs-list-added-files () - "List all files currently added to the chat session." + "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 "/ls" t) - ;; Wait briefly for output to be processed - (sleep-for 0.5) - (if-let ((files (aidermacs--parse-ls-output aidermacs--current-output))) - (progn - (message "%s" (prin1-to-string files)) - files) - (error "No files currently added to chat or unable to parse output"))) + (aidermacs--send-command + "/ls" t + (lambda (output) + (let ((files (aidermacs--parse-ls-output output))) + (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 "/ls" t) - ;; Wait briefly for output to be processed - (sleep-for 0.5) - (if-let* ((files (aidermacs-list-added-files)) - (file (completing-read "Select file to drop: " files nil t))) - (aidermacs--send-command (format "/drop %s" file) t) - (error "No files available to drop"))) + (aidermacs--send-command + "/ls" t + (lambda (output) + (condition-case nil + (if-let* ((files (aidermacs--parse-ls-output output)) + (file (completing-read "Select file to drop: " files nil t))) + (progn + (aidermacs--send-command (format "/drop %s" file))) + (message "No files available to drop")) + (quit (message "Drop file cancelled")))))) + ;;;###autoload (defun aidermacs-show-output-history () @@ -408,7 +412,7 @@ Returns a deduplicated list of such file names." (goto-char (point-min)) (setq buffer-read-only t) (local-set-key (kbd "q") 'kill-this-buffer) - (switch-to-buffer-other-frame buf)))) + (switch-to-buffer-other-frame buf)))) ;;;###autoload (defun aidermacs-get-last-output ()