aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/ai-term.el21
-rw-r--r--tests/test-ai-term--close.el31
2 files changed, 45 insertions, 7 deletions
diff --git a/modules/ai-term.el b/modules/ai-term.el
index 1384f812..baf752fe 100644
--- a/modules/ai-term.el
+++ b/modules/ai-term.el
@@ -54,8 +54,10 @@
;; instead of toggling the current one.
;; - M-F9 `cj/ai-term-close' -- gracefully close an agent: kill its
;; tmux session (stopping the agent process), then its terminal
-;; buffer and window. Confirms first. Targets the current
-;; agent, the sole live agent, or prompts among several.
+;; buffer. Its window stays in the layout (swapped to the
+;; working buffer), so closing never collapses a split. Confirms
+;; first. Targets the current agent, the sole live agent, or
+;; prompts among several.
;; - C-S-F9 `cj/ai-term-close' -- same close command, second binding.
;; (M-F9 is the primary; C-S-F9 may be swallowed by the
;; Wayland/PGTK layer on some machines.)
@@ -859,12 +861,14 @@ down."
(error nil)))
(defun cj/--ai-term-close-buffer (buffer)
- "Gracefully tear down AI-term BUFFER: tmux session, window, buffer.
+ "Gracefully tear down AI-term BUFFER: tmux session, then buffer.
Derives the tmux session name from BUFFER's `default-directory' (the
project dir the terminal was created in) and kills it so the agent
-process stops. Deletes BUFFER's window when it's shown and isn't the
-only window in its frame, then kills BUFFER (suppressing the
+process stops. When BUFFER is shown, swaps its window to a non-agent
+buffer (the working file) rather than deleting the window -- closing an
+agent must not collapse the user's window layout; the F9 hide toggle is
+what collapses the split. Then kills BUFFER (suppressing the
process-still-running prompt -- the session is already down). No-op
when BUFFER isn't an AI-term buffer."
(when (cj/--ai-term-buffer-p buffer)
@@ -872,8 +876,11 @@ when BUFFER isn't an AI-term buffer."
(cj/--ai-term-tmux-session-name
(buffer-local-value 'default-directory buffer)))
(let ((win (get-buffer-window buffer)))
- (when (and win (> (length (window-list (window-frame win) 'never)) 1))
- (delete-window win)))
+ (when (window-live-p win)
+ (with-selected-window win
+ (switch-to-buffer
+ (or (cj/--ai-term-most-recent-non-agent-buffer)
+ (other-buffer buffer t))))))
(let ((kill-buffer-query-functions nil))
(kill-buffer buffer))))
diff --git a/tests/test-ai-term--close.el b/tests/test-ai-term--close.el
index 654e85f0..4098c091 100644
--- a/tests/test-ai-term--close.el
+++ b/tests/test-ai-term--close.el
@@ -13,7 +13,9 @@
(require 'cl-lib)
(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory))
(require 'ai-term)
+(require 'testutil-ghostel-buffers)
(ert-deftest test-ai-term--kill-tmux-session-runs-kill-session ()
"Normal: invokes `tmux kill-session -t <session>'."
@@ -58,6 +60,35 @@
(should (buffer-live-p buf)))
(when (buffer-live-p buf) (kill-buffer buf)))))
+(ert-deftest test-ai-term--close-buffer-keeps-window-split ()
+ "Regression: closing an agent in a split keeps its window in the layout,
+showing a non-agent buffer, instead of deleting the split. Craig's M-F9
+annoyance -- a close must not tear down the window arrangement (the F9 hide
+toggle is what collapses the split; close should not)."
+ (cj/test--kill-agent-buffers)
+ (let ((work (get-buffer-create "*test-close-keep-work*"))
+ (agent (get-buffer-create "agent [close-keep]")))
+ (with-current-buffer agent (setq-local default-directory "/tmp/close-keep/"))
+ (unwind-protect
+ (save-window-excursion
+ (delete-other-windows)
+ (set-window-buffer (selected-window) work)
+ (let ((agent-win (split-window (selected-window) nil 'below)))
+ (set-window-buffer agent-win agent)
+ (should-not (one-window-p))
+ (cl-letf (((symbol-function 'cj/--ai-term-kill-tmux-session)
+ (lambda (_s) 0)))
+ (cj/--ai-term-close-buffer agent))
+ ;; The window survives the close ...
+ (should (window-live-p agent-win))
+ (should-not (one-window-p))
+ ;; ... now showing a non-agent buffer ...
+ (should-not (cj/--ai-term-buffer-p (window-buffer agent-win)))
+ ;; ... and the agent buffer itself is gone.
+ (should-not (buffer-live-p agent))))
+ (when (get-buffer "*test-close-keep-work*") (kill-buffer "*test-close-keep-work*"))
+ (cj/test--kill-agent-buffers))))
+
(ert-deftest test-ai-term--close-target-current-agent-buffer ()
"Normal: returns the current buffer when it is an agent buffer."
(let ((buf (get-buffer-create "agent [cur]")))