branch: elpa/aidermacs
commit a2b7e239143606ecc1708bf9e750211e238eca11
Merge: fe34d0ceca 73f96304c1
Author: Matthew Zeng <[email protected]>
Commit: GitHub <[email protected]>
Merge pull request #34 from ianschenck/ian/profile-speed-up
Optimize vterm output handling and add multiline input support
---
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