Subject: bug#54296: Add buffer-matching functionality

From: Philip Kaludercic <[email-masked]>
Date: Thu, 10 Mar 2022 10:05:04 +0000

Lars Ingebrigtsen <[email-masked]> writes:

> Philip Kaludercic <[email-masked]> writes:
>
>> 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).
>
> Hm...  how would this be used with display-buffer-alist, then?  (And
> perhaps Martin has some comments; added to the CCs.)

See below.

>> 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.
>
> I think I'd prefer to have the parameters reversed -- the condition is a
> kind of predicate, and these days we seem to prefer to have the
> predicate first.

I agree, it makes more sense.

>> +(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,
>
> Killed?

That was a typo from copying the docstring.  Here is the updated patch:

>From 6714a4dc7168e6806dba0707c9c0b80d3365b2c5 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..adcd25ec14 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -6613,4 +6613,68 @@ delete-line
                    (forward-line 1)
                    (point))))
 
+(defun buffer-match (condition buffer-or-name &optional arg)
+  "Return non-nil if BUFFER-OR-NAME matches CONDITION.
+CONDITION is 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 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
+      ((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 condition (get-buffer buf) arg)
+        (push buf bufs)))
+    bufs))
+
 ;;; subr.el ends here
-- 
2.34.0

martin rudalics <[email-masked]> writes:

>>> 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).
>>
>> Hm...  how would this be used with display-buffer-alist, then?  (And
>> perhaps Martin has some comments; added to the CCs.)
>
> Either add an optional second argument to 'buffer-match' or add to
> 'display-buffer-alist' an entry that uses a function as condition that
> calls 'buffer-match' with the first argument and returns non-nil if
> 'buffer-match' reports a match.  I see no issue here.

This seems to work, and updating window.el was also pretty trivial
(unless I have missed something):

>From fadea32d0dc74952ca70f8d98ced59616c0e3e1a Mon Sep 17 00:00:00 2001
From: Philip Kaludercic <[email-masked]>
Date: Thu, 10 Mar 2022 10:59:52 +0100
Subject: [PATCH 2/2] * window.el (display-buffer-assq-regexp): Use
 buffer-match

---
 lisp/window.el | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/lisp/window.el b/lisp/window.el
index 54c9eee5f3..4225019920 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -7497,19 +7497,12 @@ display-buffer-fallback-action
 (defun display-buffer-assq-regexp (buffer-name alist action)
   "Retrieve ALIST entry corresponding to BUFFER-NAME.
 This returns the cdr of the alist entry ALIST if either its key
-is a string that matches BUFFER-NAME, as reported by
-`string-match-p'; or if the key is a function that returns
-non-nil when called with three arguments: the ALIST key,
-BUFFER-NAME and ACTION.  ACTION should have the form of the
-action argument passed to `display-buffer'."
+satisfied a BUFFER-NAME per `buffer-match'.  ACTION should have
+the form of the action argument passed to `display-buffer'."
   (catch 'match
     (dolist (entry alist)
-      (let ((key (car entry)))
-	(when (or (and (stringp key)
-		       (string-match-p key buffer-name))
-		  (and (functionp key)
-		       (funcall key buffer-name action)))
-	  (throw 'match (cdr entry)))))))
+      (when (buffer-match (car entry) buffer-name action)
+        (throw 'match (cdr entry))))))
 
 (defvar display-buffer--same-window-action
   '(display-buffer-same-window
-- 
2.34.0

> martin

-- 
	Philip Kaludercic