GNU bug report logs - #38424
[PATCH] Add new filter functions to Package Menu

Previous Next

Package: emacs;

Reported by: Stefan Kangas <stefan <at> marxist.se>

Date: Fri, 29 Nov 2019 12:32:01 UTC

Severity: normal

Tags: patch

Fixed in version 28.1

Done: Stefan Kangas <stefan <at> marxist.se>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 38424 in the body.
You can then email your comments to 38424 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Fri, 29 Nov 2019 12:32:01 GMT) Full text and rfc822 format available.

Acknowledgement sent to Stefan Kangas <stefan <at> marxist.se>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Fri, 29 Nov 2019 12:32:01 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Stefan Kangas <stefan <at> marxist.se>
To: bug-gnu-emacs <at> gnu.org
Subject: [PATCH] Add new filter functions to Package Menu
Date: Fri, 29 Nov 2019 13:31:10 +0100
[Message part 1 (text/plain, inline)]
The attached patches adds new commands to filter the "*Packages*"
buffer by version, status and archive.  (The first patch only adds new
version list comparison predicates, something I needed to simplify the
second patch.)

I meant to sent this earlier, but got too busy with real life.  I hope
that it's not too late to make it into Emacs 27.

Best regards,
Stefan Kangas
[0001-Add-version-comparison-predicates-for-and.patch (text/x-patch, attachment)]
[0002-Add-new-filter-functions-to-Package-Menu.patch (text/x-patch, attachment)]

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Sat, 30 Nov 2019 12:19:01 GMT) Full text and rfc822 format available.

Message #8 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Stefan Kangas <stefan <at> marxist.se>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Sat, 30 Nov 2019 14:18:12 +0200
> From: Stefan Kangas <stefan <at> marxist.se>
> Date: Fri, 29 Nov 2019 13:31:10 +0100
> 
> The attached patches adds new commands to filter the "*Packages*"
> buffer by version, status and archive.  (The first patch only adds new
> version list comparison predicates, something I needed to simplify the
> second patch.)

We deliberately didn't define the functions you are now adding, since
they are just one 'not' away.  Do they really simplify the callers so
much that we now want to add them?

> * doc/emacs/package.texi (Package Menu): Document it.

This tells nothing about the changes which aren't "documenting it".
(And, btw, what is "it" here is not clear at all.)

> -        (when (or (eq packages t) (memq name packages))
> +        (when (or (not packages) (memq name packages))
>            (dolist (pkg (cdr elt))
>              (when (package--has-keyword-p pkg keywords)
>                (push pkg info-list))))))
> @@ -2950,7 +2958,7 @@ package-menu--refresh
>            (when (and (package--has-keyword-p pkg keywords)
>                       (or package-list-unversioned
>                           (package--bi-desc-version (cdr elt)))
> -                     (or (eq packages t) (memq name packages)))
> +                     (or (not packages) (memq name packages)))
>              (push pkg info-list)))))
>  
>      ;; Available and disabled packages:
> @@ -2959,7 +2967,7 @@ package-menu--refresh
>      (dolist (elt package-archive-contents)
>        (let ((name (car elt)))
>          ;; To be displayed it must be in PACKAGES;
> -        (when (and (or (eq packages t) (memq name packages))
> +        (when (and (or (not packages) (memq name packages))

Does the above mean you are suggesting a backward-incompatible API
change?

> +Arguments PACKAGES and KEYWORDS are like `package-menu--refresh'."

Arguments cannot be "like" a function.  Suggest to say "like in
`package-menu--refresh'" instead.

I don't use package.el, so I'd like someone who does or knows the code
well to review the patch.

Thanks.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Sun, 01 Dec 2019 11:14:01 GMT) Full text and rfc822 format available.

Message #11 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Stefan Kangas <stefan <at> marxist.se>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Sun, 01 Dec 2019 12:12:58 +0100
Eli Zaretskii <eliz <at> gnu.org> writes:

>> From: Stefan Kangas <stefan <at> marxist.se>
>> Date: Fri, 29 Nov 2019 13:31:10 +0100
>> 
>> The attached patches adds new commands to filter the "*Packages*"
>> buffer by version, status and archive.  (The first patch only adds new
>> version list comparison predicates, something I needed to simplify the
>> second patch.)
>
> We deliberately didn't define the functions you are now adding, since
> they are just one 'not' away.  Do they really simplify the callers so
> much that we now want to add them?

I don't know, but I'm also not sure I understand the benefit of not
adding them.

In this case, it let me do this:

+       (let ((fun (cond ((eq predicate '=) 'version-list-=)
+                        ((eq predicate '<) 'version-list-<)
+                        ((eq predicate '>) 'version-list->)

I could of course use a lambda here instead, but it makes the code a
bit uglier.  Let me know if that's preferable.

>> * doc/emacs/package.texi (Package Menu): Document it.
>
> This tells nothing about the changes which aren't "documenting it".
> (And, btw, what is "it" here is not clear at all.)

Thanks.  I thought that was how we usually wrote.

Is "Document the new commands." better?

>> -        (when (or (eq packages t) (memq name packages))
>> +        (when (or (not packages) (memq name packages))
>>            (dolist (pkg (cdr elt))
>>              (when (package--has-keyword-p pkg keywords)
>>                (push pkg info-list))))))
>> @@ -2950,7 +2958,7 @@ package-menu--refresh
>>            (when (and (package--has-keyword-p pkg keywords)
>>                       (or package-list-unversioned
>>                           (package--bi-desc-version (cdr elt)))
>> -                     (or (eq packages t) (memq name packages)))
>> +                     (or (not packages) (memq name packages)))
>>              (push pkg info-list)))))
>>  
>>      ;; Available and disabled packages:
>> @@ -2959,7 +2967,7 @@ package-menu--refresh
>>      (dolist (elt package-archive-contents)
>>        (let ((name (car elt)))
>>          ;; To be displayed it must be in PACKAGES;
>> -        (when (and (or (eq packages t) (memq name packages))
>> +        (when (and (or (not packages) (memq name packages))
>
> Does the above mean you are suggesting a backward-incompatible API
> change?

AFAIU, this does not change the API since this is an internal
function.

>> +Arguments PACKAGES and KEYWORDS are like `package-menu--refresh'."
>
> Arguments cannot be "like" a function.  Suggest to say "like in
> `package-menu--refresh'" instead.

Right, I'll fix that.

> I don't use package.el, so I'd like someone who does or knows the code
> well to review the patch.

I'll wait for that review.  Thank you for taking a look.

Best regards,
Stefan Kangas




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Sun, 01 Dec 2019 11:17:02 GMT) Full text and rfc822 format available.

Message #14 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Stefan Kangas <stefan <at> marxist.se>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Sun, 01 Dec 2019 12:16:43 +0100
Stefan Kangas <stefan <at> marxist.se> writes:

>>> * doc/emacs/package.texi (Package Menu): Document it.
>>
>> This tells nothing about the changes which aren't "documenting it".
>> (And, btw, what is "it" here is not clear at all.)
>
> Thanks.  I thought that was how we usually wrote.
>
> Is "Document the new commands." better?

Wait, do you mean to also comment on moving the old commands around?
If so, I'd suggest the following: "Document the new commands and move
the fitering commands to follow a better sorting order."

Does that sound better?

Best regards,
Stefan Kangas




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Sun, 01 Dec 2019 18:12:02 GMT) Full text and rfc822 format available.

Message #17 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Stefan Kangas <stefan <at> marxist.se>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Sun, 01 Dec 2019 20:11:22 +0200
> From: Stefan Kangas <stefan <at> marxist.se>
> Cc: 38424 <at> debbugs.gnu.org
> Date: Sun, 01 Dec 2019 12:12:58 +0100
> 
> > We deliberately didn't define the functions you are now adding, since
> > they are just one 'not' away.  Do they really simplify the callers so
> > much that we now want to add them?
> 
> I don't know, but I'm also not sure I understand the benefit of not
> adding them.

The same reason why we don't have string-greater-p: keep Emacs from
becoming larger without a good reason.

> In this case, it let me do this:
> 
> +       (let ((fun (cond ((eq predicate '=) 'version-list-=)
> +                        ((eq predicate '<) 'version-list-<)
> +                        ((eq predicate '>) 'version-list->)
> 
> I could of course use a lambda here instead, but it makes the code a
> bit uglier.

Or you could use a defsubst or an inline function.

> >> * doc/emacs/package.texi (Package Menu): Document it.
> >
> > This tells nothing about the changes which aren't "documenting it".
> > (And, btw, what is "it" here is not clear at all.)
> 
> Thanks.  I thought that was how we usually wrote.

We do, but in a different situation.  Like this:

 * lisp/foo.el (foo-bar-quux-func): New function.
 * doc/lispref/foo-docs.texi (Foo Bar): Document it.

In this case, it's immediately clear what "it" refers to.  But in your
case:

 * lisp/emacs-lisp/package.el (package-menu-filter-by-version)
 (package-menu-filter-by-status, package-menu-filter-by-archive):
 New filter functions.
 (package-menu--filter-by): New helper function.
 (package-menu-filter-by-keyword, package-menu-filter-by-name): Use
 above helper function.
 (package-menu-mode-menu):
 (package-menu-mode-map): Update menu to include new filter functions.
 * doc/emacs/package.texi (Package Menu): Document it.
 * etc/NEWS: Announce it.

it is much less clear, because there are many "its" above.

> >> -        (when (or (eq packages t) (memq name packages))
> >> +        (when (or (not packages) (memq name packages))
> >>            (dolist (pkg (cdr elt))
> >>              (when (package--has-keyword-p pkg keywords)
> >>                (push pkg info-list))))))
> >> @@ -2950,7 +2958,7 @@ package-menu--refresh
> >>            (when (and (package--has-keyword-p pkg keywords)
> >>                       (or package-list-unversioned
> >>                           (package--bi-desc-version (cdr elt)))
> >> -                     (or (eq packages t) (memq name packages)))
> >> +                     (or (not packages) (memq name packages)))
> >>              (push pkg info-list)))))
> >>  
> >>      ;; Available and disabled packages:
> >> @@ -2959,7 +2967,7 @@ package-menu--refresh
> >>      (dolist (elt package-archive-contents)
> >>        (let ((name (car elt)))
> >>          ;; To be displayed it must be in PACKAGES;
> >> -        (when (and (or (eq packages t) (memq name packages))
> >> +        (when (and (or (not packages) (memq name packages))
> >
> > Does the above mean you are suggesting a backward-incompatible API
> > change?
> 
> AFAIU, this does not change the API since this is an internal
> function.

But doesn't it change the API of that internal function in
incompatible ways?  If it does, was that really necessary?




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Sun, 01 Dec 2019 18:13:01 GMT) Full text and rfc822 format available.

Message #20 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Stefan Kangas <stefan <at> marxist.se>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Sun, 01 Dec 2019 20:12:29 +0200
> From: Stefan Kangas <stefan <at> marxist.se>
> Cc: 38424 <at> debbugs.gnu.org
> Date: Sun, 01 Dec 2019 12:16:43 +0100
> 
> Stefan Kangas <stefan <at> marxist.se> writes:
> 
> >>> * doc/emacs/package.texi (Package Menu): Document it.
> >>
> >> This tells nothing about the changes which aren't "documenting it".
> >> (And, btw, what is "it" here is not clear at all.)
> >
> > Thanks.  I thought that was how we usually wrote.
> >
> > Is "Document the new commands." better?
> 
> Wait, do you mean to also comment on moving the old commands around?

Yes.

> If so, I'd suggest the following: "Document the new commands and move
> the fitering commands to follow a better sorting order."

Much better, thanks.  Bonus points for saying what sorting order is
that, to make your motivation clear.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Sat, 25 Jan 2020 01:39:02 GMT) Full text and rfc822 format available.

Message #23 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Stefan Kangas <stefan <at> marxist.se>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Sat, 25 Jan 2020 02:37:57 +0100
[Message part 1 (text/plain, inline)]
Eli Zaretskii <eliz <at> gnu.org> writes:

>> > We deliberately didn't define the functions you are now adding, since
>> > they are just one 'not' away.  Do they really simplify the callers so
>> > much that we now want to add them?
>> 
>> I don't know, but I'm also not sure I understand the benefit of not
>> adding them.
>
> The same reason why we don't have string-greater-p: keep Emacs from
> becoming larger without a good reason.

I've rewritten the patch to not add such functions.

>> > This tells nothing about the changes which aren't "documenting it".
>> > (And, btw, what is "it" here is not clear at all.)

Fixed.

> But doesn't it change the API of that internal function in
> incompatible ways?  If it does, was that really necessary?

I've changed the patch to no longer do that change.  The change is in
any case orthogonal to what I'm trying to achieve here and better to
make separately.  (I might later suggest that change in a separate
patch.)

Please find attached an updated patch.  I hope it's more
straightforward to review.

Best regards,
Stefan Kangas

[0001-Add-new-filter-functions-to-Package-Menu.patch (text/x-diff, inline)]
From 69c048a68cfe9cce14b6aa141bea770187496aec Mon Sep 17 00:00:00 2001
From: Stefan Kangas <stefankangas <at> gmail.com>
Date: Tue, 5 Nov 2019 15:48:48 +0100
Subject: [PATCH] Add new filter functions to Package Menu

* lisp/emacs-lisp/package.el (package-menu-filter-by-version)
(package-menu-filter-by-status, package-menu-filter-by-archive):
New filter functions.
(package-menu--filter-by): New helper function.
(package-menu-filter-by-keyword, package-menu-filter-by-name): Use
the above helper function.
(package-menu-mode-menu):
(package-menu-mode-map): Update menu to include new filter functions.
* doc/emacs/package.texi (Package Menu): Document the new commands and
re-arrange the sort order of commands to be closer to the one in
describe-major-mode.
* etc/NEWS: Announce the new commands.

* lisp/emacs-lisp/package.el (package-menu--display): New function
extracted from....
(package-menu--generate): ...here.

* test/lisp/emacs-lisp/package-tests.el (with-package-menu-test):
New macro.
(package-test-update-listing, package-test-list-filter-by-name)
(package-test-list-filter-clear): Use above macro.
(package-test-list-filter-by-archive)
(package-test-list-filter-by-keyword)
(package-test-list-filter-by-status)
(package-test-list-filter-by-version-=)
(package-test-list-filter-by-version-<)
(package-test-list-filter-by-version->): New tests.
(package-test-filter-by-version): New helper function.
---
 doc/emacs/package.texi                |  63 +++++---
 etc/NEWS                              |  15 ++
 lisp/emacs-lisp/package.el            | 219 ++++++++++++++++++++------
 test/lisp/emacs-lisp/package-tests.el | 117 ++++++++++----
 4 files changed, 320 insertions(+), 94 deletions(-)

diff --git a/doc/emacs/package.texi b/doc/emacs/package.texi
index 1cac7f9b4b..360fc980e4 100644
--- a/doc/emacs/package.texi
+++ b/doc/emacs/package.texi
@@ -151,27 +151,6 @@ Package Menu
 list of available packages from the package archive again, and
 redisplays the package list.
 
-@item / k
-@kindex / k @r{(Package Menu)}
-@findex package-menu-filter-by-keyword
-Filter the package list by keyword
-(@code{package-menu-filter-by-keyword}).  This prompts for a keyword
-(e.g., @samp{games}), then shows only the packages that relate to that
-keyword.
-
-@item / n
-@kindex / n @r{(Package Menu)}
-@findex package-menu-filter-by-name
-Filter the package list by name (@code{package-menu-filter-by-name}).
-This prompts for a string, then shows only the packages whose names
-match a regexp with that value.
-
-@item / /
-@kindex / / @r{(Package Menu)}
-@findex package-menu-clear-filter
-Clear filter currently applied to the package list
-(@code{package-menu-clear-filter}).
-
 @item H
 @kindex H @r{(Package Menu)}
 @findex package-menu-hide-package
@@ -183,6 +162,48 @@ Package Menu
 @findex package-menu-toggle-hiding
 Toggle visibility of old versions of packages and also of versions
 from lower-priority archives (@code{package-menu-toggle-hiding}).
+
+@item / a
+@kindex / a @r{(Package Menu)}
+@findex package-menu-filter-by-archive
+Filter package list by archive (@code{package-menu-filter-by-archive}).
+This prompts for a package archive (e.g., @samp{gnu}), then shows only
+packages from that archive.
+
+@item / k
+@kindex / k @r{(Package Menu)}
+@findex package-menu-filter-by-keyword
+Filter package list by keyword (@code{package-menu-filter-by-keyword}).
+This prompts for a keyword (e.g., @samp{games}), then shows only
+packages with that keyword.
+
+@item / n
+@kindex / n @r{(Package Menu)}
+@findex package-menu-filter-by-name
+Filter package list by name (@code{package-menu-filter-by-name}).
+This prompts for a regular expression, then shows only packages
+with names matching that regexp.
+
+@item / s
+@kindex / s @r{(Package Menu)}
+@findex package-menu-filter-by-status
+Filter package list by status (@code{package-menu-filter-by-status}).
+This prompts for one or more statuses (e.g., @samp{available}), then
+shows only packages with matching status.
+
+@item / v
+@kindex / v @r{(Package Menu)}
+@findex package-menu-filter-by-version
+Filter package list by version (@code{package-menu-filter-by-version}).
+This prompts first for one of the qualifiers @samp{<}, @samp{>} or
+@samp{=}, and then a package version, and shows packages that has a
+lower, equal or higher version than the one specified.
+
+@item / /
+@kindex / / @r{(Package Menu)}
+@findex package-menu-filter-clear
+Clear filter currently applied to the package list
+(@code{package-menu-filter-clear}).
 @end table
 
 @noindent
diff --git a/etc/NEWS b/etc/NEWS
index 5395f2ccfb..f9cdfbcca9 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -103,6 +103,21 @@ supplied error message.
 *** New connection method "media", which allows accessing media devices
 like cell phones, tablets or cameras.
 
+** Package
+
++++
+*** New functions to filter the package list.
+The filter command key bindings are as follows:
+
+key             binding
+---             -------
+/ a             package-menu-filter-by-archive
+/ k             package-menu-filter-by-keyword
+/ n             package-menu-filter-by-name
+/ s             package-menu-filter-by-status
+/ v             package-menu-filter-by-version
+/ /             package-menu-filter-clear
+
 
 * New Modes and Packages in Emacs 28.1
 
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index a9508c1bdc..f14ef7919e 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -2679,15 +2679,18 @@ package-menu-mode-map
     (define-key map "i" 'package-menu-mark-install)
     (define-key map "U" 'package-menu-mark-upgrades)
     (define-key map "r" 'revert-buffer)
-    (define-key map (kbd "/ k") 'package-menu-filter-by-keyword)
-    (define-key map (kbd "/ n") 'package-menu-filter-by-name)
-    (define-key map (kbd "/ /") 'package-menu-clear-filter)
     (define-key map "~" 'package-menu-mark-obsolete-for-deletion)
     (define-key map "x" 'package-menu-execute)
     (define-key map "h" 'package-menu-quick-help)
     (define-key map "H" #'package-menu-hide-package)
     (define-key map "?" 'package-menu-describe-package)
     (define-key map "(" #'package-menu-toggle-hiding)
+    (define-key map (kbd "/ /") 'package-menu-clear-filter)
+    (define-key map (kbd "/ a") 'package-menu-filter-by-archive)
+    (define-key map (kbd "/ k") 'package-menu-filter-by-keyword)
+    (define-key map (kbd "/ n") 'package-menu-filter-by-name)
+    (define-key map (kbd "/ s") 'package-menu-filter-by-status)
+    (define-key map (kbd "/ v") 'package-menu-filter-by-version)
     map)
   "Local keymap for `package-menu-mode' buffers.")
 
@@ -2714,8 +2717,11 @@ package-menu-mode-menu
 
     "--"
     ("Filter Packages"
+     ["Filter by Archive" package-menu-filter-by-archive :help "Filter packages by archive"]
      ["Filter by Keyword" package-menu-filter-by-keyword :help "Filter packages by keyword"]
      ["Filter by Name" package-menu-filter-by-name :help "Filter packages by name"]
+     ["Filter by Status" package-menu-filter-by-status :help "Filter packages by status"]
+     ["Filter by Version" package-menu-filter-by-version :help "Filter packages by version"]
      ["Clear Filter" package-menu-clear-filter :help "Clear package list filter"])
 
     ["Hide by Regexp" package-menu-hide-package :help "Permanently hide all packages matching a regexp"]
@@ -3021,22 +3027,31 @@ package--has-keyword-p
         found)
     t))
 
-(defun package-menu--generate (remember-pos packages &optional keywords)
-  "Populate the Package Menu.
+(defun package-menu--display (remember-pos suffix)
+  "Display the Package Menu.
 If REMEMBER-POS is non-nil, keep point on the same entry.
+
+If SUFFIX is non-nil, append that to \"Package\" for the first
+column in the header line."
+  (setf (car (aref tabulated-list-format 0))
+        (if suffix
+            (concat "Package[" suffix "]")
+          "Package"))
+  (tabulated-list-init-header)
+  (tabulated-list-print remember-pos))
+
+(defun package-menu--generate (remember-pos &optional packages keywords)
+  "Populate and display the Package Menu.
 PACKAGES should be t, which means to display all known packages,
 or a list of package names (symbols) to display.
 
 With KEYWORDS given, only packages with those keywords are
 shown."
   (package-menu--refresh packages keywords)
-  (setf (car (aref tabulated-list-format 0))
-        (if keywords
-            (let ((filters (mapconcat #'identity keywords ",")))
-              (concat "Package[" filters "]"))
-          "Package"))
-  (tabulated-list-init-header)
-  (tabulated-list-print remember-pos))
+  (package-menu--display remember-pos
+                  (when keywords
+                    (let ((filters (mapconcat #'identity keywords ",")))
+                      (concat "Package[" filters "]")))))
 
 (defun package-menu--print-info (pkg)
   "Return a package entry suitable for `tabulated-list-entries'.
@@ -3673,45 +3688,160 @@ package-show-package-list
         (select-window win)
       (switch-to-buffer buf))))
 
+(defun package-menu--filter-by (predicate suffix)
+  "Filter \"*Packages*\" buffer by PREDICATE and add SUFFIX to header.
+PREDICATE is a function which will be called with one argument, a
+`package-desc' object, and returns t if that object should be
+listed in the Package Menu.
+
+SUFFIX is passed on to `package-menu--display' and is added to
+the header line of the first column."
+  ;; Update `tabulated-list-entries' so that it contains all
+  ;; packages before searching.
+  (package-menu--refresh t nil)
+  (let (found-entries)
+    (dolist (entry tabulated-list-entries)
+      (when (funcall predicate (car entry))
+        (push entry found-entries)))
+    (if found-entries
+        (progn
+          (setq tabulated-list-entries found-entries)
+          (package-menu--display t suffix))
+      (user-error "No packages found"))))
+
+(defun package-menu-filter-by-archive (archive)
+  "Filter the \"*Packages*\" buffer by ARCHIVE.
+Display only packages from package archive ARCHIVE.
+
+When called interactively, prompt for ARCHIVE, which can be a
+comma-separated string.  If ARCHIVE is empty, show all packages.
+
+When called from Lisp, ARCHIVE can be a string or a list of
+strings.  If ARCHIVE is nil or the empty string, show all
+packages."
+  (interactive (list (completing-read-multiple
+                      "Filter by archive (comma separated): "
+                      (mapcar #'car package-archives))))
+  (package--ensure-package-menu-mode)
+  (let ((re (if (listp archive)
+                (regexp-opt archive)
+              archive)))
+    (package-menu--filter-by (lambda (pkg-desc)
+                        (let ((pkg-archive (package-desc-archive pkg-desc)))
+                          (and pkg-archive
+                               (string-match-p re pkg-archive))))
+                      (concat "archive:" (if (listp archive)
+                                             (string-join archive ",")
+                                           archive)))))
+
 (defun package-menu-filter-by-keyword (keyword)
   "Filter the \"*Packages*\" buffer by KEYWORD.
-Show only those items that relate to the specified KEYWORD.
-
-KEYWORD can be a string or a list of strings.  If it is a list, a
-package will be displayed if it matches any of the keywords.
-Interactively, it is a list of strings separated by commas.
-
-KEYWORD can also be used to filter by status or archive name by
-using keywords like \"arc:gnu\" and \"status:available\".
-Statuses available include \"incompat\", \"available\",
-\"built-in\" and \"installed\"."
-  (interactive
-   (list (completing-read-multiple
-          "Keywords (comma separated): " (package-all-keywords))))
+Display only packages with specified KEYWORD.
+
+When called interactively, prompt for KEYWORD, which can be a
+comma-separated string.  If KEYWORD is empty, show all packages.
+
+When called from Lisp, KEYWORD can be a string or a list of
+strings.  If KEYWORD is nil or the empty string, show all
+packages."
+  (interactive (list (completing-read-multiple
+                      "Keywords (comma separated): "
+                      (package-all-keywords))))
+  (when (stringp keyword)
+    (setq keyword (list keyword)))
   (package--ensure-package-menu-mode)
-  (package-show-package-list t (if (stringp keyword)
-                                   (list keyword)
-                                 keyword)))
+  (if (not keyword)
+      (package-menu--generate t t)
+    (package-menu--filter-by (lambda (pkg-desc)
+                        (package--has-keyword-p pkg-desc keyword))
+                      (concat "keyword:" (string-join keyword ",")))))
 
 (defun package-menu-filter-by-name (name)
-  "Filter the \"*Packages*\" buffer by NAME.
-Show only those items whose name matches the regular expression
-NAME.  If NAME is nil or the empty string, show all packages."
-  (interactive (list (read-from-minibuffer "Filter by name (regexp): ")))
+  "Filter the \"*Packages*\" buffer by NAME regexp.
+Display only packages with name that matches regexp NAME.
+
+When called interactively, prompt for NAME.
+
+If NAME is nil or the empty string, show all packages."
+  (interactive (list (read-regexp "Filter by name (regexp)")))
   (package--ensure-package-menu-mode)
   (if (or (not name) (string-empty-p name))
-      (package-show-package-list t nil)
-    ;; Update `tabulated-list-entries' so that it contains all
-    ;; packages before searching.
-    (package-menu--refresh t nil)
-    (let (matched)
-      (dolist (entry tabulated-list-entries)
-        (let* ((pkg-name (package-desc-name (car entry))))
-          (when (string-match name (symbol-name pkg-name))
-            (push pkg-name matched))))
-      (if matched
-          (package-show-package-list matched nil)
-        (user-error "No packages found")))))
+      (package-menu--generate t t)
+    (package-menu--filter-by (lambda (pkg-desc)
+                        (string-match-p name (symbol-name
+                                              (package-desc-name pkg-desc))))
+                      (format "name:%s" name))))
+
+(defun package-menu-filter-by-status (status)
+  "Filter the \"*Packages*\" buffer by STATUS.
+Display only packages with specified STATUS.
+
+When called interactively, prompt for STATUS, which can be a
+comma-separated string.  If STATUS is empty, show all packages.
+
+When called from Lisp, STATUS can be a string or a list of
+strings.  If STATUS is nil or the empty string, show all
+packages."
+  (interactive (list (completing-read "Filter by status: "
+                                      '("avail-obso"
+                                        "available"
+                                        "built-in"
+                                        "dependency"
+                                        "disabled"
+                                        "external"
+                                        "held"
+                                        "incompat"
+                                        "installed"
+                                        "new"
+                                        "unsigned"))))
+  (package--ensure-package-menu-mode)
+  (if (or (not status) (string-empty-p status))
+      (package-menu--generate t t)
+    (package-menu--filter-by (lambda (pkg-desc)
+                        (string-match-p status (package-desc-status pkg-desc)))
+                      (format "status:%s" status))))
+
+(defun package-menu-filter-by-version (version predicate)
+  "Filter the \"*Packages*\" buffer by VERSION and PREDICATE.
+Display only packages with a matching version.
+
+When called interactively, prompt for one of the qualifiers `<',
+`>' or `=', and a package version.  Show only packages that has a
+lower (`<'), equal (`=') or higher (`>') version than the
+specified one.
+
+When called from Lisp, VERSION should be a version string and
+PREDICATE should be the symbol `=', `<' or `>'.
+
+If VERSION is nil or the empty string, show all packages."
+  (interactive (let ((choice (intern
+                              (char-to-string
+                               (read-char-choice
+                                "Filter by version? [Type =, <, > or q] "
+                                '(?< ?> ?= ?q))))))
+                 (if (eq choice 'q)
+                     '(quit nil)
+                   (list (read-from-minibuffer
+                          (concat "Filter by version ("
+                                  (pcase choice
+                                    ('= "= equal to")
+                                    ('< "< less than")
+                                    ('> "> greater than"))
+                                  "): "))
+                         choice))))
+  (unless (equal predicate 'quit)
+    (if (or (not version) (string-empty-p version))
+        (package-menu--generate t t)
+      (package-menu--filter-by
+       (let ((fun (pcase predicate
+                    ('= 'version-list-=)
+                    ('< 'version-list-<)
+                    ('> '(lambda (a b) (not (version-list-<= a b))))
+                    (_ (error "Unknown predicate: %s" predicate))))
+             (ver (version-to-list version)))
+         (lambda (pkg-desc)
+           (funcall fun (package-desc-version pkg-desc) ver)))
+       (format "versions:%s%s" predicate version)))))
 
 (defun package-menu-clear-filter ()
   "Clear any filter currently applied to the \"*Packages*\" buffer."
@@ -3760,6 +3890,7 @@ package-get-version
             (or (lm-header "package-version")
                 (lm-header "version")))))))))
 
+
 ;;;; Quickstart: precompute activation actions for faster start up.
 
 ;; Activating packages via `package-initialize' is costly: for N installed
diff --git a/test/lisp/emacs-lisp/package-tests.el b/test/lisp/emacs-lisp/package-tests.el
index 7d354d6ecd..adf917aef4 100644
--- a/test/lisp/emacs-lisp/package-tests.el
+++ b/test/lisp/emacs-lisp/package-tests.el
@@ -349,43 +349,102 @@ package-test-install-multifile
           (goto-char (point-min))
           (should (re-search-forward re nil t)))))))
 
+
+;;; Package Menu tests
+
+(defmacro with-package-menu-test (&rest body)
+  "Set up Package Menu (\"*Packages*\") buffer for testing."
+  (declare (indent 0) (debug (([&rest form]) body)))
+  `(with-package-test ()
+     (let ((buf (package-list-packages)))
+       (unwind-protect
+           (progn ,@body)
+         (kill-buffer buf)))))
+
 (ert-deftest package-test-update-listing ()
   "Ensure installed package status is updated."
-  (with-package-test ()
-    (let ((buf (package-list-packages)))
-      (search-forward-regexp "^ +simple-single")
-      (package-menu-mark-install)
-      (package-menu-execute)
-      (run-hooks 'post-command-hook)
-      (should (package-installed-p 'simple-single))
-      (switch-to-buffer "*Packages*")
-      (goto-char (point-min))
-      (should (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+installed" nil t))
-      (goto-char (point-min))
-      (should-not (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+\\(available\\|new\\)" nil t))
-      (kill-buffer buf))))
+  (with-package-menu-test
+    (search-forward-regexp "^ +simple-single")
+    (package-menu-mark-install)
+    (package-menu-execute)
+    (run-hooks 'post-command-hook)
+    (should (package-installed-p 'simple-single))
+    (switch-to-buffer "*Packages*")
+    (goto-char (point-min))
+    (should (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+installed" nil t))
+    (goto-char (point-min))
+    (should-not (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+\\(available\\|new\\)" nil t))))
+
+(ert-deftest package-test-list-filter-by-archive ()
+  "Ensure package list is filtered correctly by archive version."
+  (with-package-menu-test
+    ;; TODO: Add another package archive to test filtering, because
+    ;;       the testing environment currently only has one.
+    (package-menu-filter-by-archive "gnu")
+    (goto-char (point-min))
+    (should (looking-at "^\\s-+multi-file"))
+    (should (= (count-lines (point-min) (point-max)) 4))
+    (should-error (package-menu-filter-by-archive "non-existent archive"))))
+
+(ert-deftest package-test-list-filter-by-keyword ()
+  "Ensure package list is filtered correctly by package keyword."
+  (with-package-menu-test
+    (package-menu-filter-by-keyword "frobnicate")
+    (goto-char (point-min))
+    (should (re-search-forward "^\\s-+simple-single" nil t))
+    (should (= (count-lines (point-min) (point-max)) 1))
+    (should-error (package-menu-filter-by-keyword "non-existent-keyword"))))
 
 (ert-deftest package-test-list-filter-by-name ()
   "Ensure package list is filtered correctly by package name."
-  (with-package-test ()
-    (let ((buf (package-list-packages)))
-      (package-menu-filter-by-name "tetris")
-      (goto-char (point-min))
-      (should (re-search-forward "^\\s-+tetris" nil t))
-      (should (= (count-lines (point-min) (point-max)) 1))
-      (kill-buffer buf))))
+  (with-package-menu-test ()
+    (package-menu-filter-by-name "tetris")
+    (goto-char (point-min))
+    (should (re-search-forward "^\\s-+tetris" nil t))
+    (should (= (count-lines (point-min) (point-max)) 1))))
+
+(ert-deftest package-test-list-filter-by-status ()
+  "Ensure package list is filtered correctly by package status."
+  (with-package-menu-test
+    (package-menu-filter-by-status "available")
+    (goto-char (point-min))
+    (should (re-search-forward "^\\s-+multi-file" nil t))
+    (should (= (count-lines (point-min) (point-max)) 4))
+    ;; No installed packages in default environment.
+    (should-error (package-menu-filter-by-status "installed"))))
+
+(ert-deftest package-test-list-filter-by-version ()
+  (with-package-menu-test
+    (should-error (package-menu-filter-by-version "1.1" 'unknown-symbol)))  )
+
+(defun package-test-filter-by-version (version predicate name)
+  (with-package-menu-test
+    (package-menu-filter-by-version version predicate)
+    (goto-char (point-min))
+    ;; We just check that the given package is included in the
+    ;; listing.  One could be more ambitious.
+    (should (re-search-forward name))))
+
+(ert-deftest package-test-list-filter-by-version-= ()
+  "Ensure package list is filtered correctly by package version (=)."
+  (package-test-filter-by-version "1.1" '= "^\\s-+simple-two-depend"))
+
+(ert-deftest package-test-list-filter-by-version-< ()
+  "Ensure package list is filtered correctly by package version (<)."
+  (package-test-filter-by-version "1.2" '< "^\\s-+simple-two-depend"))
+
+(ert-deftest package-test-list-filter-by-version-> ()
+  "Ensure package list is filtered correctly by package version (>)."
+  (package-test-filter-by-version "1.0" '> "^\\s-+simple-two-depend"))
 
 (ert-deftest package-test-list-clear-filter ()
   "Ensure package list filter is cleared correctly."
-  (with-package-test ()
-    (let ((buf (package-list-packages)))
-      (let ((num-packages (count-lines (point-min) (point-max))))
-        (should (> num-packages 1))
-        (package-menu-filter-by-name "tetris")
-        (should (= (count-lines (point-min) (point-max)) 1))
-        (package-menu-clear-filter)
-        (should (= (count-lines (point-min) (point-max)) num-packages)))
-      (kill-buffer buf))))
+  (with-package-menu-test
+    (let ((num-packages (count-lines (point-min) (point-max))))
+      (package-menu-filter-by-name "tetris")
+      (should (= (count-lines (point-min) (point-max)) 1))
+      (package-menu-clear-filter)
+      (should (= (count-lines (point-min) (point-max)) num-packages)))))
 
 (ert-deftest package-test-update-archives ()
   "Test updating package archives."
-- 
2.20.1


Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Fri, 31 Jan 2020 13:56:02 GMT) Full text and rfc822 format available.

Message #26 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Stefan Kangas <stefan <at> marxist.se>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Fri, 31 Jan 2020 15:54:40 +0200
> From: Stefan Kangas <stefan <at> marxist.se>
> Cc: 38424 <at> debbugs.gnu.org
> Date: Sat, 25 Jan 2020 02:37:57 +0100
> 
> Please find attached an updated patch.  I hope it's more
> straightforward to review.

Thanks, since no one of those who use package.el and know more than I
do about its internals voice any objections, I guess this is good to
go.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#38424; Package emacs. (Wed, 05 Feb 2020 12:37:02 GMT) Full text and rfc822 format available.

Message #29 received at 38424 <at> debbugs.gnu.org (full text, mbox):

From: Stefan Kangas <stefan <at> marxist.se>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: 38424 <at> debbugs.gnu.org
Subject: Re: bug#38424: [PATCH] Add new filter functions to Package Menu
Date: Wed, 05 Feb 2020 13:36:32 +0100
close 38424 28.1
thanks

Eli Zaretskii <eliz <at> gnu.org> writes:

> Thanks, since no one of those who use package.el and know more than I
> do about its internals voice any objections, I guess this is good to
> go.

Thanks.  Pushed to master as commit aea12d4903.

Best regards,
Stefan Kangas




bug marked as fixed in version 28.1, send any further explanations to 38424 <at> debbugs.gnu.org and Stefan Kangas <stefan <at> marxist.se> Request was from Stefan Kangas <stefan <at> marxist.se> to control <at> debbugs.gnu.org. (Wed, 05 Feb 2020 12:37:02 GMT) Full text and rfc822 format available.

bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Thu, 05 Mar 2020 12:24:04 GMT) Full text and rfc822 format available.

This bug report was last modified 4 years and 51 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.