aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-13 15:39:40 -0500
committerCraig Jennings <c@cjennings.net>2026-05-13 15:39:40 -0500
commitc3ec8508a8ff414d099d9e567eb5ffd43a9c93f2 (patch)
tree4229514bcfe38d3c569eeadfc5a04226d8816c39
parent9600611d5f8382ffc849d56a67ba5eb980d64e04 (diff)
downloaddotemacs-c3ec8508a8ff414d099d9e567eb5ffd43a9c93f2.tar.gz
dotemacs-c3ec8508a8ff414d099d9e567eb5ffd43a9c93f2.zip
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.
-rw-r--r--modules/vterm-config.el9
-rw-r--r--tests/test-vterm-tmux-history.el48
-rw-r--r--todo.org35
3 files changed, 71 insertions, 21 deletions
diff --git a/modules/vterm-config.el b/modules/vterm-config.el
index 764c9dcd..954e096a 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', `<escape>', 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 eec6c622..c0a71421 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 / <escape> / C-g (cj/vterm-tmux-history-quit) kills the history
diff --git a/todo.org b/todo.org
index 97a77094..86fa4a2a 100644
--- a/todo.org
+++ b/todo.org
@@ -142,23 +142,36 @@ flag set on bury, flag cleared on delete-window, flag respected only
when still one-window, flag not set when bury didn't run, and the
end-to-end roundtrip. Full =make test-unit= green.
-** TODO [#B] AI-vterm scrollback history should replace agent buffer in place :feature:
+** DONE [#B] AI-vterm scrollback history should replace agent buffer in place :feature:
When viewing the scrollback history of an AI-vterm buffer, the history view should
replace the live agent buffer in the same window rather than splitting or popping
a separate window. Goal: read past output without losing the agent's frame slot,
then snap back to the live buffer when done.
-Open questions before implementation:
-- Is the trigger an existing command (e.g. =vterm-copy-mode= toggle staying
- in-place) or a new command that builds a read-only history buffer?
-- Round-trip ergonomics: how does the user return to the live agent? Same key as
- the entry, or a separate "resume" binding?
-- Does this need to integrate with the F9 toggle state (so a toggle-off while in
- history mode does the right thing)?
-
-Locations: =modules/ai-vterm.el= (no scrollback-history command exists yet) and
-=modules/vterm-config.el= (vterm base bindings).
+Decisions on the open questions:
+- *Trigger*: reused the existing =cj/vterm-tmux-history= command (=C-; x h=).
+ No new command -- it already captures the tmux pane and runs from any
+ vterm buffer including agents.
+- *Round-trip*: =q= / =<escape>= / =C-g= already restore the origin in
+ the same window via =cj/vterm-tmux-history-quit=. Same key as the
+ scrollback mode's other exits.
+- *F9 integration*: deferred. Pressing F9 in history mode now treats the
+ history buffer as non-agent (its name is =*vterm tmux history: ...*=,
+ not =agent [...]=) so dispatch falls through to redisplay-recent + a
+ saved-direction split. A user who wants to toggle agent off should
+ press =q= first, then F9. Filed as a follow-up if it bites.
+
+Fix: =modules/vterm-config.el= -- one line. =pop-to-buffer buffer=
+became =switch-to-buffer buffer= so the history view replaces the origin
+in the selected window instead of going through display-buffer's split
+logic. Quit was already in-place via =set-window-buffer=.
+
+New test in =tests/test-vterm-tmux-history.el= asserts the selected
+window's buffer becomes the history buffer with no extra window
+created (=one-window-p= still t). Existing tests dropped their
+=pop-to-buffer= stub since =switch-to-buffer= works directly in batch.
+Full =make test-unit= green.
** TODO [#B] Add ERT coverage for modules below 70% :tests: