From 84944a8572a84862fa39e52a62ccba24cdee9891 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 13 May 2026 15:39:40 -0500 Subject: feat(vterm): show tmux scrollback history in place `cj/vterm-tmux-history' previously used `pop-to-buffer', which routed the history view through display-buffer-alist -- in an agent window that often meant a split or a hand-off to another window, costing the agent its frame slot. `switch-to-buffer' instead drops the history into the selected window directly; the existing quit handler already restores the origin in that same window via `set-window-buffer'. New test asserts the in-place behavior: starting single-window with a vterm origin, invoking the command leaves `(one-window-p)` t with the history buffer in the original slot. The existing render test no longer needs its `pop-to-buffer' stub since `switch-to-buffer' works in batch. --- modules/vterm-config.el | 9 ++++++-- tests/test-vterm-tmux-history.el | 48 +++++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/modules/vterm-config.el b/modules/vterm-config.el index 764c9dcd7..954e096ab 100644 --- a/modules/vterm-config.el +++ b/modules/vterm-config.el @@ -121,7 +121,12 @@ returns to the vterm without copying. RET is left unbound." The history buffer uses normal Emacs navigation and selection. `M-w' copies the active region and stays open, so several pieces can be copied in a row; `q', `', or `C-g' returns point to the vterm -buffer that launched it." +buffer that launched it. + +The history view replaces the origin vterm buffer in the same window +(via `switch-to-buffer'), not a split or a popped-up window -- reading +past output should keep the agent's frame slot intact, and quit puts +the live terminal back where it was." (interactive) (let* ((origin-buffer (current-buffer)) (origin-window (selected-window)) @@ -139,7 +144,7 @@ buffer that launched it." (setq-local cj/vterm-tmux-history--origin-window origin-window) (setq-local cj/vterm-tmux-history--origin-point origin-point) (goto-char (point-max))) - (pop-to-buffer buffer))) + (switch-to-buffer buffer))) (defun cj/vterm-copy-mode-cancel () "Exit `vterm-copy-mode' without copying." diff --git a/tests/test-vterm-tmux-history.el b/tests/test-vterm-tmux-history.el index eec6c6220..c0a71421d 100644 --- a/tests/test-vterm-tmux-history.el +++ b/tests/test-vterm-tmux-history.el @@ -63,15 +63,12 @@ RESPONSES is an alist of (ARGS EXIT-CODE OUTPUT)." "Normal: command renders tmux history in a normal Emacs buffer." (let ((origin (cj/test--make-fake-vterm-buffer "*test-vterm-history-origin*"))) (unwind-protect - (with-current-buffer origin + (save-window-excursion + (switch-to-buffer origin) (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8")) - ((symbol-function 'pop-to-buffer) - (lambda (buffer &rest _) - (set-buffer buffer) - buffer))) + (lambda (_process) "/dev/pts/8"))) (test-vterm-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/8\t%8\n") @@ -81,10 +78,45 @@ RESPONSES is an alist of (ARGS EXIT-CODE OUTPUT)." (should (eq major-mode 'cj/vterm-tmux-history-mode)) (should buffer-read-only) (should (string-match-p "history http://example.com" - (buffer-string))))))) + (buffer-string)))))) + (cj/test--kill-buffers-matching-prefix "*vterm tmux history") + (when (buffer-live-p origin) + (kill-buffer origin))))) + +(ert-deftest test-vterm-tmux-history-replaces-origin-buffer-in-same-window () + "Normal: the history view replaces the origin in the selected window. + +Before the in-place change, `cj/vterm-tmux-history' used `pop-to-buffer' +which could split or hand the buffer to a different window. The fix +uses `switch-to-buffer' so reading scrollback keeps the agent's frame +slot." + (let ((origin (cj/test--make-fake-vterm-buffer "*test-vterm-history-inplace*"))) + (unwind-protect + (save-window-excursion + (delete-other-windows) + (switch-to-buffer origin) + (let ((win (selected-window))) + (should (eq (window-buffer win) origin)) + (should (one-window-p)) + (cl-letf (((symbol-function 'get-buffer-process) + (lambda (_buffer) 'fake-process)) + ((symbol-function 'process-tty-name) + (lambda (_process) "/dev/pts/8"))) + (test-vterm-tmux-history--with-tmux-mock + '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 + "/dev/pts/8\t%8\n") + (("capture-pane" "-p" "-J" "-S" "-" "-E" "-" "-t" "%8") 0 + "scrollback line\n")) + (cj/vterm-tmux-history))) + ;; Same window, no split, history buffer now in the slot. + (should (one-window-p)) + (should (eq (selected-window) win)) + (should (string-prefix-p + "*vterm tmux history:" + (buffer-name (window-buffer win)))))) (cj/test--kill-buffers-matching-prefix "*vterm tmux history") (when (buffer-live-p origin) - (kill-buffer origin)))) + (kill-buffer origin))))) (ert-deftest test-vterm-tmux-history-quit-returns-to-origin () "Normal: q / / C-g (cj/vterm-tmux-history-quit) kills the history -- cgit v1.2.3