Subject: Re: bug#54296: Add buffer-matching functionality

From: Philip Kaludercic <[email-masked]>
Date: Thu, 10 Mar 2022 12:13:59 +0000

Eli Zaretskii <[email-masked]> writes:

>> From: Philip Kaludercic <[email-masked]>
>> Date: Thu, 10 Mar 2022 10:05:04 +0000
>> Cc: [email-masked]
>> 
>> +(defun buffer-match (condition buffer-or-name &optional arg)
>> +  "Return non-nil if BUFFER-OR-NAME matches CONDITION.
>
> This is a predicate function, so its name should be buffer-matches-p
> or maybe buffer-matches-condition-p.

I like buffer-matches-p.

>> +CONDITION is is either:
>              ^^^^^
> Typo.

Sorry about those, it and the other ones here:

>From 8dfcabb8c655f06c8c7285c6da7efad7057b636e Mon Sep 17 00:00:00 2001
From: Philip Kaludercic <[email-masked]>
Date: Mon, 7 Mar 2022 20:49:42 +0100
Subject: [PATCH 1/2] 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 | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/lisp/subr.el b/lisp/subr.el
index 2321765f95..e55a4c5802 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -6613,4 +6613,68 @@ delete-line
                    (forward-line 1)
                    (point))))
 
+(defun buffer-match-p (condition buffer-or-name &optional arg)
+  "Return non-nil if BUFFER-OR-NAME matches CONDITION.
+CONDITION is either:
+- a regular expression, to match a buffer name,
+- a predicate function that takes a buffer object and ARG as
+  arguments, and returns non-nil if the buffer matches,
+- a cons-cell, where the car describes how to interpret the cdr.
+  The car can be one of the following:
+  * `major-mode': the buffer matches if the buffer's major
+    mode is eq to the cons-cell's cdr
+  * `derived-mode': the buffer matches 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 conditions, 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
+      ((buffer (get-buffer buffer-or-name))
+       (match
+        (lambda (conditions)
+          (catch 'match
+            (dolist (condition conditions)
+              (when (cond
+                     ((stringp condition)
+                      (string-match-p condition (buffer-name buffer)))
+                     ((functionp condition)
+                      (if (eq 1 (cdr (func-arity condition)))
+                          (funcall condition buffer)
+                        (funcall condition buffer arg)))
+                     ((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 arg)
+  "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.
+ARG is passed to `buffer-match', for predicate conditions in
+CONDITION."
+  (let (bufs)
+    (dolist (buf (or buffers (buffer-list)))
+      (when (buffer-match-p condition (get-buffer buf) arg)
+        (push buf bufs)))
+    bufs))
+
 ;;; subr.el ends here
-- 
2.34.0

>> +- a cons-cell, where the car describes how to interpret the cdr.
>> +  The car can be one of the following:
>> +  * `major-mode': the buffer matches if the buffer's major
>> +    mode is eq to the cons-cell's cdr
>> +  * `derived-mode': the buffer matches if the buffer's major
>> +    mode is derived from the major mode denoted by the cons-cell's
>> +    cdr
>
> Do we really need both major-mode and derived-mode?

It seems to have been useful in project.el, see
`project-kill-buffer-conditions'.  In that case you want to both be able
to say something like "kill buffers only if they are in
fundamental-mode", but also something like "kill all buffers that are
based on comint-mode".

-- 
	Philip Kaludercic