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

From: Philip Kaludercic <[email-masked]>
Date: Thu, 14 Apr 2022 08:25:58 +0000

Eli Zaretskii <[email-masked]> writes:

>> From: Philip Kaludercic <[email-masked]>
>> Cc: [email-masked],  [email-masked]
>> Date: Mon, 14 Mar 2022 08:21:56 +0000
>> 
>> Eli Zaretskii <[email-masked]> writes:
>> 
>> >> From: Philip Kaludercic <[email-masked]>
>> >> Cc: [email-masked],  [email-masked]
>> >> Date: Sun, 13 Mar 2022 20:40:49 +0000
>> >> 
>> >> Eli Zaretskii <[email-masked]> writes:
>> >> 
>> >> >> From: Philip Kaludercic <[email-masked]>
>> >> >> Cc: [email-masked],  [email-masked]
>> >> >> Date: Fri, 11 Mar 2022 16:21:06 +0000
>> >> >> 
>> >> >> > I think we want in general avoid comparison with major-mode, and
>> >> >> > prefer derived-mode instead, and if so, IMO we had better
>> >> >> > did as we
>> >> >> > say and not exposed comparison to major mode unless we
>> >> >> > absolutely
>> >> >> > must.
>> >> >> 
>> >> >> Would it be enough to clarify this point in the documentation
>> >> >> string?
>> >> >
>> >> > What would you like to clarify?
>> >> 
>> >> To clarify that the usage of (major-mode . foo-mode) might not be
>> >> what
>> >> the user intends, and that in most cases derived-mode is preferable.
>> >
>> > I suggested to "clarify" that by not providing the 'major-mode'
>> > predicate at all.  I still don't think I understand why it is so
>> > important that we should provide a special case for it.
>> 
>> It is not inherently important, it is just that if the predicate would
>> also be used in project.el, then compatibility would have to be broken,
>> as the distinction between `major-mode' and `derived-mode' exists there.
>
> Then project.el could use the predicate route, right?  It's quite a
> special case, AFAIU, so having a special solution is OK.

I have updated the commits as you recommended, and add a commit
deprecating the use of `derived-mode' in project.el.

-- 
	Philip Kaludercic

>From f218d19837ce53e5b94d16356594a5f48bd8f3d1 Mon Sep 17 00:00:00 2001
From: Philip Kaludercic <[email-masked]>
Date: Mon, 7 Mar 2022 20:49:42 +0100
Subject: [PATCH 1/3] 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 e7d5d36461..d0b73db019 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -6651,4 +6651,63 @@ string-lines
 If OMIT-NULLS, empty lines will be removed from the results."
   (split-string string "\n" omit-nulls))
 
+(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 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)
+                      (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

>From b6e03a6d4dda8db47405475675f2defb5b8520d9 Mon Sep 17 00:00:00 2001
From: Philip Kaludercic <[email-masked]>
Date: Thu, 10 Mar 2022 10:59:52 +0100
Subject: [PATCH 2/3] * 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 dd297a3169..2da2f8bb2c 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -7498,19 +7498,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-p (car entry) buffer-name action)
+        (throw 'match (cdr entry))))))
 
 (defvar display-buffer--same-window-action
   '(display-buffer-same-window
-- 
2.34.0

>From 1f2e73890f378ee4a25a6bcd2683f922115a4a58 Mon Sep 17 00:00:00 2001
From: Philip Kaludercic <[email-masked]>
Date: Thu, 14 Apr 2022 10:24:27 +0200
Subject: [PATCH 3/3] Update project-kill-buffer-conditions to match
 buffer-match-p

* project.el (project-kill-buffer-conditions): Document the
deprecation of the use of derived-mode
(project--buffer-check): Have `major-mode' behave like `derived-mode'
did previously, and issue a warning of `derived-mode' is used.
---
 lisp/progmodes/project.el | 38 +++++++++++++++++++++++---------------
 1 file changed, 23 insertions(+), 15 deletions(-)

diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index daaf86f327..1d04c19906 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -1203,16 +1203,20 @@ project-display-buffer-other-frame
 (defcustom project-kill-buffer-conditions
   '(buffer-file-name    ; All file-visiting buffers are included.
     ;; Most of the temp buffers in the background:
-    (major-mode . fundamental-mode)
+    (lambda (buf)
+      (not (eq (buffer-local-value 'major-mode buf)
+               'fundamental-mode)))
     ;; non-text buffer such as xref, occur, vc, log, ...
-    (and (derived-mode . special-mode)
-         (not (major-mode . help-mode)))
-    (derived-mode . compilation-mode)
-    (derived-mode . dired-mode)
-    (derived-mode . diff-mode)
-    (derived-mode . comint-mode)
-    (derived-mode . eshell-mode)
-    (derived-mode . change-log-mode))
+    (and (major-mode . special-mode)
+         (lambda (buf)
+           (not (eq (buffer-local-value 'major-mode buf)
+                    'help-mode))))
+    (major-mode . compilation-mode)
+    (major-mode . dired-mode)
+    (major-mode . diff-mode)
+    (major-mode . comint-mode)
+    (major-mode . eshell-mode)
+    (major-mode . change-log-mode))
   "List of conditions to kill buffers related to a project.
 This list is used by `project-kill-buffers'.
 Each condition is either:
@@ -1222,10 +1226,11 @@ project-kill-buffer-conditions
 - 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
+    cdr.
+  * `derived-mode': the buffer is killed if the buffer's major
+    mode is eq to the cons-cell's cdr (this is deprecated and will
+    result in a warning if used).
   * `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.
@@ -1285,10 +1290,13 @@ project--buffer-check
               (string-match-p c (buffer-name buf)))
              ((symbolp c)
               (funcall c buf))
-             ((eq (car-safe c) 'major-mode)
-              (eq (buffer-local-value 'major-mode buf)
-                  (cdr c)))
              ((eq (car-safe c) 'derived-mode)
+              (warn "The use of `derived-mode' in \
+`project--buffer-check' is deprecated.")
+              (provided-mode-derived-p
+               (buffer-local-value 'major-mode buf)
+               (cdr c)))
+             ((eq (car-safe c) 'major-mode)
               (provided-mode-derived-p
                (buffer-local-value 'major-mode buf)
                (cdr c)))
-- 
2.34.0