Subject: bug#54296: Add buffer-matching functionality

From: Dmitry Gutov <[email-masked]>
Date: Fri, 17 Jun 2022 04:21:04 +0300

On 16.06.2022 08:51, Eli Zaretskii wrote:
>> Date: Thu, 16 Jun 2022 03:47:15 +0300
>> Cc: [email-masked], [email-masked], [email-masked]
>> From: Dmitry Gutov <[email-masked]>
>>
>>> Can you show a patch?
>>
>> Please look at the patch attached to the very first message in this bug
>> report.
>>
>> https://debbugs.gnu.org/cgi/bugreport.cgi?att=1;bug=54296;msg=5;filename=0001-Generalise-buffer-matching-from-project.el.patch
>>
>> The function name has changed after discussion, but the docstring is
>> still relevant, and it mirrors 'project-kill-buffer-conditions'.
> 
> Other things changed as well, even in the doc string, and not just in
> name changes.  Going back to the original patch makes little sense to
> me.  So I'd still prefer to see an actual patch, before I tell whether
> I'm okay with it.
> 
> In any case, if we will keep both major-mode and derived-mode
> conditions in subr.el, let's at least document that derived-mode
> should be preferred where it can do the job, and major-mode used only
> where derived-mode will not DTRT.

Sure. Here you go.

The meat of the change is intermixed here with documentation fixes (and 
the addition of support for lambdas in project-kill-buffer-conditions), 
but they should be easy enough to tell apart.

diff --git a/doc/lispref/buffers.texi b/doc/lispref/buffers.texi
index 1cbe8bc093..25e8e96d88 100644
--- a/doc/lispref/buffers.texi
+++ b/doc/lispref/buffers.texi
@@ -981,13 +981,18 @@ Buffer List
 Satisfied if @var{expr} doesn't satisfy @code{buffer-match-p} with
 the same buffer and @code{arg}.
 @item or
-Satisfied if @var{oper} is a list and @emph{any} condition if
+Satisfied if @var{expr} is a list and @emph{any} condition in
 @var{expr} satisfies @code{buffer-match-p}, with the same buffer and
 @code{arg}.
 @item and
-Satisfied if @var{oper} is a list and @emph{all} condition if
-@var{expr} satisfies @code{buffer-match-p}, with the same buffer and
+Satisfied if @var{expr} is a list and @emph{all} conditions in
+@var{expr} satisfy @code{buffer-match-p}, with the same buffer and
 @code{arg}.
+@item derived-mode
+Satisfied if the buffer's major mode derives from @var{expr}.
+@item major-mode
+Satisfied if the buffer's major mode is eq to @var{expr}.  Prefer
+using @code{derived-mode} instead when both can work.
 @end table
 @end itemize
 @end defun
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index f4d6742ed8..30f51704dc 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -1221,22 +1221,18 @@ project-display-buffer-other-frame
   (display-buffer-other-frame buffer-or-name))
 
 (defcustom project-kill-buffer-conditions
-  `(buffer-file-name    ; All file-visiting buffers are included.
+  '(buffer-file-name    ; All file-visiting buffers are included.
     ;; Most of the temp buffers in the background:
-    ,(lambda (buf)
-       (not (eq (buffer-local-value 'major-mode buf)
-                'fundamental-mode)))
+    (major-mode . fundamental-mode)
     ;; non-text buffer such as xref, occur, vc, log, ...
-    (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))
+    (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))
   "List of conditions to kill buffers related to a project.
 This list is used by `project-kill-buffers'.
 Each condition is either:
@@ -1246,11 +1242,9 @@ 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 derived from the major mode denoted by the cons-cell's
-    cdr.
+    mode is eq to the cons-cell's 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).
+    mode is derived from the major mode in 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.
@@ -1308,15 +1302,12 @@ project--buffer-check
       (when (cond
              ((stringp c)
               (string-match-p c (buffer-name buf)))
-             ((symbolp c)
+             ((functionp c)
               (funcall c buf))
-             ((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)
+              (eq (buffer-local-value 'major-mode buf)
+                  (cdr c)))
+             ((eq (car-safe c) 'derived-mode)
               (provided-mode-derived-p
                (buffer-local-value 'major-mode buf)
                (cdr c)))
diff --git a/lisp/subr.el b/lisp/subr.el
index 50ae357a13..c1c9759b03 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -6855,9 +6855,11 @@ buffer-match-p
   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
+  * `derived-mode': the buffer matches if the buffer's major mode
+    is derived from the major mode in the cons-cell's cdr.
+  * `major-mode': the buffer matches if the buffer's major mode
+    is eq to the cons-cell's cdr.  Prefer using `derived-mode'
+    instead when both can work.
   * `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.
@@ -6877,6 +6879,10 @@ buffer-match-p
                           (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)))