branch: elpa/aidermacs
commit 73f96304c1f947a4cdc4daf287720b3469280282
Author: Ian Sung-Schenck (aider) <i...@liquidmetal.ai>
Commit: Ian Sung-Schenck <i...@liquidmetal.ai>

    Optimize vterm output handling and add multiline input support
    
    Current vterm processing is inefficient due to forced rendering on every
    check. This change optimizes performance by rendering only when necessary
    and checking output more intelligently.
    
    The commit also adds a multiline input mode for vterm, allowing users to
    compose and edit complex commands before sending them. This addresses
    the limitation of vterm's single-line input model while maintaining the
    interactive terminal experience.
    
    Key changes:
    - Reduce unnecessary vterm rendering to improve performance
    - Implement smarter prompt detection with error handling
    - Add multiline input mode with configurable key bindings
    - Improve resource management with cleanup hooks
---
 aidermacs-backend-vterm.el | 177 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 143 insertions(+), 34 deletions(-)

diff --git a/aidermacs-backend-vterm.el b/aidermacs-backend-vterm.el
index 5fbef78148..b7c81ab2b4 100644
--- a/aidermacs-backend-vterm.el
+++ b/aidermacs-backend-vterm.el
@@ -18,46 +18,58 @@
 (defvar vterm-buffer-name)
 
 (defun aidermacs--vterm-check-finish-sequence-repeated (proc orig-filter 
start-point expected)
-  "Check for the finish sequence repeatedly in PROC's buffer.
+  "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.  Forces a vterm render and redisplay.  If the finish
-sequence is detected, store the output via `aidermacs--store-output`,
-restore ORIG-FILTER, and return t."
+pattern to match.  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)))))
+      ;; Only render vterm if needed - this is expensive
+      (when (and (fboundp 'vterm--render)
+                 (vterm--invalidate-p))
+        (condition-case nil
+            (vterm--render)
+          (error nil)))
+      
+      (let* ((prompt-point (condition-case nil
+                               (vterm--get-prompt-point)
+                             (error (point-max))))
+             ;; Only do these expensive operations if we have a new prompt
+             (has-new-prompt (< start-point prompt-point)))
+        
+        (when has-new-prompt
+          ;; Only search for boundaries when we have a new prompt
+          (let* ((seq-start (or (save-excursion
+                                  (goto-char prompt-point)
+                                  (condition-case nil
+                                      (search-backward "\n" nil t)
+                                    (error nil)))
+                                (point-min)))
+                 ;; Only get the prompt line, not the whole sequence
+                 (prompt-line (buffer-substring-no-properties 
+                               seq-start 
+                               (min (+ seq-start 200) (point-max)))))
+            
+            (when (string-match-p expected prompt-line)
+              (let ((output (buffer-substring-no-properties start-point 
seq-start)))
+                (aidermacs--store-output (string-trim output)))
+              (set-process-filter proc orig-filter)
+              t)))))))
 
 (defun aidermacs--vterm-output-advice (orig-fun &rest args)
   "Capture vterm output until the finish sequence appears.
 ORIG-FUN is the original function being advised.  ARGS are its arguments.
-This sets a temporary process filter and installs a repeating timer to
-force vterm to update until the expected finish sequence is detected."
+This sets a temporary process filter that checks for the finish sequence
+after each output chunk, reducing the need for timers."
   (if (and (bound-and-true-p aidermacs-minor-mode)
            (eq major-mode 'vterm-mode))
-      (let* ((start-point (vterm--get-prompt-point))
+      (let* ((start-point (condition-case nil
+                              (vterm--get-prompt-point)
+                            (error (point-min))))
              (proc (get-buffer-process (current-buffer)))
-             (expected "\n[^[:space:]]*>[[:space:]].*\n")
+             ;; Simplified pattern that just looks for a shell prompt
+             (expected "^[^[:space:]]*>[[:space:]]")
              (orig-filter (process-filter proc))
              (timer nil))
         ;; Set our temporary process filter.
@@ -66,15 +78,29 @@ force vterm to update until the expected finish sequence is 
detected."
          (lambda (proc string)
            ;; Call the original filter.
            (funcall orig-filter proc string)
-           ;; If we haven't yet started our repeating timer, do so.
+           
+           ;; Check immediately after receiving output
+           (when (aidermacs--vterm-check-finish-sequence-repeated
+                  proc orig-filter start-point expected)
+             (when timer
+               (cancel-timer timer)
+               (setq timer nil))
+             (return))
+           
+           ;; If we haven't found it yet, set up a timer with adaptive 
frequency
            (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)
+                            (cond
+                             ;; Found the prompt, we're done
+                             ((aidermacs--vterm-check-finish-sequence-repeated
+                               proc orig-filter start-point expected)
                               (cancel-timer timer)
-                              (setq timer nil))))))))
+                              (setq timer nil))
+                             
+                             ;; Just keep checking until we find the prompt
+                             )))))))
         (apply orig-fun args))
     (apply orig-fun args)))
 
@@ -94,16 +120,99 @@ BUFFER-NAME is the name for the vterm buffer."
            (vterm-shell-orig vterm-shell))
       (with-current-buffer (vterm-other-window)
         (aidermacs-minor-mode 1)
-        (advice-add 'vterm-send-return :around 
#'aidermacs--vterm-output-advice))))
+        (advice-add 'vterm-send-return :around 
#'aidermacs--vterm-output-advice)
+        ;; Set a reasonable scrollback limit to prevent memory issues
+        (setq-local vterm-max-scrollback 5000)
+        ;; Set up multi-line key binding
+        (let ((map (make-sparse-keymap)))
+          (set-keymap-parent map (current-local-map))
+          (define-key map (kbd aidermacs-vterm-multiline-newline-key) 
#'aidermacs-vterm-insert-newline)
+          (define-key map (kbd aidermacs-vterm-multiline-send-key) 
#'aidermacs-vterm-send-multi-line)
+          (define-key map (kbd "C-c C-k") #'aidermacs-vterm-cancel-multi-line)
+          (use-local-map map))
+        ;; Add cleanup hook
+        (add-hook 'kill-buffer-hook #'aidermacs--vterm-cleanup nil t))))
   buffer-name)
 
+(defvar-local aidermacs--vterm-active-timer nil
+  "Store the active timer for vterm output processing.")
+
+(defvar-local aidermacs--vterm-multi-line-input nil
+  "Accumulated multi-line input in vterm mode.")
+
+(defvar-local aidermacs--vterm-multi-line-mode nil
+  "Non-nil when in multi-line input mode in vterm.")
+
+(defcustom aidermacs-vterm-multiline-newline-key "S-<return>"
+  "Key binding to enter a newline without sending in vterm."
+  :type 'string
+  :group 'aidermacs)
+
+(defcustom aidermacs-vterm-multiline-send-key "C-<return>"
+  "Key binding to send multi-line input in vterm mode."
+  :type 'string
+  :group 'aidermacs)
+
 (defun aidermacs--send-command-vterm (buffer command)
   "Send command to the aidermacs vterm buffer.
 BUFFER is the target buffer to send to.  COMMAND is the text to send."
   (with-current-buffer buffer
+    ;; Cancel any existing timer to prevent resource leaks
+    (when aidermacs--vterm-active-timer
+      (cancel-timer aidermacs--vterm-active-timer)
+      (setq aidermacs--vterm-active-timer nil))
+    ;; Reset multiline mode if active
+    (aidermacs-vterm-reset-multi-line-state)
     (vterm-send-string command)
     (vterm-send-return)))
 
+(defun aidermacs-vterm-insert-newline ()
+  "Insert a newline in vterm multi-line input."
+  (interactive)
+  (if aidermacs--vterm-multi-line-mode
+      (progn
+        (setq aidermacs--vterm-multi-line-input 
+              (concat aidermacs--vterm-multi-line-input "\n"))
+        (let ((inhibit-read-only t))
+          (vterm-insert "\n")))
+    ;; If not in multi-line mode, enter it
+    (setq aidermacs--vterm-multi-line-mode t
+          aidermacs--vterm-multi-line-input "")
+    (let ((inhibit-read-only t))
+      (vterm-insert "\n[multi-line mode] (Use Shift+Enter for new line, 
Ctrl+Enter to send)\n"))))
+
+(defun aidermacs-vterm-send-multi-line ()
+  "Send accumulated multi-line input in vterm."
+  (interactive)
+  (when aidermacs--vterm-multi-line-mode
+    (let ((input (string-trim aidermacs--vterm-multi-line-input)))
+      (setq aidermacs--vterm-multi-line-mode nil
+            aidermacs--vterm-multi-line-input nil)
+      ;; Format and send the input
+      (vterm-send-string (format "{aidermacs\n%s\naidermacs}" input))
+      (vterm-send-return))))
+
+(defun aidermacs-vterm-cancel-multi-line ()
+  "Cancel multiline input mode in vterm."
+  (interactive)
+  (when aidermacs--vterm-multi-line-mode
+    (setq aidermacs--vterm-multi-line-mode nil
+          aidermacs--vterm-multi-line-input nil)
+    (let ((inhibit-read-only t))
+      (vterm-insert "\n[multi-line mode canceled]\n"))))
+
+(defun aidermacs-vterm-reset-multi-line-state ()
+  "Reset multi-line state variables."
+  (setq aidermacs--vterm-multi-line-mode nil
+        aidermacs--vterm-multi-line-input nil))
+
+(defun aidermacs--vterm-cleanup ()
+  "Clean up vterm resources when buffer is killed."
+  (when aidermacs--vterm-active-timer
+    (cancel-timer aidermacs--vterm-active-timer)
+    (setq aidermacs--vterm-active-timer nil))
+  (aidermacs-vterm-reset-multi-line-state))
+
 (provide 'aidermacs-backend-vterm)
 
 ;;; aidermacs-backend-vterm.el ends here

Reply via email to