Subject: Add buffer-matching functionality
From: Philip Kaludercic <[email-masked]>
To: [email-masked]
Date: Mon, 07 Mar 2022 22:33:29 +0000
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

Project.el currently has a small language for matching buffers, used by
project-kill-buffers and project--read-project-buffer.  As mentioned in
[0], this could be generalised, as done in the patch below.  As to what
file this should be added to, should be discussed.

Either way I would consider these functions useful and would have wanted
to use them in my own code many times before.  While difficult, it might
also be useful for things like display-buffer-alist (the issue is that
a function as a condition in display-buffer-alist has to accept two
arguments, while the proposed patch only takes one).

To match functions such as string-match, the argument of buffer-match
could be reversed so that the function can be used as a testfn to
assoc/alist-get.

<tangent>
The reason this was not immediately done when project-kill-buffers was
implemented, was that this would raise the "emacs" dependency of the
ELPA package "project" to the latest release or even the current
development version.

To solve issues like these, I have been working on "compat", a
yet-unreleased library added to GNU ELPA a while back that could be
added as a dependency to project.  That way newer functions, such as the
ones propose below could be used, without breaking ELPA compatibility.

To make this work properly in the near future, compat would have to
follow the upstream development, before a release is made.  If there is
any interest in this kind of an arrangement, I could start a thread on
emacs-devel to discuss the details.
</tangent>

[0] https://mail.gnu.org/archive/html/emacs-devel/2020-09/msg00082.html
[1] https://elpa.gnu.org/devel/compat.html

In GNU Emacs 29.0.50 (build 13, x86_64-pc-linux-gnu, GTK+ Version 3.24.30, cairo version 1.16.0)
 of 2022-02-24 built on viero
Repository revision: bd17fa2c7565f180cedbfa396c0b159e144178cb
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12011000
From f268509ba1b617b65851231c5ae262f3b169cd6c Mon Sep 17 00:00:00 2001
From: Philip Kaludercic <[email-masked]>
Date: Mon, 7 Mar 2022 20:49:42 +0100
Subject: [PATCH] Generalise buffer matching from project.el

* subr.el (buffer-match): Add function to check if a buffer satisfies
a condition.
(match-buffers): Returns all buffers that satisfy a condition.
---
 lisp/subr.el | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/lisp/subr.el b/lisp/subr.el
index eb9af0b36d..1d0c9cf967 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -6613,4 +6613,63 @@ delete-line
                    (forward-line 1)
                    (point))))
 
+(defun buffer-match (buffer condition)
+  "Return non-nil if BUFFER matches CONDITION.
+CONDITION is is either:
+- a regular expression, to match a buffer name,
+- a predicate function that takes a buffer object as argument
+  and returns non-nil if the buffer should be killed,
+- a cons-cell, where the car describes how to interpret the cdr.
+  The car can be one of the following:
+  * `major-mode': the buffer is killed if the buffer's major
+    mode is eq to the cons-cell's cdr
+  * `derived-mode': the buffer is killed if the buffer's major
+    mode is derived from the major mode denoted by the cons-cell's
+    cdr
+  * `not': the cdr is interpreted as a negation of a condition.
+  * `and': the cdr is a list of recursive condition, that all have
+    to be met.
+  * `or': the cdr is a list of recursive condition, of which at
+    least one has to be met."
+  (letrec
+      ((match
+        (lambda (conditions)
+          (catch 'match
+            (dolist (condition conditions)
+              (when (cond
+                     ((stringp condition)
+                      (string-match-p condition (buffer-name buffer)))
+                     ((symbolp condition)
+                      (funcall condition buffer))
+                     ((eq (car-safe condition) 'major-mode)
+                      (eq (buffer-local-value 'major-mode buffer)
+                          (cdr condition)))
+                     ((eq (car-safe condition) 'derived-mode)
+                      (provided-mode-derived-p
+                       (buffer-local-value 'major-mode buffer)
+                       (cdr condition)))
+                     ((eq (car-safe condition) 'not)
+                      (not (funcall match (cdr condition))))
+                     ((eq (car-safe condition) 'or)
+                      (funcall match (cdr condition)))
+                     ((eq (car-safe condition) 'and)
+                      (catch 'fail
+                        (dolist (c conditions)
+                          (unless (funcall match c)
+                            (throw 'fail nil)))
+                        t)))
+                (throw 'match t)))))))
+    (funcall match (list condition))))
+
+(defun match-buffers (condition &optional buffers)
+  "Return a list of buffers that match CONDITION.
+See `buffer-match' for details on CONDITION.  By default all
+buffers are checked, this can be restricted by passing an
+optional argument BUFFERS, set to a list of buffers to check."
+  (let (bufs)
+    (dolist (buf (or buffers (buffer-list)))
+      (when (buffer-match buf condition)
+        (push buf bufs)))
+    bufs))
+
 ;;; subr.el ends here
-- 
2.34.0

-- 
	Philip Kaludercic