aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
authorStefan Kangas2020-02-05 13:12:01 +0100
committerStefan Kangas2020-02-05 13:18:12 +0100
commitaea12d4903136c057bb14d3fd7683bf7a4e1eff6 (patch)
tree183e578c697d5b95191df9384f714c999f84a7e7 /lisp
parent196da3017bc9b2fc6fecff0c0ce560e6c46b8a72 (diff)
downloademacs-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.
Diffstat (limited to 'lisp')
-rw-r--r--lisp/emacs-lisp/package.el219
1 files changed, 175 insertions, 44 deletions
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.
3026If REMEMBER-POS is non-nil, keep point on the same entry. 3032If REMEMBER-POS is non-nil, keep point on the same entry.
3033
3034If SUFFIX is non-nil, append that to \"Package\" for the first
3035column 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.
3027PACKAGES should be t, which means to display all known packages, 3045PACKAGES should be t, which means to display all known packages,
3028or a list of package names (symbols) to display. 3046or a list of package names (symbols) to display.
3029 3047
3030With KEYWORDS given, only packages with those keywords are 3048With KEYWORDS given, only packages with those keywords are
3031shown." 3049shown."
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.
3693PREDICATE is a function which will be called with one argument, a
3694`package-desc' object, and returns t if that object should be
3695listed in the Package Menu.
3696
3697SUFFIX is passed on to `package-menu--display' and is added to
3698the 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.
3714Display only packages from package archive ARCHIVE.
3715
3716When called interactively, prompt for ARCHIVE, which can be a
3717comma-separated string. If ARCHIVE is empty, show all packages.
3718
3719When called from Lisp, ARCHIVE can be a string or a list of
3720strings. If ARCHIVE is nil or the empty string, show all
3721packages."
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.
3678Show only those items that relate to the specified KEYWORD. 3739Display only packages with specified KEYWORD.
3679 3740
3680KEYWORD can be a string or a list of strings. If it is a list, a 3741When called interactively, prompt for KEYWORD, which can be a
3681package will be displayed if it matches any of the keywords. 3742comma-separated string. If KEYWORD is empty, show all packages.
3682Interactively, it is a list of strings separated by commas. 3743
3683 3744When called from Lisp, KEYWORD can be a string or a list of
3684KEYWORD can also be used to filter by status or archive name by 3745strings. If KEYWORD is nil or the empty string, show all
3685using keywords like \"arc:gnu\" and \"status:available\". 3746packages."
3686Statuses 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.
3698Show only those items whose name matches the regular expression 3761Display only packages with name that matches regexp NAME.
3699NAME. If NAME is nil or the empty string, show all packages." 3762
3700 (interactive (list (read-from-minibuffer "Filter by name (regexp): "))) 3763When called interactively, prompt for NAME.
3764
3765If 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)))) 3777Display only packages with specified STATUS.
3712 (if matched 3778
3713 (package-show-package-list matched nil) 3779When called interactively, prompt for STATUS, which can be a
3714 (user-error "No packages found"))))) 3780comma-separated string. If STATUS is empty, show all packages.
3781
3782When called from Lisp, STATUS can be a string or a list of
3783strings. If STATUS is nil or the empty string, show all
3784packages."
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.
3806Display only packages with a matching version.
3807
3808When called interactively, prompt for one of the qualifiers `<',
3809`>' or `=', and a package version. Show only packages that has a
3810lower (`<'), equal (`=') or higher (`>') version than the
3811specified one.
3812
3813When called from Lisp, VERSION should be a version string and
3814PREDICATE should be the symbol `=', `<' or `>'.
3815
3816If 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