diff options
| -rw-r--r-- | modules/ai-term.el | 7 | ||||
| -rw-r--r-- | modules/undead-buffers.el | 29 | ||||
| -rw-r--r-- | tests/test-undead-buffers--buffer-undead-p.el | 52 |
3 files changed, 85 insertions, 3 deletions
diff --git a/modules/ai-term.el b/modules/ai-term.el index ff240b9bf..6dfb669a9 100644 --- a/modules/ai-term.el +++ b/modules/ai-term.el @@ -30,6 +30,7 @@ (require 'keybindings) ;; provides cj/register-prefix-map (C-; a) (declare-function eat "eat" (&optional program arg)) +(declare-function cj/make-buffer-pattern-undead "undead-buffers") (defvar eat-buffer-name) (defvar eat-semi-char-mode-map) @@ -516,6 +517,12 @@ repeated capture/replay drifts the dock height a couple rows per cycle." (add-hook 'window-configuration-change-hook #'cj/--ai-term-track-geometry) +;; Agent buffers ("agent [<project>]") are buried, not killed, by the +;; kill-all sweep (F1 / `cj/dashboard-only'). Register the family pattern so +;; every agent -- however and whenever created -- survives with its session. +(with-eval-after-load 'undead-buffers + (cj/make-buffer-pattern-undead "\\`agent \\[")) + (defun cj/--ai-term-reuse-existing-agent (buffer _alist) "Display-buffer action: reuse any window in this frame already showing an agent buffer. diff --git a/modules/undead-buffers.el b/modules/undead-buffers.el index fe43575e9..4780ef227 100644 --- a/modules/undead-buffers.el +++ b/modules/undead-buffers.el @@ -32,7 +32,13 @@ (defvar cj/undead-buffer-list '("*scratch*" "*EMMS-Playlist*" "*Messages*" "*ert*" "*AI-Assistant*") - "Buffers to bury instead of killing.") + "Buffer names to bury instead of killing (exact match).") + +(defvar cj/undead-buffer-regexps nil + "Regexps for buffer names to bury instead of killing, alongside +`cj/undead-buffer-list'. Use for dynamically-named buffer families where an +exact name can't be pre-listed -- e.g. ai-term agents, named \"agent [<project>]\". +Register one with `cj/make-buffer-pattern-undead'.") (defun cj/make-buffer-undead (name) "Append NAME to `cj/undead-buffer-list' if not present. @@ -41,6 +47,23 @@ Signal an error if NAME is not a non-empty string. Return the updated list." (error "cj/bury-alive-add: NAME must be a non-empty string")) (add-to-list 'cj/undead-buffer-list name t)) +(defun cj/make-buffer-pattern-undead (regexp) + "Append REGEXP to `cj/undead-buffer-regexps' if not present. +A buffer whose name matches REGEXP is buried instead of killed. Signal an +error if REGEXP is not a non-empty string. Return the updated list." + (unless (and (stringp regexp) (> (length regexp) 0)) + (error "cj/make-buffer-pattern-undead: REGEXP must be a non-empty string")) + (add-to-list 'cj/undead-buffer-regexps regexp t)) + +(defun cj/--buffer-undead-p (name) + "Return non-nil when buffer NAME should be buried instead of killed. +NAME is undead when it is in `cj/undead-buffer-list' (exact) or matches any +regexp in `cj/undead-buffer-regexps'." + (and (stringp name) + (or (member name cj/undead-buffer-list) + (seq-some (lambda (re) (string-match-p re name)) + cj/undead-buffer-regexps)))) + (defun cj/kill-buffer-or-bury-alive (buffer) "Kill BUFFER or bury it if it's in `cj/undead-buffer-list'." (interactive "bBuffer to kill or bury: ") @@ -49,7 +72,7 @@ Signal an error if NAME is not a non-empty string. Return the updated list." (progn (add-to-list 'cj/undead-buffer-list (buffer-name)) (message "Added %s to bury-alive-list" (buffer-name))) - (if (member (buffer-name) cj/undead-buffer-list) + (if (cj/--buffer-undead-p (buffer-name)) (bury-buffer) (kill-buffer))))) (keymap-global-set "<remap> <kill-buffer>" #'cj/kill-buffer-or-bury-alive) @@ -60,7 +83,7 @@ Undead-buffers are buffers in `cj/undead-buffer-list'." (let* ((buf (current-buffer)) (name (buffer-name buf))) (and - (not (member name cj/undead-buffer-list)) + (not (cj/--buffer-undead-p name)) (buffer-file-name buf) (buffer-modified-p buf)))) diff --git a/tests/test-undead-buffers--buffer-undead-p.el b/tests/test-undead-buffers--buffer-undead-p.el new file mode 100644 index 000000000..e196e41a9 --- /dev/null +++ b/tests/test-undead-buffers--buffer-undead-p.el @@ -0,0 +1,52 @@ +;;; test-undead-buffers--buffer-undead-p.el --- undead predicate (name + regexp) -*- lexical-binding: t; -*- + +;;; Commentary: +;; `cj/--buffer-undead-p' decides whether a buffer name is buried instead of +;; killed. A name is undead when it is in `cj/undead-buffer-list' (exact) or +;; matches any regexp in `cj/undead-buffer-regexps' (dynamic families like the +;; ai-term agent buffers, named "agent [<project>]"). `cj/make-buffer-pattern-undead' +;; registers a regexp. + +;;; Code: + +(require 'ert) +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'undead-buffers) + +(ert-deftest test-undead-buffer-undead-p-name-list () + "Normal: a name in the exact list is undead; others are not." + (let ((cj/undead-buffer-list '("*scratch*" "*Messages*")) + (cj/undead-buffer-regexps nil)) + (should (cj/--buffer-undead-p "*scratch*")) + (should (cj/--buffer-undead-p "*Messages*")) + (should-not (cj/--buffer-undead-p "other")))) + +(ert-deftest test-undead-buffer-undead-p-regexp () + "Normal: a name matching a regexp is undead; the agent pattern is anchored." + (let ((cj/undead-buffer-list nil) + (cj/undead-buffer-regexps '("\\`agent \\["))) + (should (cj/--buffer-undead-p "agent [rulesets]")) + (should (cj/--buffer-undead-p "agent [.emacs.d]")) + (should-not (cj/--buffer-undead-p "not an agent")) + (should-not (cj/--buffer-undead-p "my agent [x]")))) ; anchored: must start with "agent [" + +(ert-deftest test-undead-buffer-undead-p-neither () + "Boundary/Error: a name in neither, an empty string, and a non-string are not undead." + (let ((cj/undead-buffer-list '("*scratch*")) + (cj/undead-buffer-regexps '("\\`agent \\["))) + (should-not (cj/--buffer-undead-p "random")) + (should-not (cj/--buffer-undead-p "")) + (should-not (cj/--buffer-undead-p nil)))) + +(ert-deftest test-undead-make-buffer-pattern-undead-adds-and-rejects () + "Normal/Error: registering a regexp makes matching names undead; a blank or +non-string regexp signals." + (let ((cj/undead-buffer-regexps nil)) + (cj/make-buffer-pattern-undead "\\`agent \\[") + (should (member "\\`agent \\[" cj/undead-buffer-regexps)) + (should (cj/--buffer-undead-p "agent [x]")) + (should-error (cj/make-buffer-pattern-undead "")) + (should-error (cj/make-buffer-pattern-undead 42)))) + +(provide 'test-undead-buffers--buffer-undead-p) +;;; test-undead-buffers--buffer-undead-p.el ends here |
