aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-02 13:04:37 -0500
committerCraig Jennings <c@cjennings.net>2026-06-02 13:04:37 -0500
commit38dad925528e05f2084474ead5519a6a6ceb70f7 (patch)
tree84f4928e3e9b3fdb88be8d73a4db46728beacc41 /modules
parent76f983eff93e6d37d0710d2854361a8314f63d79 (diff)
downloaddotemacs-38dad925528e05f2084474ead5519a6a6ceb70f7.tar.gz
dotemacs-38dad925528e05f2084474ead5519a6a6ceb70f7.zip
fix(ai-vterm): make F9 a faithful toggle of the agent split
F9 toggle-off used quit-restore-window to dismiss the agent. With several agents alive sharing one slot, switching among them (C-F9) reuses the window via set-window-buffer, which leaves the window's quit-restore parameter pointing at the first agent shown. Once stale, quit-restore-window falls back to switch-to-prev-buffer and surfaces a different agent instead of removing the window, so F9 appeared to "show another agent" rather than hide the split. Toggle-off now collapses the split with delete-window, which is independent of the slot's buffer history, so the working buffer reclaims the frame. Geometry is captured first so the next toggle-on re-splits at the same width. Toggle-on reopens the exact agent that was hidden (new cj/--ai-vterm-last-hidden-buffer), falling back to the most-recent agent only when that buffer has been killed. Hide-then-show is now a faithful round trip, not a jump to whichever agent is most-recent in buffer-list. Sole-window toggle-off returns to the most-recent non-agent buffer instead of other-buffer, which could land on another agent. I updated the two reuse-edge-window tests that asserted the old restore-displaced-into-a-kept-slot behavior to match the new always-collapse behavior.
Diffstat (limited to 'modules')
-rw-r--r--modules/ai-vterm.el83
1 files changed, 64 insertions, 19 deletions
diff --git a/modules/ai-vterm.el b/modules/ai-vterm.el
index 00589890..4f086636 100644
--- a/modules/ai-vterm.el
+++ b/modules/ai-vterm.el
@@ -176,6 +176,21 @@ recently-selected first. Non-AI-vterm buffers are filtered out via
`cj/--ai-vterm-buffer-p'."
(seq-filter #'cj/--ai-vterm-buffer-p (buffer-list)))
+(defun cj/--ai-vterm-most-recent-non-agent-buffer ()
+ "Return the most-recently-selected live non-agent buffer, or nil.
+
+Walks `buffer-list' (most-recently-selected first) and returns the
+first buffer that is not an AI-vterm agent buffer (per
+`cj/--ai-vterm-buffer-p') and is not an internal buffer (name starting
+with a space). Used by the single-window F9 toggle-off so dismissing a
+full-frame agent returns to the file the user was working in (e.g.
+todo.org) rather than swapping in another agent."
+ (seq-find (lambda (b)
+ (and (buffer-live-p b)
+ (not (cj/--ai-vterm-buffer-p b))
+ (not (string-prefix-p " " (buffer-name b)))))
+ (buffer-list)))
+
(defun cj/--ai-vterm-displayed-agent-window (&optional frame)
"Return a window in FRAME currently displaying an AI-vterm buffer, or nil.
@@ -409,6 +424,17 @@ without deleting), nil when the window was deleted. Consumed by
buried agent in the current window (the only one) or splitting per
the saved direction.")
+(defvar cj/--ai-vterm-last-hidden-buffer nil
+ "The agent buffer hidden by the most recent F9 toggle-off.
+
+Captured in `cj/ai-vterm' just before an agent window is torn down, and
+consumed by `cj/--ai-vterm-dispatch' so the next toggle-on reopens the
+SAME agent that was on screen rather than whichever agent happens to be
+most-recent in `buffer-list'. Without this, hiding one agent and
+reopening could surface a different one when several agents are alive --
+the \"the displayed buffer changes\" bug. Falls back to the buffer-list
+MRU when nil or when the remembered buffer has been killed.")
+
(defvar cj/--ai-vterm-last-size nil
"Last user-chosen body size for the AI-vterm display.
@@ -695,7 +721,15 @@ without firing real `display-buffer' or `quit-window' calls."
(t
(let ((buffers (cj/--ai-vterm-agent-buffers)))
(cond
- (buffers (cons 'redisplay-recent (car buffers)))
+ (buffers
+ ;; Reopen the agent the last toggle-off hid (faithful toggle), so
+ ;; long as it's still alive and among the live agents. Otherwise
+ ;; fall back to the most-recently-selected agent.
+ (cons 'redisplay-recent
+ (if (and (buffer-live-p cj/--ai-vterm-last-hidden-buffer)
+ (memq cj/--ai-vterm-last-hidden-buffer buffers))
+ cj/--ai-vterm-last-hidden-buffer
+ (car buffers))))
(t '(pick-project))))))))
(defun cj/--ai-vterm-refuse-in-terminal ()
@@ -754,6 +788,9 @@ M-F9 (and C-S-F9) close an agent via `cj/ai-vterm-close'."
(cj/--ai-vterm-refuse-in-terminal)
(pcase (cj/--ai-vterm-dispatch)
(`(toggle-off . ,win)
+ ;; Remember which agent we're hiding so the next toggle-on reopens this
+ ;; same one, not whichever agent is most-recent in `buffer-list'.
+ (setq cj/--ai-vterm-last-hidden-buffer (window-buffer win))
(cond
;; Lone fullscreen agent (e.g. after `C-x 1' inside it): there is no
;; prior layout for the native undo to restore and deleting would
@@ -770,27 +807,35 @@ M-F9 (and C-S-F9) close an agent via `cj/ai-vterm-close'."
(when (and (window-live-p win)
(cj/--ai-vterm-buffer-p (window-buffer win)))
(with-selected-window win
- (switch-to-buffer (other-buffer (window-buffer win) t)))))
- ;; Multi-window: `quit-restore-window' is the native undo for a
- ;; `display-buffer' display. The agent's display path records the
- ;; matching `quit-restore' state -- `display-buffer-record-window'
- ;; (type `reuse') in `cj/--ai-vterm-reuse-edge-window' when it takes
- ;; over a slot, `display-buffer-in-direction' (type `window') when it
- ;; splits a fresh one. So one call restores the displaced buffer
- ;; into a reused slot, or deletes a window that was split for the
- ;; agent. No BURY-OR-KILL argument: burying would move the agent to
- ;; the end of the buffer list, so with several agents alive the next
- ;; F9 (`cj/--ai-vterm-dispatch' re-shows the most-recent agent) would
- ;; bring back a different one instead of the agent just toggled off.
+ (switch-to-buffer
+ (or (cj/--ai-vterm-most-recent-non-agent-buffer)
+ (other-buffer (window-buffer win) t))))))
+ ;; Multi-window: collapse the agent split outright by deleting its
+ ;; window, so the working buffer (e.g. todo.org) reclaims the space.
+ ;; F9 is a pure show/hide toggle of THE agent split -- it must never
+ ;; surface a different agent. `quit-restore-window' can't guarantee
+ ;; that here: switching among several agents reuses the one slot via
+ ;; `set-window-buffer' (see `cj/--ai-vterm-reuse-existing-agent'),
+ ;; which leaves the window's `quit-restore' parameter pointing at the
+ ;; FIRST agent shown. Once it's stale, `quit-restore-window' falls
+ ;; back to `switch-to-prev-buffer' and surfaces another agent instead
+ ;; of removing the window -- exactly the "F9 shows another agent"
+ ;; bug. `delete-window' is unconditional and slot-history-independent.
+ ;; Capture geometry first so the next toggle-on splits at the same
+ ;; size (the user's chosen split width is preserved across the toggle).
(t
- ;; Capture geometry first: when the agent had its own split window
- ;; (axis-mismatch / single-window origin), `quit-restore-window'
- ;; removes it and the next toggle-on splits afresh -- replaying the
- ;; captured size preserves a user resize across the toggle. Harmless
- ;; in the reused-slot case, where the split path is never taken.
(cj/--ai-vterm-capture-state win)
(setq cj/--ai-vterm-last-was-bury nil)
- (quit-restore-window win)))
+ (if (and (window-live-p win)
+ (> (length (window-list (window-frame win) 'never)) 1))
+ (delete-window win)
+ ;; Degenerate fallback (window became sole between dispatch and
+ ;; here): swap to a non-agent buffer rather than leave the agent up.
+ (when (window-live-p win)
+ (with-selected-window win
+ (switch-to-buffer
+ (or (cj/--ai-vterm-most-recent-non-agent-buffer)
+ (other-buffer (window-buffer win) t))))))))
nil)
(`(redisplay-recent . ,buf)
(display-buffer buf)