branch: elpa/aidermacs commit a2b7e239143606ecc1708bf9e750211e238eca11 Merge: fe34d0ceca 73f96304c1 Author: Matthew Zeng <matthew...@posteo.net> Commit: GitHub <nore...@github.com>
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