diff options
| author | Stefan Kangas | 2020-02-05 13:12:01 +0100 |
|---|---|---|
| committer | Stefan Kangas | 2020-02-05 13:18:12 +0100 |
| commit | aea12d4903136c057bb14d3fd7683bf7a4e1eff6 (patch) | |
| tree | 183e578c697d5b95191df9384f714c999f84a7e7 | |
| parent | 196da3017bc9b2fc6fecff0c0ce560e6c46b8a72 (diff) | |
| download | emacs-aea12d4903136c057bb14d3fd7683bf7a4e1eff6.tar.gz emacs-aea12d4903136c057bb14d3fd7683bf7a4e1eff6.zip | |
Add new filter commands to Package Menu (Bug#38424)
* lisp/emacs-lisp/package.el (package-menu-filter-by-version)
(package-menu-filter-by-status, package-menu-filter-by-archive):
New filter commands.
(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 commands.
* 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.
| -rw-r--r-- | doc/emacs/package.texi | 63 | ||||
| -rw-r--r-- | etc/NEWS | 14 | ||||
| -rw-r--r-- | lisp/emacs-lisp/package.el | 219 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/package-tests.el | 117 |
4 files changed, 319 insertions, 94 deletions
diff --git a/doc/emacs/package.texi b/doc/emacs/package.texi index 1cac7f9b4b6..360fc980e4a 100644 --- a/doc/emacs/package.texi +++ b/doc/emacs/package.texi | |||
| @@ -151,27 +151,6 @@ Refresh the package list (@code{revert-buffer}). This fetches the | |||
| 151 | list of available packages from the package archive again, and | 151 | list of available packages from the package archive again, and |
| 152 | redisplays the package list. | 152 | redisplays the package list. |
| 153 | 153 | ||
| 154 | @item / k | ||
| 155 | @kindex / k @r{(Package Menu)} | ||
| 156 | @findex package-menu-filter-by-keyword | ||
| 157 | Filter the package list by keyword | ||
| 158 | (@code{package-menu-filter-by-keyword}). This prompts for a keyword | ||
| 159 | (e.g., @samp{games}), then shows only the packages that relate to that | ||
| 160 | keyword. | ||
| 161 | |||
| 162 | @item / n | ||
| 163 | @kindex / n @r{(Package Menu)} | ||
| 164 | @findex package-menu-filter-by-name | ||
| 165 | Filter the package list by name (@code{package-menu-filter-by-name}). | ||
| 166 | This prompts for a string, then shows only the packages whose names | ||
| 167 | match a regexp with that value. | ||
| 168 | |||
| 169 | @item / / | ||
| 170 | @kindex / / @r{(Package Menu)} | ||
| 171 | @findex package-menu-clear-filter | ||
| 172 | Clear filter currently applied to the package list | ||
| 173 | (@code{package-menu-clear-filter}). | ||
| 174 | |||
| 175 | @item H | 154 | @item H |
| 176 | @kindex H @r{(Package Menu)} | 155 | @kindex H @r{(Package Menu)} |
| 177 | @findex package-menu-hide-package | 156 | @findex package-menu-hide-package |
| @@ -183,6 +162,48 @@ Permanently hide packages that match a regexp | |||
| 183 | @findex package-menu-toggle-hiding | 162 | @findex package-menu-toggle-hiding |
| 184 | Toggle visibility of old versions of packages and also of versions | 163 | Toggle visibility of old versions of packages and also of versions |
| 185 | from lower-priority archives (@code{package-menu-toggle-hiding}). | 164 | from lower-priority archives (@code{package-menu-toggle-hiding}). |
| 165 | |||
| 166 | @item / a | ||
| 167 | @kindex / a @r{(Package Menu)} | ||
| 168 | @findex package-menu-filter-by-archive | ||
| 169 | Filter package list by archive (@code{package-menu-filter-by-archive}). | ||
| 170 | This prompts for a package archive (e.g., @samp{gnu}), then shows only | ||
| 171 | packages from that archive. | ||
| 172 | |||
| 173 | @item / k | ||
| 174 | @kindex / k @r{(Package Menu)} | ||
| 175 | @findex package-menu-filter-by-keyword | ||
| 176 | Filter package list by keyword (@code{package-menu-filter-by-keyword}). | ||
| 177 | This prompts for a keyword (e.g., @samp{games}), then shows only | ||
| 178 | packages with that keyword. | ||
| 179 | |||
| 180 | @item / n | ||
| 181 | @kindex / n @r{(Package Menu)} | ||
| 182 | @findex package-menu-filter-by-name | ||
| 183 | Filter package list by name (@code{package-menu-filter-by-name}). | ||
| 184 | This prompts for a regular expression, then shows only packages | ||
| 185 | with names matching that regexp. | ||
| 186 | |||
| 187 | @item / s | ||
| 188 | @kindex / s @r{(Package Menu)} | ||
| 189 | @findex package-menu-filter-by-status | ||
| 190 | Filter package list by status (@code{package-menu-filter-by-status}). | ||
| 191 | This prompts for one or more statuses (e.g., @samp{available}), then | ||
| 192 | shows only packages with matching status. | ||
| 193 | |||
| 194 | @item / v | ||
| 195 | @kindex / v @r{(Package Menu)} | ||
| 196 | @findex package-menu-filter-by-version | ||
| 197 | Filter package list by version (@code{package-menu-filter-by-version}). | ||
| 198 | This prompts first for one of the qualifiers @samp{<}, @samp{>} or | ||
| 199 | @samp{=}, and then a package version, and shows packages that has a | ||
| 200 | lower, equal or higher version than the one specified. | ||
| 201 | |||
| 202 | @item / / | ||
| 203 | @kindex / / @r{(Package Menu)} | ||
| 204 | @findex package-menu-filter-clear | ||
| 205 | Clear filter currently applied to the package list | ||
| 206 | (@code{package-menu-filter-clear}). | ||
| 186 | @end table | 207 | @end table |
| 187 | 208 | ||
| 188 | @noindent | 209 | @noindent |
| @@ -120,6 +120,20 @@ like cell phones, tablets or cameras. | |||
| 120 | *** Pcase 'map' pattern added keyword symbols abbreviation. | 120 | *** Pcase 'map' pattern added keyword symbols abbreviation. |
| 121 | A pattern like '(map :sym)' binds the map's value for ':sym' to 'sym', | 121 | A pattern like '(map :sym)' binds the map's value for ':sym' to 'sym', |
| 122 | equivalent to '(map (:sym sym))'. | 122 | equivalent to '(map (:sym sym))'. |
| 123 | ** Package | ||
| 124 | |||
| 125 | +++ | ||
| 126 | *** New functions to filter the package list. | ||
| 127 | The filter command key bindings are as follows: | ||
| 128 | |||
| 129 | key binding | ||
| 130 | --- ------- | ||
| 131 | / a package-menu-filter-by-archive | ||
| 132 | / k package-menu-filter-by-keyword | ||
| 133 | / n package-menu-filter-by-name | ||
| 134 | / s package-menu-filter-by-status | ||
| 135 | / v package-menu-filter-by-version | ||
| 136 | / / package-menu-filter-clear | ||
| 123 | 137 | ||
| 124 | 138 | ||
| 125 | * New Modes and Packages in Emacs 28.1 | 139 | * New Modes and Packages in Emacs 28.1 |
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el index a9508c1bdc5..f14ef7919ea 100644 --- a/lisp/emacs-lisp/package.el +++ b/lisp/emacs-lisp/package.el | |||
| @@ -2679,15 +2679,18 @@ either a full name or nil, and EMAIL is a valid email address." | |||
| 2679 | (define-key map "i" 'package-menu-mark-install) | 2679 | (define-key map "i" 'package-menu-mark-install) |
| 2680 | (define-key map "U" 'package-menu-mark-upgrades) | 2680 | (define-key map "U" 'package-menu-mark-upgrades) |
| 2681 | (define-key map "r" 'revert-buffer) | 2681 | (define-key map "r" 'revert-buffer) |
| 2682 | (define-key map (kbd "/ k") 'package-menu-filter-by-keyword) | ||
| 2683 | (define-key map (kbd "/ n") 'package-menu-filter-by-name) | ||
| 2684 | (define-key map (kbd "/ /") 'package-menu-clear-filter) | ||
| 2685 | (define-key map "~" 'package-menu-mark-obsolete-for-deletion) | 2682 | (define-key map "~" 'package-menu-mark-obsolete-for-deletion) |
| 2686 | (define-key map "x" 'package-menu-execute) | 2683 | (define-key map "x" 'package-menu-execute) |
| 2687 | (define-key map "h" 'package-menu-quick-help) | 2684 | (define-key map "h" 'package-menu-quick-help) |
| 2688 | (define-key map "H" #'package-menu-hide-package) | 2685 | (define-key map "H" #'package-menu-hide-package) |
| 2689 | (define-key map "?" 'package-menu-describe-package) | 2686 | (define-key map "?" 'package-menu-describe-package) |
| 2690 | (define-key map "(" #'package-menu-toggle-hiding) | 2687 | (define-key map "(" #'package-menu-toggle-hiding) |
| 2688 | (define-key map (kbd "/ /") 'package-menu-clear-filter) | ||
| 2689 | (define-key map (kbd "/ a") 'package-menu-filter-by-archive) | ||
| 2690 | (define-key map (kbd "/ k") 'package-menu-filter-by-keyword) | ||
| 2691 | (define-key map (kbd "/ n") 'package-menu-filter-by-name) | ||
| 2692 | (define-key map (kbd "/ s") 'package-menu-filter-by-status) | ||
| 2693 | (define-key map (kbd "/ v") 'package-menu-filter-by-version) | ||
| 2691 | map) | 2694 | map) |
| 2692 | "Local keymap for `package-menu-mode' buffers.") | 2695 | "Local keymap for `package-menu-mode' buffers.") |
| 2693 | 2696 | ||
| @@ -2714,8 +2717,11 @@ either a full name or nil, and EMAIL is a valid email address." | |||
| 2714 | 2717 | ||
| 2715 | "--" | 2718 | "--" |
| 2716 | ("Filter Packages" | 2719 | ("Filter Packages" |
| 2720 | ["Filter by Archive" package-menu-filter-by-archive :help "Filter packages by archive"] | ||
| 2717 | ["Filter by Keyword" package-menu-filter-by-keyword :help "Filter packages by keyword"] | 2721 | ["Filter by Keyword" package-menu-filter-by-keyword :help "Filter packages by keyword"] |
| 2718 | ["Filter by Name" package-menu-filter-by-name :help "Filter packages by name"] | 2722 | ["Filter by Name" package-menu-filter-by-name :help "Filter packages by name"] |
| 2723 | ["Filter by Status" package-menu-filter-by-status :help "Filter packages by status"] | ||
| 2724 | ["Filter by Version" package-menu-filter-by-version :help "Filter packages by version"] | ||
| 2719 | ["Clear Filter" package-menu-clear-filter :help "Clear package list filter"]) | 2725 | ["Clear Filter" package-menu-clear-filter :help "Clear package list filter"]) |
| 2720 | 2726 | ||
| 2721 | ["Hide by Regexp" package-menu-hide-package :help "Permanently hide all packages matching a regexp"] | 2727 | ["Hide by Regexp" package-menu-hide-package :help "Permanently hide all packages matching a regexp"] |
| @@ -3021,22 +3027,31 @@ When none are given, the package matches." | |||
| 3021 | found) | 3027 | found) |
| 3022 | t)) | 3028 | t)) |
| 3023 | 3029 | ||
| 3024 | (defun package-menu--generate (remember-pos packages &optional keywords) | 3030 | (defun package-menu--display (remember-pos suffix) |
| 3025 | "Populate the Package Menu. | 3031 | "Display the Package Menu. |
| 3026 | If REMEMBER-POS is non-nil, keep point on the same entry. | 3032 | If REMEMBER-POS is non-nil, keep point on the same entry. |
| 3033 | |||
| 3034 | If SUFFIX is non-nil, append that to \"Package\" for the first | ||
| 3035 | column in the header line." | ||
| 3036 | (setf (car (aref tabulated-list-format 0)) | ||
| 3037 | (if suffix | ||
| 3038 | (concat "Package[" suffix "]") | ||
| 3039 | "Package")) | ||
| 3040 | (tabulated-list-init-header) | ||
| 3041 | (tabulated-list-print remember-pos)) | ||
| 3042 | |||
| 3043 | (defun package-menu--generate (remember-pos &optional packages keywords) | ||
| 3044 | "Populate and display the Package Menu. | ||
| 3027 | PACKAGES should be t, which means to display all known packages, | 3045 | PACKAGES should be t, which means to display all known packages, |
| 3028 | or a list of package names (symbols) to display. | 3046 | or a list of package names (symbols) to display. |
| 3029 | 3047 | ||
| 3030 | With KEYWORDS given, only packages with those keywords are | 3048 | With KEYWORDS given, only packages with those keywords are |
| 3031 | shown." | 3049 | shown." |
| 3032 | (package-menu--refresh packages keywords) | 3050 | (package-menu--refresh packages keywords) |
| 3033 | (setf (car (aref tabulated-list-format 0)) | 3051 | (package-menu--display remember-pos |
| 3034 | (if keywords | 3052 | (when keywords |
| 3035 | (let ((filters (mapconcat #'identity keywords ","))) | 3053 | (let ((filters (mapconcat #'identity keywords ","))) |
| 3036 | (concat "Package[" filters "]")) | 3054 | (concat "Package[" filters "]"))))) |
| 3037 | "Package")) | ||
| 3038 | (tabulated-list-init-header) | ||
| 3039 | (tabulated-list-print remember-pos)) | ||
| 3040 | 3055 | ||
| 3041 | (defun package-menu--print-info (pkg) | 3056 | (defun package-menu--print-info (pkg) |
| 3042 | "Return a package entry suitable for `tabulated-list-entries'. | 3057 | "Return a package entry suitable for `tabulated-list-entries'. |
| @@ -3673,45 +3688,160 @@ shown." | |||
| 3673 | (select-window win) | 3688 | (select-window win) |
| 3674 | (switch-to-buffer buf)))) | 3689 | (switch-to-buffer buf)))) |
| 3675 | 3690 | ||
| 3691 | (defun package-menu--filter-by (predicate suffix) | ||
| 3692 | "Filter \"*Packages*\" buffer by PREDICATE and add SUFFIX to header. | ||
| 3693 | PREDICATE is a function which will be called with one argument, a | ||
| 3694 | `package-desc' object, and returns t if that object should be | ||
| 3695 | listed in the Package Menu. | ||
| 3696 | |||
| 3697 | SUFFIX is passed on to `package-menu--display' and is added to | ||
| 3698 | the header line of the first column." | ||
| 3699 | ;; Update `tabulated-list-entries' so that it contains all | ||
| 3700 | ;; packages before searching. | ||
| 3701 | (package-menu--refresh t nil) | ||
| 3702 | (let (found-entries) | ||
| 3703 | (dolist (entry tabulated-list-entries) | ||
| 3704 | (when (funcall predicate (car entry)) | ||
| 3705 | (push entry found-entries))) | ||
| 3706 | (if found-entries | ||
| 3707 | (progn | ||
| 3708 | (setq tabulated-list-entries found-entries) | ||
| 3709 | (package-menu--display t suffix)) | ||
| 3710 | (user-error "No packages found")))) | ||
| 3711 | |||
| 3712 | (defun package-menu-filter-by-archive (archive) | ||
| 3713 | "Filter the \"*Packages*\" buffer by ARCHIVE. | ||
| 3714 | Display only packages from package archive ARCHIVE. | ||
| 3715 | |||
| 3716 | When called interactively, prompt for ARCHIVE, which can be a | ||
| 3717 | comma-separated string. If ARCHIVE is empty, show all packages. | ||
| 3718 | |||
| 3719 | When called from Lisp, ARCHIVE can be a string or a list of | ||
| 3720 | strings. If ARCHIVE is nil or the empty string, show all | ||
| 3721 | packages." | ||
| 3722 | (interactive (list (completing-read-multiple | ||
| 3723 | "Filter by archive (comma separated): " | ||
| 3724 | (mapcar #'car package-archives)))) | ||
| 3725 | (package--ensure-package-menu-mode) | ||
| 3726 | (let ((re (if (listp archive) | ||
| 3727 | (regexp-opt archive) | ||
| 3728 | archive))) | ||
| 3729 | (package-menu--filter-by (lambda (pkg-desc) | ||
| 3730 | (let ((pkg-archive (package-desc-archive pkg-desc))) | ||
| 3731 | (and pkg-archive | ||
| 3732 | (string-match-p re pkg-archive)))) | ||
| 3733 | (concat "archive:" (if (listp archive) | ||
| 3734 | (string-join archive ",") | ||
| 3735 | archive))))) | ||
| 3736 | |||
| 3676 | (defun package-menu-filter-by-keyword (keyword) | 3737 | (defun package-menu-filter-by-keyword (keyword) |
| 3677 | "Filter the \"*Packages*\" buffer by KEYWORD. | 3738 | "Filter the \"*Packages*\" buffer by KEYWORD. |
| 3678 | Show only those items that relate to the specified KEYWORD. | 3739 | Display only packages with specified KEYWORD. |
| 3679 | 3740 | ||
| 3680 | KEYWORD can be a string or a list of strings. If it is a list, a | 3741 | When called interactively, prompt for KEYWORD, which can be a |
| 3681 | package will be displayed if it matches any of the keywords. | 3742 | comma-separated string. If KEYWORD is empty, show all packages. |
| 3682 | Interactively, it is a list of strings separated by commas. | 3743 | |
| 3683 | 3744 | When called from Lisp, KEYWORD can be a string or a list of | |
| 3684 | KEYWORD can also be used to filter by status or archive name by | 3745 | strings. If KEYWORD is nil or the empty string, show all |
| 3685 | using keywords like \"arc:gnu\" and \"status:available\". | 3746 | packages." |
| 3686 | Statuses available include \"incompat\", \"available\", | 3747 | (interactive (list (completing-read-multiple |
| 3687 | \"built-in\" and \"installed\"." | 3748 | "Keywords (comma separated): " |
| 3688 | (interactive | 3749 | (package-all-keywords)))) |
| 3689 | (list (completing-read-multiple | 3750 | (when (stringp keyword) |
| 3690 | "Keywords (comma separated): " (package-all-keywords)))) | 3751 | (setq keyword (list keyword))) |
| 3691 | (package--ensure-package-menu-mode) | 3752 | (package--ensure-package-menu-mode) |
| 3692 | (package-show-package-list t (if (stringp keyword) | 3753 | (if (not keyword) |
| 3693 | (list keyword) | 3754 | (package-menu--generate t t) |
| 3694 | keyword))) | 3755 | (package-menu--filter-by (lambda (pkg-desc) |
| 3756 | (package--has-keyword-p pkg-desc keyword)) | ||
| 3757 | (concat "keyword:" (string-join keyword ","))))) | ||
| 3695 | 3758 | ||
| 3696 | (defun package-menu-filter-by-name (name) | 3759 | (defun package-menu-filter-by-name (name) |
| 3697 | "Filter the \"*Packages*\" buffer by NAME. | 3760 | "Filter the \"*Packages*\" buffer by NAME regexp. |
| 3698 | Show only those items whose name matches the regular expression | 3761 | Display only packages with name that matches regexp NAME. |
| 3699 | NAME. If NAME is nil or the empty string, show all packages." | 3762 | |
| 3700 | (interactive (list (read-from-minibuffer "Filter by name (regexp): "))) | 3763 | When called interactively, prompt for NAME. |
| 3764 | |||
| 3765 | If NAME is nil or the empty string, show all packages." | ||
| 3766 | (interactive (list (read-regexp "Filter by name (regexp)"))) | ||
| 3701 | (package--ensure-package-menu-mode) | 3767 | (package--ensure-package-menu-mode) |
| 3702 | (if (or (not name) (string-empty-p name)) | 3768 | (if (or (not name) (string-empty-p name)) |
| 3703 | (package-show-package-list t nil) | 3769 | (package-menu--generate t t) |
| 3704 | ;; Update `tabulated-list-entries' so that it contains all | 3770 | (package-menu--filter-by (lambda (pkg-desc) |
| 3705 | ;; packages before searching. | 3771 | (string-match-p name (symbol-name |
| 3706 | (package-menu--refresh t nil) | 3772 | (package-desc-name pkg-desc)))) |
| 3707 | (let (matched) | 3773 | (format "name:%s" name)))) |
| 3708 | (dolist (entry tabulated-list-entries) | 3774 | |
| 3709 | (let* ((pkg-name (package-desc-name (car entry)))) | 3775 | (defun package-menu-filter-by-status (status) |
| 3710 | (when (string-match name (symbol-name pkg-name)) | 3776 | "Filter the \"*Packages*\" buffer by STATUS. |
| 3711 | (push pkg-name matched)))) | 3777 | Display only packages with specified STATUS. |
| 3712 | (if matched | 3778 | |
| 3713 | (package-show-package-list matched nil) | 3779 | When called interactively, prompt for STATUS, which can be a |
| 3714 | (user-error "No packages found"))))) | 3780 | comma-separated string. If STATUS is empty, show all packages. |
| 3781 | |||
| 3782 | When called from Lisp, STATUS can be a string or a list of | ||
| 3783 | strings. If STATUS is nil or the empty string, show all | ||
| 3784 | packages." | ||
| 3785 | (interactive (list (completing-read "Filter by status: " | ||
| 3786 | '("avail-obso" | ||
| 3787 | "available" | ||
| 3788 | "built-in" | ||
| 3789 | "dependency" | ||
| 3790 | "disabled" | ||
| 3791 | "external" | ||
| 3792 | "held" | ||
| 3793 | "incompat" | ||
| 3794 | "installed" | ||
| 3795 | "new" | ||
| 3796 | "unsigned")))) | ||
| 3797 | (package--ensure-package-menu-mode) | ||
| 3798 | (if (or (not status) (string-empty-p status)) | ||
| 3799 | (package-menu--generate t t) | ||
| 3800 | (package-menu--filter-by (lambda (pkg-desc) | ||
| 3801 | (string-match-p status (package-desc-status pkg-desc))) | ||
| 3802 | (format "status:%s" status)))) | ||
| 3803 | |||
| 3804 | (defun package-menu-filter-by-version (version predicate) | ||
| 3805 | "Filter the \"*Packages*\" buffer by VERSION and PREDICATE. | ||
| 3806 | Display only packages with a matching version. | ||
| 3807 | |||
| 3808 | When called interactively, prompt for one of the qualifiers `<', | ||
| 3809 | `>' or `=', and a package version. Show only packages that has a | ||
| 3810 | lower (`<'), equal (`=') or higher (`>') version than the | ||
| 3811 | specified one. | ||
| 3812 | |||
| 3813 | When called from Lisp, VERSION should be a version string and | ||
| 3814 | PREDICATE should be the symbol `=', `<' or `>'. | ||
| 3815 | |||
| 3816 | If VERSION is nil or the empty string, show all packages." | ||
| 3817 | (interactive (let ((choice (intern | ||
| 3818 | (char-to-string | ||
| 3819 | (read-char-choice | ||
| 3820 | "Filter by version? [Type =, <, > or q] " | ||
| 3821 | '(?< ?> ?= ?q)))))) | ||
| 3822 | (if (eq choice 'q) | ||
| 3823 | '(quit nil) | ||
| 3824 | (list (read-from-minibuffer | ||
| 3825 | (concat "Filter by version (" | ||
| 3826 | (pcase choice | ||
| 3827 | ('= "= equal to") | ||
| 3828 | ('< "< less than") | ||
| 3829 | ('> "> greater than")) | ||
| 3830 | "): ")) | ||
| 3831 | choice)))) | ||
| 3832 | (unless (equal predicate 'quit) | ||
| 3833 | (if (or (not version) (string-empty-p version)) | ||
| 3834 | (package-menu--generate t t) | ||
| 3835 | (package-menu--filter-by | ||
| 3836 | (let ((fun (pcase predicate | ||
| 3837 | ('= 'version-list-=) | ||
| 3838 | ('< 'version-list-<) | ||
| 3839 | ('> '(lambda (a b) (not (version-list-<= a b)))) | ||
| 3840 | (_ (error "Unknown predicate: %s" predicate)))) | ||
| 3841 | (ver (version-to-list version))) | ||
| 3842 | (lambda (pkg-desc) | ||
| 3843 | (funcall fun (package-desc-version pkg-desc) ver))) | ||
| 3844 | (format "versions:%s%s" predicate version))))) | ||
| 3715 | 3845 | ||
| 3716 | (defun package-menu-clear-filter () | 3846 | (defun package-menu-clear-filter () |
| 3717 | "Clear any filter currently applied to the \"*Packages*\" buffer." | 3847 | "Clear any filter currently applied to the \"*Packages*\" buffer." |
| @@ -3760,6 +3890,7 @@ The return value is a string (or nil in case we can't find it)." | |||
| 3760 | (or (lm-header "package-version") | 3890 | (or (lm-header "package-version") |
| 3761 | (lm-header "version"))))))))) | 3891 | (lm-header "version"))))))))) |
| 3762 | 3892 | ||
| 3893 | |||
| 3763 | ;;;; Quickstart: precompute activation actions for faster start up. | 3894 | ;;;; Quickstart: precompute activation actions for faster start up. |
| 3764 | 3895 | ||
| 3765 | ;; Activating packages via `package-initialize' is costly: for N installed | 3896 | ;; 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 7d354d6ecde..adf917aef46 100644 --- a/test/lisp/emacs-lisp/package-tests.el +++ b/test/lisp/emacs-lisp/package-tests.el | |||
| @@ -349,43 +349,102 @@ Must called from within a `tar-mode' buffer." | |||
| 349 | (goto-char (point-min)) | 349 | (goto-char (point-min)) |
| 350 | (should (re-search-forward re nil t))))))) | 350 | (should (re-search-forward re nil t))))))) |
| 351 | 351 | ||
| 352 | |||
| 353 | ;;; Package Menu tests | ||
| 354 | |||
| 355 | (defmacro with-package-menu-test (&rest body) | ||
| 356 | "Set up Package Menu (\"*Packages*\") buffer for testing." | ||
| 357 | (declare (indent 0) (debug (([&rest form]) body))) | ||
| 358 | `(with-package-test () | ||
| 359 | (let ((buf (package-list-packages))) | ||
| 360 | (unwind-protect | ||
| 361 | (progn ,@body) | ||
| 362 | (kill-buffer buf))))) | ||
| 363 | |||
| 352 | (ert-deftest package-test-update-listing () | 364 | (ert-deftest package-test-update-listing () |
| 353 | "Ensure installed package status is updated." | 365 | "Ensure installed package status is updated." |
| 354 | (with-package-test () | 366 | (with-package-menu-test |
| 355 | (let ((buf (package-list-packages))) | 367 | (search-forward-regexp "^ +simple-single") |
| 356 | (search-forward-regexp "^ +simple-single") | 368 | (package-menu-mark-install) |
| 357 | (package-menu-mark-install) | 369 | (package-menu-execute) |
| 358 | (package-menu-execute) | 370 | (run-hooks 'post-command-hook) |
| 359 | (run-hooks 'post-command-hook) | 371 | (should (package-installed-p 'simple-single)) |
| 360 | (should (package-installed-p 'simple-single)) | 372 | (switch-to-buffer "*Packages*") |
| 361 | (switch-to-buffer "*Packages*") | 373 | (goto-char (point-min)) |
| 362 | (goto-char (point-min)) | 374 | (should (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+installed" nil t)) |
| 363 | (should (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+installed" nil t)) | 375 | (goto-char (point-min)) |
| 364 | (goto-char (point-min)) | 376 | (should-not (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+\\(available\\|new\\)" nil t)))) |
| 365 | (should-not (re-search-forward "^\\s-+simple-single\\s-+1.3\\s-+\\(available\\|new\\)" nil t)) | 377 | |
| 366 | (kill-buffer buf)))) | 378 | (ert-deftest package-test-list-filter-by-archive () |
| 379 | "Ensure package list is filtered correctly by archive version." | ||
| 380 | (with-package-menu-test | ||
| 381 | ;; TODO: Add another package archive to test filtering, because | ||
| 382 | ;; the testing environment currently only has one. | ||
| 383 | (package-menu-filter-by-archive "gnu") | ||
| 384 | (goto-char (point-min)) | ||
| 385 | (should (looking-at "^\\s-+multi-file")) | ||
| 386 | (should (= (count-lines (point-min) (point-max)) 4)) | ||
| 387 | (should-error (package-menu-filter-by-archive "non-existent archive")))) | ||
| 388 | |||
| 389 | (ert-deftest package-test-list-filter-by-keyword () | ||
| 390 | "Ensure package list is filtered correctly by package keyword." | ||
| 391 | (with-package-menu-test | ||
| 392 | (package-menu-filter-by-keyword "frobnicate") | ||
| 393 | (goto-char (point-min)) | ||
| 394 | (should (re-search-forward "^\\s-+simple-single" nil t)) | ||
| 395 | (should (= (count-lines (point-min) (point-max)) 1)) | ||
| 396 | (should-error (package-menu-filter-by-keyword "non-existent-keyword")))) | ||
| 367 | 397 | ||
| 368 | (ert-deftest package-test-list-filter-by-name () | 398 | (ert-deftest package-test-list-filter-by-name () |
| 369 | "Ensure package list is filtered correctly by package name." | 399 | "Ensure package list is filtered correctly by package name." |
| 370 | (with-package-test () | 400 | (with-package-menu-test () |
| 371 | (let ((buf (package-list-packages))) | 401 | (package-menu-filter-by-name "tetris") |
| 372 | (package-menu-filter-by-name "tetris") | 402 | (goto-char (point-min)) |
| 373 | (goto-char (point-min)) | 403 | (should (re-search-forward "^\\s-+tetris" nil t)) |
| 374 | (should (re-search-forward "^\\s-+tetris" nil t)) | 404 | (should (= (count-lines (point-min) (point-max)) 1)))) |
| 375 | (should (= (count-lines (point-min) (point-max)) 1)) | 405 | |
| 376 | (kill-buffer buf)))) | 406 | (ert-deftest package-test-list-filter-by-status () |
| 407 | "Ensure package list is filtered correctly by package status." | ||
| 408 | (with-package-menu-test | ||
| 409 | (package-menu-filter-by-status "available") | ||
| 410 | (goto-char (point-min)) | ||
| 411 | (should (re-search-forward "^\\s-+multi-file" nil t)) | ||
| 412 | (should (= (count-lines (point-min) (point-max)) 4)) | ||
| 413 | ;; No installed packages in default environment. | ||
| 414 | (should-error (package-menu-filter-by-status "installed")))) | ||
| 415 | |||
| 416 | (ert-deftest package-test-list-filter-by-version () | ||
| 417 | (with-package-menu-test | ||
| 418 | (should-error (package-menu-filter-by-version "1.1" 'unknown-symbol))) ) | ||
| 419 | |||
| 420 | (defun package-test-filter-by-version (version predicate name) | ||
| 421 | (with-package-menu-test | ||
| 422 | (package-menu-filter-by-version version predicate) | ||
| 423 | (goto-char (point-min)) | ||
| 424 | ;; We just check that the given package is included in the | ||
| 425 | ;; listing. One could be more ambitious. | ||
| 426 | (should (re-search-forward name)))) | ||
| 427 | |||
| 428 | (ert-deftest package-test-list-filter-by-version-= () | ||
| 429 | "Ensure package list is filtered correctly by package version (=)." | ||
| 430 | (package-test-filter-by-version "1.1" '= "^\\s-+simple-two-depend")) | ||
| 431 | |||
| 432 | (ert-deftest package-test-list-filter-by-version-< () | ||
| 433 | "Ensure package list is filtered correctly by package version (<)." | ||
| 434 | (package-test-filter-by-version "1.2" '< "^\\s-+simple-two-depend")) | ||
| 435 | |||
| 436 | (ert-deftest package-test-list-filter-by-version-> () | ||
| 437 | "Ensure package list is filtered correctly by package version (>)." | ||
| 438 | (package-test-filter-by-version "1.0" '> "^\\s-+simple-two-depend")) | ||
| 377 | 439 | ||
| 378 | (ert-deftest package-test-list-clear-filter () | 440 | (ert-deftest package-test-list-clear-filter () |
| 379 | "Ensure package list filter is cleared correctly." | 441 | "Ensure package list filter is cleared correctly." |
| 380 | (with-package-test () | 442 | (with-package-menu-test |
| 381 | (let ((buf (package-list-packages))) | 443 | (let ((num-packages (count-lines (point-min) (point-max)))) |
| 382 | (let ((num-packages (count-lines (point-min) (point-max)))) | 444 | (package-menu-filter-by-name "tetris") |
| 383 | (should (> num-packages 1)) | 445 | (should (= (count-lines (point-min) (point-max)) 1)) |
| 384 | (package-menu-filter-by-name "tetris") | 446 | (package-menu-clear-filter) |
| 385 | (should (= (count-lines (point-min) (point-max)) 1)) | 447 | (should (= (count-lines (point-min) (point-max)) num-packages))))) |
| 386 | (package-menu-clear-filter) | ||
| 387 | (should (= (count-lines (point-min) (point-max)) num-packages))) | ||
| 388 | (kill-buffer buf)))) | ||
| 389 | 448 | ||
| 390 | (ert-deftest package-test-update-archives () | 449 | (ert-deftest package-test-update-archives () |
| 391 | "Test updating package archives." | 450 | "Test updating package archives." |