summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/ai-vterm.el119
1 files changed, 91 insertions, 28 deletions
diff --git a/modules/ai-vterm.el b/modules/ai-vterm.el
index 266966ea..4306db9a 100644
--- a/modules/ai-vterm.el
+++ b/modules/ai-vterm.el
@@ -24,21 +24,24 @@
;; "[running]" when a live vterm buffer exists), the rest follow in
;; alphabetical order.
;;
-;; Three F-key entry points:
+;; Four F-key entry points:
;;
-;; - F9 `cj/ai-vterm' -- DWIM dispatch. If an agent buffer is
-;; currently displayed in this frame, F9 quits its window
-;; (toggle off). Otherwise, if exactly one agent buffer is
-;; alive, F9 re-displays it; if zero or two-plus are alive, F9
-;; falls through to the project picker.
-;; - C-F9 `cj/ai-vterm-pick-project' -- always show the project
-;; picker, even when an agent buffer is currently displayed.
-;; Used when the user wants to start a new project session
-;; instead of toggling the current one.
-;; - M-F9 `cj/toggle-gptel' -- toggle gptel's *AI-Assistant* window.
-;; Lives outside this module (defined in `modules/ai-config.el')
-;; but the binding is grouped with the other F9-family launchers
-;; here so the dispatch shape is visible in one place.
+;; - F9 `cj/ai-vterm' -- DWIM dispatch. If an agent buffer is
+;; currently displayed in this frame, F9 quits its window
+;; (toggle off). Otherwise, if exactly one agent buffer is
+;; alive, F9 re-displays it; if zero or two-plus are alive, F9
+;; falls through to the project picker.
+;; - C-F9 `cj/ai-vterm-pick-project' -- always show the project
+;; picker, even when an agent buffer is currently displayed.
+;; Used when the user wants to start a new project session
+;; instead of toggling the current one.
+;; - M-F9 `cj/ai-vterm-close' -- gracefully close an agent: kill its
+;; tmux session (stopping the agent process), then its vterm
+;; buffer and window. Confirms first. Targets the current
+;; agent, the sole live agent, or prompts among several.
+;; - C-S-F9 `cj/ai-vterm-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.)
;;
;; Existing windmove (Shift-arrows) handles code <-> agent focus
;; toggling. Buffer-move (C-M-arrows) handles side-swap. Neither
@@ -57,12 +60,6 @@
(declare-function vterm-send-return "vterm" ())
(defvar vterm-mode-map)
-;; `cj/toggle-gptel' lives in ai-config.el. Declaring it as an interactive
-;; autoload (rather than `require'ing ai-config here) silences the byte-compile
-;; warning at line 685/696 while keeping ai-vterm.el free of a load-time
-;; dependency on the full ai-config stack.
-(autoload 'cj/toggle-gptel "ai-config" nil t)
-
(defgroup ai-vterm nil
"In-Emacs AI-agent launcher with vertical-split vterm."
:group 'tools)
@@ -680,8 +677,7 @@ With prefix ARG, display the buffer without selecting its window
when a buffer is being shown (no effect on the toggle-off branch).
See `cj/ai-vterm-pick-project' (C-F9) to force the project picker.
-M-F9 toggles gptel's *AI-Assistant* window (`cj/toggle-gptel',
-defined in `modules/ai-config.el')."
+M-F9 (and C-S-F9) close an agent via `cj/ai-vterm-close'."
(interactive "P")
(pcase (cj/--ai-vterm-dispatch)
(`(toggle-off . ,win)
@@ -728,9 +724,75 @@ defined in `modules/ai-config.el')."
(`(pick-project)
(cj/ai-vterm-pick-project arg))))
-(keymap-global-set "<f9>" #'cj/ai-vterm)
-(keymap-global-set "C-<f9>" #'cj/ai-vterm-pick-project)
-(keymap-global-set "M-<f9>" #'cj/toggle-gptel)
+;; ----------------------------- Close an agent --------------------------------
+
+(defun cj/--ai-vterm-kill-tmux-session (session)
+ "Kill the tmux SESSION via `tmux kill-session -t SESSION'.
+
+Returns the process exit status (0 on success), or nil when tmux is
+unavailable or already gone -- a session that no longer exists is not
+an error worth surfacing, since the goal is just to make sure it's
+down."
+ (condition-case nil
+ (process-file "tmux" nil nil nil "kill-session" "-t" session)
+ (error nil)))
+
+(defun cj/--ai-vterm-close-buffer (buffer)
+ "Gracefully tear down AI-vterm BUFFER: tmux session, window, buffer.
+
+Derives the tmux session name from BUFFER's `default-directory' (the
+project dir the vterm 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-still-running prompt -- the session is already down). No-op
+when BUFFER isn't an AI-vterm buffer."
+ (when (cj/--ai-vterm-buffer-p buffer)
+ (cj/--ai-vterm-kill-tmux-session
+ (cj/--ai-vterm-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)))
+ (let ((kill-buffer-query-functions nil))
+ (kill-buffer buffer))))
+
+(defun cj/--ai-vterm-close-target ()
+ "Return the AI-vterm buffer `cj/ai-vterm-close' should act on, or nil.
+
+The current buffer when it is an agent buffer; else the sole live
+agent buffer; else a `completing-read' choice among the live agent
+buffers; nil when none are alive."
+ (cond
+ ((cj/--ai-vterm-buffer-p (current-buffer)) (current-buffer))
+ (t (let ((buffers (cj/--ai-vterm-agent-buffers)))
+ (cond
+ ((null buffers) nil)
+ ((null (cdr buffers)) (car buffers))
+ (t (get-buffer
+ (completing-read "Close AI vterm: "
+ (mapcar #'buffer-name buffers) nil t))))))))
+
+(defun cj/ai-vterm-close ()
+ "Gracefully close an AI-vterm agent: kill its tmux session and buffer.
+
+Targets the current agent buffer, the sole live agent, or prompts when
+several are alive (see `cj/--ai-vterm-close-target'). Asks for
+confirmation first -- this kills the running agent process, which can
+interrupt work in progress. Bound to M-<f9> (primary) and C-S-<f9>."
+ (interactive)
+ (let ((buffer (cj/--ai-vterm-close-target)))
+ (unless buffer
+ (user-error "No AI-vterm agent buffers to close"))
+ (let ((name (buffer-name buffer)))
+ (when (y-or-n-p (format "Close agent %s? This kills its tmux session. "
+ name))
+ (cj/--ai-vterm-close-buffer buffer)
+ (message "Closed agent %s." name)))))
+
+(keymap-global-set "<f9>" #'cj/ai-vterm)
+(keymap-global-set "C-<f9>" #'cj/ai-vterm-pick-project)
+(keymap-global-set "M-<f9>" #'cj/ai-vterm-close)
+(keymap-global-set "C-S-<f9>" #'cj/ai-vterm-close)
;; vterm binds <f1>..<f12> to `vterm--self-insert', so a plain <f9> typed
;; while point is inside an agent buffer gets sent to the terminal program
@@ -739,9 +801,10 @@ defined in `modules/ai-config.el')."
;; the toggle reaches Emacs from there too. (C-<f9> / M-<f9> aren't in vterm's
;; intercept set, but bind them here as well so the behaviour is uniform.)
(with-eval-after-load 'vterm
- (keymap-set vterm-mode-map "<f9>" #'cj/ai-vterm)
- (keymap-set vterm-mode-map "C-<f9>" #'cj/ai-vterm-pick-project)
- (keymap-set vterm-mode-map "M-<f9>" #'cj/toggle-gptel))
+ (keymap-set vterm-mode-map "<f9>" #'cj/ai-vterm)
+ (keymap-set vterm-mode-map "C-<f9>" #'cj/ai-vterm-pick-project)
+ (keymap-set vterm-mode-map "M-<f9>" #'cj/ai-vterm-close)
+ (keymap-set vterm-mode-map "C-S-<f9>" #'cj/ai-vterm-close))
;; ---------- emacsclient: keep opened files off the agent vterm ----------
;;