aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/package/package-menu.el
diff options
context:
space:
mode:
authorPhilip Kaludercic2025-08-06 15:12:55 +0200
committerPhilip Kaludercic2025-08-07 15:55:01 +0200
commitbdd0220f6571906b0618924274ec12fbb876a09e (patch)
tree8c7a38e924ddb329f226a98eeec43dc331e8a67d /lisp/package/package-menu.el
parent5c153cfb9620baf44dd388bb509c5aca82e377e9 (diff)
downloademacs-scratch/package.el-experiments.tar.gz
emacs-scratch/package.el-experiments.zip
Split package.el into multiple filesscratch/package.el-experiments
* lisp/emacs-lisp/package.el: Replace this file with... * lisp/emacs-lisp/package/package.el: a stub file and... * lisp/emacs-lisp/package/package-compile.el: file. * lisp/emacs-lisp/package/package-describe.el: * lisp/emacs-lisp/package/package-elpa.el: * lisp/emacs-lisp/package/package-install.el: * lisp/emacs-lisp/package/package-menu.el: * lisp/emacs-lisp/package/package-misc.el: * lisp/emacs-lisp/package/package-quickstart.el: Multiple files. * lisp/emacs-lisp/package-vc.el: Move this file... * lisp/emacs-lisp/package/package-vc.el: to here.
Diffstat (limited to 'lisp/package/package-menu.el')
-rw-r--r--lisp/package/package-menu.el1580
1 files changed, 1580 insertions, 0 deletions
diff --git a/lisp/package/package-menu.el b/lisp/package/package-menu.el
new file mode 100644
index 00000000000..4be14069999
--- /dev/null
+++ b/lisp/package/package-menu.el
@@ -0,0 +1,1580 @@
1;;; package-compile.el --- Byte-Compilation of Packages -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2025 Philip Kaludercic
4
5;; Author: Philip Kaludercic <philipk@posteo.net>
6
7;; This program is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; This program is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;;; Code:
23
24(require 'package-core)
25(require 'package-install)
26(require 'package-vc)
27
28(require 'tabulated-list)
29(require 'browse-url)
30(require 'macroexp)
31(require 'lisp-mnt)
32
33(defgroup package-menu nil
34 "A interface for package management."
35 :group 'package
36 :version "24.1")
37
38(defcustom package-menu-async t
39 "If non-nil, package-menu will use async operations when possible.
40Currently, only the refreshing of archive contents supports
41asynchronous operations. Package transactions are still done
42synchronously."
43 :type 'boolean
44 :version "25.1")
45
46(defcustom package-menu-hide-low-priority 'archive
47 "If non-nil, hide low priority packages from the packages menu.
48A package is considered low priority if there's another version
49of it available such that:
50 (a) the archive of the other package is higher priority than
51 this one, as per `package-archive-priorities';
52 or
53 (b) they both have the same archive priority but the other
54 package has a higher version number.
55
56This variable has three possible values:
57 nil: no packages are hidden;
58 `archive': only criterion (a) is used;
59 t: both criteria are used.
60
61This variable has no effect if `package-menu--hide-packages' is
62nil, so it can be toggled with \\<package-menu-mode-map>\\[package-menu-toggle-hiding]."
63 :type '(choice (const :tag "Don't hide anything" nil)
64 (const :tag "Hide per package-archive-priorities"
65 archive)
66 (const :tag "Hide per archive and version number" t))
67 :version "25.1")
68
69(defcustom package-hidden-regexps nil
70 "List of regexps matching the name of packages to hide.
71If the name of a package matches any of these regexps it is
72omitted from the package menu. To toggle this, type \\[package-menu-toggle-hiding].
73
74Values can be interactively added to this list by typing
75\\[package-menu-hide-package] on a package."
76 :version "25.1"
77 :type '(repeat (regexp :tag "Hide packages with name matching")))
78
79(defcustom package-menu-use-current-if-no-marks t
80 "Whether \\<package-menu-mode-map>\\[package-menu-execute] in package menu operates on current package if none are marked.
81
82If non-nil, and no packages are marked for installation or
83deletion, \\<package-menu-mode-map>\\[package-menu-execute] will operate on the current package at point,
84see `package-menu-execute' for details.
85The default is t. Set to nil to get back the original behavior
86of having `package-menu-execute' signal an error when no packages
87are marked for installation or deletion."
88 :version "29.1"
89 :type 'boolean)
90
91(defcustom package-name-column-width 30
92 "Column width for the Package name in the package menu."
93 :type 'natnum
94 :version "28.1")
95
96(defcustom package-version-column-width 14
97 "Column width for the Package version in the package menu."
98 :type 'natnum
99 :version "28.1")
100
101(defcustom package-status-column-width 12
102 "Column width for the Package status in the package menu."
103 :type 'natnum
104 :version "28.1")
105
106(defcustom package-archive-column-width 8
107 "Column width for the Package archive in the package menu."
108 :type 'natnum
109 :version "28.1")
110
111(defun package-browse-url (desc &optional secondary)
112 "Open the website of the package under point in a browser.
113`browse-url' is used to determine the browser to be used. If
114SECONDARY (interactively, the prefix), use the secondary browser.
115DESC must be a `package-desc' object."
116 (interactive (list (package--query-desc)
117 current-prefix-arg)
118 package-menu-mode)
119 (unless desc
120 (user-error "No package here"))
121 (let ((url (cdr (assoc :url (package-desc-extras desc)))))
122 (unless url
123 (user-error "No website for %s" (package-desc-name desc)))
124 (if secondary
125 (funcall browse-url-secondary-browser-function url)
126 (browse-url url))))
127
128(defun package--imenu-prev-index-position-function ()
129 "Move point to previous line in package-menu buffer.
130This function is used as a value for
131`imenu-prev-index-position-function'."
132 (unless (bobp)
133 (forward-line -1)))
134
135(defun package--imenu-extract-index-name-function ()
136 "Return imenu name for line at point.
137This function is used as a value for
138`imenu-extract-index-name-function'. Point should be at the
139beginning of the line."
140 (let ((package-desc (tabulated-list-get-id)))
141 (format "%s (%s): %s"
142 (package-desc-name package-desc)
143 (package-version-join (package-desc-version package-desc))
144 (package-desc-summary package-desc))))
145
146(defun package-menu--display (remember-pos suffix)
147 "Display the Package Menu.
148If REMEMBER-POS is non-nil, keep point on the same entry.
149
150If SUFFIX is non-nil, append that to \"Package\" for the first
151column in the header line."
152 (setf (car (aref tabulated-list-format 0))
153 (if suffix
154 (concat "Package[" suffix "]")
155 "Package"))
156 (tabulated-list-init-header)
157 (tabulated-list-print remember-pos))
158
159(defun package-menu--generate (remember-pos &optional packages keywords)
160 "Populate and display the Package Menu.
161If REMEMBER-POS is non-nil, keep point on the same entry.
162PACKAGES should be t, which means to display all known packages,
163or a list of package names (symbols) to display.
164
165With KEYWORDS given, only packages with those keywords are
166shown."
167 (package-menu--refresh packages keywords)
168 (package-menu--display remember-pos
169 (when keywords
170 (let ((filters (mapconcat #'identity keywords ",")))
171 (concat "Package[" filters "]")))))
172
173(defun package-menu--print-info (pkg)
174 "Return a package entry suitable for `tabulated-list-entries'.
175PKG has the form (PKG-DESC . STATUS).
176Return (PKG-DESC [NAME VERSION STATUS DOC])."
177 (package-menu--print-info-simple (car pkg)))
178(make-obsolete 'package-menu--print-info
179 'package-menu--print-info-simple "25.1")
180
181
182;;; Package menu faces
183
184(defface package-name
185 '((t :inherit link))
186 "Face used on package names in the package menu."
187 :version "25.1")
188
189(defface package-description
190 '((t :inherit default))
191 "Face used on package description summaries in the package menu."
192 :version "25.1")
193
194;; Shame this hyphenates "built-in", when "font-lock-builtin-face" doesn't.
195(defface package-status-built-in
196 '((t :inherit font-lock-builtin-face))
197 "Face used on the status and version of built-in packages."
198 :version "25.1")
199
200(defface package-status-external
201 '((t :inherit package-status-built-in))
202 "Face used on the status and version of external packages."
203 :version "25.1")
204
205(defface package-status-available
206 '((t :inherit default))
207 "Face used on the status and version of available packages."
208 :version "25.1")
209
210(defface package-status-new
211 '((t :inherit (bold package-status-available)))
212 "Face used on the status and version of new packages."
213 :version "25.1")
214
215(defface package-status-held
216 '((t :inherit font-lock-constant-face))
217 "Face used on the status and version of held packages."
218 :version "25.1")
219
220(defface package-status-disabled
221 '((t :inherit font-lock-warning-face))
222 "Face used on the status and version of disabled packages."
223 :version "25.1")
224
225(defface package-status-installed
226 '((t :inherit font-lock-comment-face))
227 "Face used on the status and version of installed packages."
228 :version "25.1")
229
230(defface package-status-from-source
231 '((t :inherit font-lock-negation-char-face))
232 "Face used on the status and version of installed packages."
233 :version "29.1")
234
235(defface package-status-dependency
236 '((t :inherit package-status-installed))
237 "Face used on the status and version of dependency packages."
238 :version "25.1")
239
240(defface package-status-unsigned
241 '((t :inherit font-lock-warning-face))
242 "Face used on the status and version of unsigned packages."
243 :version "25.1")
244
245(defface package-status-incompat
246 '((t :inherit error))
247 "Face used on the status and version of incompat packages."
248 :version "25.1")
249
250(defface package-status-avail-obso
251 '((t :inherit package-status-incompat))
252 "Face used on the status and version of avail-obso packages."
253 :version "25.1")
254
255(defface package-mark-install-line
256 '((((class color) (background light))
257 :background "darkolivegreen1" :extend t)
258 (((class color) (background dark))
259 :background "seagreen" :extend t)
260 (t :inherit (highlight) :extend t))
261 "Face used for highlighting in package-menu packages marked to be installed."
262 :version "31.1")
263
264(defface package-mark-delete-line
265 '((((class color) (background light))
266 :background "rosybrown1" :extend t)
267 (((class color) (background dark))
268 :background "indianred4" :extend t)
269 (t :inherit (highlight) :extend t))
270 "Face used for highlighting in package-menu packages marked to be deleted."
271 :version "31.1")
272
273(defface package-mode-line-total nil
274 "Face for the total number of packages displayed on the mode line."
275 :version "31.1")
276
277(defface package-mode-line-installed '((t :inherit package-status-installed))
278 "Face for the number of installed packages displayed on the mode line."
279 :version "31.1")
280
281(defface package-mode-line-to-upgrade '((t :inherit bold))
282 "Face for the number of packages to upgrade displayed on the mode line."
283 :version "31.1")
284
285(defface package-mode-line-new '((t :inherit package-status-new))
286 "Face for the number of new packages displayed on the mode line."
287 :version "31.1")
288
289;;; Package menu printing
290
291(defun package-menu--print-info-simple (pkg)
292 "Return a package entry suitable for `tabulated-list-entries'.
293PKG is a `package-desc' object.
294Return (PKG-DESC [NAME VERSION STATUS DOC])."
295 (let* ((status (package-desc-status pkg))
296 (face (pcase status
297 ("built-in" 'package-status-built-in)
298 ("external" 'package-status-external)
299 ("available" 'package-status-available)
300 ("avail-obso" 'package-status-avail-obso)
301 ("new" 'package-status-new)
302 ("held" 'package-status-held)
303 ("disabled" 'package-status-disabled)
304 ("installed" 'package-status-installed)
305 ("source" 'package-status-from-source)
306 ("dependency" 'package-status-dependency)
307 ("unsigned" 'package-status-unsigned)
308 ("incompat" 'package-status-incompat)
309 (_ 'font-lock-warning-face)))) ; obsolete.
310 (list pkg
311 `[(,(symbol-name (package-desc-name pkg))
312 face package-name
313 font-lock-face package-name
314 follow-link t
315 package-desc ,pkg
316 action package-menu-describe-package)
317 ,(propertize
318 (if (package-vc-p pkg)
319 (package-vc-commit pkg)
320 (package-version-join
321 (package-desc-version pkg)))
322 'font-lock-face face)
323 ,(propertize status 'font-lock-face face)
324 ,(propertize (or (package-desc-archive pkg) "")
325 'font-lock-face face)
326 ,(propertize (package-desc-summary pkg)
327 'font-lock-face 'package-description)])))
328
329(defvar package-menu--old-archive-contents nil
330 "`package-archive-contents' before the latest refresh.")
331
332(defun package--ensure-package-menu-mode ()
333 "Signal a user-error if major mode is not `package-menu-mode'."
334 (unless (derived-mode-p 'package-menu-mode)
335 (user-error "The current buffer is not a Package Menu")))
336
337(defvar package-menu--new-package-list nil
338 "List of newly-available packages since `list-packages' was last called.")
339
340(defun package-menu--refresh-contents (&optional _arg _noconfirm)
341 "In Package Menu, download the Emacs Lisp package archive.
342Fetch the contents of each archive specified in
343`package-archives', and then refresh the package menu.
344
345`package-menu-mode' sets `revert-buffer-function' to this
346function. The args ARG and NOCONFIRM, passed from
347`revert-buffer', are ignored."
348 (package--ensure-package-menu-mode)
349 (setq package-menu--old-archive-contents package-archive-contents)
350 (setq package-menu--new-package-list nil)
351 (package-refresh-contents package-menu-async))
352(define-obsolete-function-alias 'package-menu-refresh 'revert-buffer "27.1")
353
354(defun package-menu--overlay-line (face)
355 "Highlight whole line with face FACE."
356 (let ((ov (make-overlay (line-beginning-position)
357 (1+ (line-end-position)))))
358 (overlay-put ov 'pkg-menu-ov t)
359 (overlay-put ov 'evaporate t)
360 (overlay-put ov 'face face)))
361
362(defun package-menu--remove-overlay ()
363 "Remove all overlays done by `package-menu--overlay-line' in current line."
364 (remove-overlays (line-beginning-position)
365 (1+ (line-end-position))
366 'pkg-menu-ov t))
367
368(defun package-menu-hide-package ()
369 "Hide in Package Menu packages that match a regexp.
370Prompt for the regexp to match against package names.
371The default regexp will hide only the package whose name is at point.
372
373The regexp is added to the list in the user option
374`package-hidden-regexps' and saved for future sessions.
375
376To unhide a package, type
377`\\[customize-variable] RET package-hidden-regexps', and then modify
378the regexp such that it no longer matches the package's name.
379
380Type \\[package-menu-toggle-hiding] to toggle package hiding."
381 (declare (interactive-only "change `package-hidden-regexps' instead."))
382 (interactive nil package-menu-mode)
383 (package--ensure-package-menu-mode)
384 (let* ((name (when (derived-mode-p 'package-menu-mode)
385 (concat "\\`" (regexp-quote (symbol-name (package-desc-name
386 (tabulated-list-get-id))))
387 "\\'")))
388 (re (read-string "Hide packages matching regexp: " name)))
389 ;; Test if it is valid.
390 (string-match re "")
391 (push re package-hidden-regexps)
392 (customize-save-variable 'package-hidden-regexps package-hidden-regexps)
393 (package-menu--post-refresh)
394 (let ((hidden
395 (cl-remove-if-not (lambda (e) (string-match re (symbol-name (car e))))
396 package-archive-contents)))
397 (message "Packages to hide: %d. Type `%s' to toggle or `%s' to customize"
398 (length hidden)
399 (substitute-command-keys "\\[package-menu-toggle-hiding]")
400 (substitute-command-keys "\\[customize-variable] RET package-hidden-regexps")))))
401
402
403(defun package-menu-describe-package (&optional button)
404 "Describe the current package.
405The current package is the package at point.
406If optional arg BUTTON is non-nil, describe its associated
407package(s); this is always nil in interactive invocations."
408 (interactive nil package-menu-mode)
409 (let ((pkg-desc (if button (button-get button 'package-desc)
410 (tabulated-list-get-id))))
411 (if pkg-desc
412 (describe-package pkg-desc)
413 (user-error "No package here"))))
414
415;; fixme numeric argument
416(defun package-menu-mark-delete (&optional _num)
417 "Mark the current package for deletion and move to the next line.
418The current package is the package at point."
419 (interactive "p" package-menu-mode)
420 (package--ensure-package-menu-mode)
421 (if (member (package-menu-get-status)
422 '("installed" "source" "dependency" "obsolete" "unsigned"))
423 (progn (package-menu--overlay-line 'package-mark-delete-line)
424 (tabulated-list-put-tag "D" t))
425 (forward-line)))
426
427(defun package-menu-mark-install (&optional _num)
428 "Mark the current package for installation and move to the next line.
429The current package is the package at point."
430 (interactive "p" package-menu-mode)
431 (package--ensure-package-menu-mode)
432 (if (member (package-menu-get-status) '("available" "avail-obso" "new" "dependency"))
433 (progn (package-menu--overlay-line 'package-mark-install-line)
434 (tabulated-list-put-tag "I" t))
435 (forward-line)))
436
437(defun package-menu-mark-unmark (&optional _num)
438 "Clear any marks on the current package and move to the next line.
439The current package is the package at point."
440 (interactive "p" package-menu-mode)
441 (package--ensure-package-menu-mode)
442 (package-menu--remove-overlay)
443 (tabulated-list-put-tag " " t))
444
445(defun package-menu-backup-unmark ()
446 "Back up one line and clear any marks on that line's package."
447 (interactive nil package-menu-mode)
448 (package--ensure-package-menu-mode)
449 (forward-line -1)
450 (package-menu--remove-overlay)
451 (tabulated-list-put-tag " "))
452
453(defun package-menu-mark-obsolete-for-deletion ()
454 "Mark all obsolete packages for deletion."
455 (interactive nil package-menu-mode)
456 (package--ensure-package-menu-mode)
457 (save-excursion
458 (goto-char (point-min))
459 (while (not (eobp))
460 (if (equal (package-menu-get-status) "obsolete")
461 (progn (package-menu--overlay-line 'package-mark-delete-line)
462 (tabulated-list-put-tag "D" t))
463 (forward-line 1)))))
464
465(defvar package--quick-help-keys
466 '((("mark for installation," . 9)
467 ("mark for deletion," . 9) "unmark," ("execute marked actions" . 1))
468 ("next," "previous")
469 ("Hide-package," "(-toggle-hidden")
470 ("g-refresh-contents," "/-filter," "help")))
471
472(defun package--prettify-quick-help-key (desc)
473 "Prettify DESC to be displayed as a help menu."
474 (if (listp desc)
475 (if (listp (cdr desc))
476 (mapconcat #'package--prettify-quick-help-key desc " ")
477 (let ((place (cdr desc))
478 (out (copy-sequence (car desc))))
479 (add-text-properties place (1+ place)
480 '(face help-key-binding)
481 out)
482 out))
483 (package--prettify-quick-help-key (cons desc 0))))
484
485(defun package-menu-quick-help ()
486 "Show short help for key bindings in `package-menu-mode'.
487You can view the full list of keys with \\[describe-mode]."
488 (interactive nil package-menu-mode)
489 (package--ensure-package-menu-mode)
490 (message (mapconcat #'package--prettify-quick-help-key
491 package--quick-help-keys "\n")))
492
493(defun package-menu-get-status ()
494 "Return status description of package at point in Package Menu."
495 (package--ensure-package-menu-mode)
496 (let* ((id (tabulated-list-get-id))
497 (entry (and id (assoc id tabulated-list-entries))))
498 (if entry
499 (aref (cadr entry) 2)
500 "")))
501
502(defun package-menu--find-upgrades ()
503 "In Package Menu, return an alist of packages that can be upgraded.
504The alist has the same form as `package-alist', namely a list
505of elements of the form (PKG . DESCS), but where DESCS is the `package-desc'
506object corresponding to the newer version."
507 (let (installed available upgrades)
508 ;; Build list of installed/available packages in this buffer.
509 (dolist (entry tabulated-list-entries)
510 ;; ENTRY is (PKG-DESC [NAME VERSION STATUS DOC])
511 (let ((pkg-desc (car entry))
512 (status (aref (cadr entry) 2)))
513 (cond ((member status '("installed" "dependency" "unsigned" "external" "built-in"))
514 (push pkg-desc installed))
515 ((member status '("available" "new"))
516 (setq available (package--append-to-alist pkg-desc available))))))
517 ;; Loop through list of installed packages, finding upgrades.
518 (dolist (pkg-desc installed)
519 (let* ((name (package-desc-name pkg-desc))
520 (avail-pkg (cadr (assq name available))))
521 (and avail-pkg
522 (version-list-< (package-desc-priority-version pkg-desc)
523 (package-desc-priority-version avail-pkg))
524 (or (not (package--active-built-in-p pkg-desc))
525 package-install-upgrade-built-in)
526 (push (cons name avail-pkg) upgrades))))
527 upgrades))
528
529(defvar package-menu--mark-upgrades-pending nil
530 "Whether mark-upgrades is waiting for a refresh to finish.")
531
532(defun package-menu--mark-upgrades-1 ()
533 "Mark all upgradable packages in the Package Menu.
534Implementation of `package-menu-mark-upgrades'."
535 (setq package-menu--mark-upgrades-pending nil)
536 (let ((upgrades (package-menu--find-upgrades)))
537 (if (null upgrades)
538 (message "No packages to upgrade")
539 (widen)
540 (save-excursion
541 (goto-char (point-min))
542 (while (not (eobp))
543 (let* ((pkg-desc (tabulated-list-get-id))
544 (upgrade (cdr (assq (package-desc-name pkg-desc) upgrades))))
545 (cond ((null upgrade)
546 (forward-line 1))
547 ((equal pkg-desc upgrade)
548 (package-menu-mark-install))
549 (t
550 (package-menu-mark-delete))))))
551 (message "Packages marked for upgrading: %d"
552 (length upgrades)))))
553
554
555(defun package-menu-mark-upgrades ()
556 "Mark all upgradable packages in the Package Menu.
557For each installed package for which a newer version is available,
558place an (I)nstall flag on the available version and a (D)elete flag
559on the installed version. A subsequent \\[package-menu-execute] command will upgrade
560the marked packages.
561
562If there's an async refresh operation in progress, the flags will
563be placed as part of `package-menu--post-refresh' instead of
564immediately."
565 (interactive nil package-menu-mode)
566 (package--ensure-package-menu-mode)
567 (if (not package--downloads-in-progress)
568 (package-menu--mark-upgrades-1)
569 (setq package-menu--mark-upgrades-pending t)
570 (message "Waiting for refresh to finish...")))
571
572(defun package-menu--list-to-prompt (packages &optional include-dependencies)
573 "Return a string listing PACKAGES that's usable in a prompt.
574PACKAGES is a list of `package-desc' objects.
575Formats the returned string to be usable in a minibuffer
576prompt (see `package-menu--prompt-transaction-p').
577
578If INCLUDE-DEPENDENCIES, also include the number of uninstalled
579dependencies."
580 ;; The case where `package' is empty is handled in
581 ;; `package-menu--prompt-transaction-p' below.
582 (format "%d (%s)%s"
583 (length packages)
584 (mapconcat #'package-desc-full-name packages " ")
585 (let ((deps
586 (seq-remove
587 #'package-installed-p
588 (delete-dups
589 (apply
590 #'nconc
591 (mapcar (lambda (package)
592 (package--dependencies
593 (package-desc-name package)))
594 packages))))))
595 (if (and include-dependencies deps)
596 (if (length= deps 1)
597 (format " plus 1 dependency")
598 (format " plus %d dependencies" (length deps)))
599 ""))))
600
601(defun package-menu--prompt-transaction-p (delete install upgrade)
602 "Prompt the user about DELETE, INSTALL, and UPGRADE.
603DELETE, INSTALL, and UPGRADE are lists of `package-desc' objects.
604Either may be nil, but not all."
605 (y-or-n-p
606 (concat
607 (when delete
608 (format "Packages to delete: %s. "
609 (package-menu--list-to-prompt delete)))
610 (when install
611 (format "Packages to install: %s. "
612 (package-menu--list-to-prompt install t)))
613 (when upgrade
614 (format "Packages to upgrade: %s. "
615 (package-menu--list-to-prompt upgrade)))
616 "Proceed? ")))
617
618
619(defun package-menu--partition-transaction (install delete)
620 "Return an alist describing an INSTALL DELETE transaction.
621Alist contains three entries, upgrade, delete, and install, each
622with a list of package names.
623
624The upgrade entry contains any `package-desc' objects in INSTALL
625whose name coincides with an object in DELETE. The delete and
626the install entries are the same as DELETE and INSTALL with such
627objects removed."
628 (let* ((upg (cl-intersection install delete :key #'package-desc-name))
629 (ins (cl-set-difference install upg :key #'package-desc-name))
630 (del (cl-set-difference delete upg :key #'package-desc-name)))
631 `((delete . ,del) (install . ,ins) (upgrade . ,upg))))
632
633(defvar package-menu--transaction-status nil
634 "Mode-line status of ongoing package transaction.")
635
636(defun package-menu--perform-transaction (install-list delete-list)
637 "Install packages in INSTALL-LIST and delete DELETE-LIST.
638Return nil if there were no errors; non-nil otherwise."
639 (let ((errors nil))
640 (if install-list
641 (let ((status-format (format ":Installing %%d/%d"
642 (length install-list)))
643 (i 0)
644 (package-menu--transaction-status))
645 (dolist (pkg install-list)
646 (setq package-menu--transaction-status
647 (format status-format (incf i)))
648 (force-mode-line-update)
649 (redisplay 'force)
650 ;; Don't mark as selected, `package-menu-execute' already
651 ;; does that.
652 (package-install pkg 'dont-select))))
653 (let ((package-menu--transaction-status ":Deleting"))
654 (force-mode-line-update)
655 (redisplay 'force)
656 (dolist (elt (package--sort-by-dependence delete-list))
657 (condition-case-unless-debug err
658 (let ((inhibit-message (or inhibit-message package-menu-async)))
659 (package-delete elt nil 'nosave))
660 (error
661 (push (package-desc-full-name elt) errors)
662 (message "Error trying to delete `%s': %s"
663 (package-desc-full-name elt)
664 (error-message-string err))))))
665 errors))
666
667(defun package--update-selected-packages (add remove)
668 "Update the `package-selected-packages' list according to ADD and REMOVE.
669ADD and REMOVE must be disjoint lists of package names (or
670`package-desc' objects) to be added and removed to the selected
671packages list, respectively."
672 (dolist (p add)
673 (cl-pushnew (if (package-desc-p p) (package-desc-name p) p)
674 package-selected-packages))
675 (dolist (p remove)
676 (setq package-selected-packages
677 (remove (if (package-desc-p p) (package-desc-name p) p)
678 package-selected-packages)))
679 (when (or add remove)
680 (package--save-selected-packages package-selected-packages)))
681
682(defun package-menu-execute (&optional noquery)
683 "Perform Package Menu actions on marked packages.
684Packages marked for installation are downloaded and installed,
685packages marked for deletion are removed, and packages marked for
686upgrading are downloaded and upgraded.
687
688If no packages are marked, the action taken depends on the state
689of the current package, the one at point. If it's not already
690installed, this command will install the package; if it's installed,
691the command will delete the package.
692
693Optional argument NOQUERY non-nil means do not ask the user to
694confirm the installations/deletions; this is always nil in interactive
695invocations."
696 (interactive nil package-menu-mode)
697 (package--ensure-package-menu-mode)
698 (let (install-list delete-list cmd pkg-desc)
699 (save-excursion
700 (goto-char (point-min))
701 (while (not (eobp))
702 (setq cmd (char-after))
703 (unless (eq cmd ?\s)
704 ;; This is the key PKG-DESC.
705 (setq pkg-desc (tabulated-list-get-id))
706 (cond ((eq cmd ?D)
707 (push pkg-desc delete-list))
708 ((eq cmd ?I)
709 (push pkg-desc install-list))))
710 (forward-line)))
711 ;; Nothing marked.
712 (unless (or delete-list install-list)
713 ;; Not on a package line.
714 (unless (and (tabulated-list-get-id)
715 package-menu-use-current-if-no-marks)
716 (user-error "No operations specified"))
717 (let* ((id (tabulated-list-get-id))
718 (status (package-menu-get-status)))
719 (cond
720 ((member status '("installed"))
721 (push id delete-list))
722 ((member status '("available" "avail-obso" "new" "dependency"))
723 (push id install-list))
724 (t (user-error "No default action available for status: %s"
725 status)))))
726 (let-alist (package-menu--partition-transaction install-list delete-list)
727 (when (or noquery
728 (package-menu--prompt-transaction-p .delete .install .upgrade))
729 (let ((message-template
730 (concat "[ "
731 (when .delete
732 (format "Delete %d " (length .delete)))
733 (when .install
734 (format "Install %d " (length .install)))
735 (when .upgrade
736 (format "Upgrade %d " (length .upgrade)))
737 "]")))
738 (message "Operation %s started" message-template)
739 ;; Packages being upgraded are not marked as selected.
740 (package--update-selected-packages .install .delete)
741 (unless (package-menu--perform-transaction install-list delete-list)
742 ;; If there weren't errors, output data.
743 (if-let* ((removable (package--removable-packages)))
744 (message "Operation finished. Packages that are no longer needed: %d. Type `%s' to remove them"
745 (length removable)
746 (substitute-command-keys "\\[package-autoremove]"))
747 (message "Operation %s finished" message-template))))))))
748
749(defun package-menu--version-predicate (A B)
750 "Predicate to sort \"*Packages*\" buffer by the version column.
751This is used for `tabulated-list-format' in `package-menu-mode'."
752 (let ((vA (or (ignore-error error (version-to-list (aref (cadr A) 1))) '(0)))
753 (vB (or (ignore-error error (version-to-list (aref (cadr B) 1))) '(0))))
754 (if (version-list-= vA vB)
755 (package-menu--name-predicate A B)
756 (version-list-< vA vB))))
757
758(defun package-menu--status-predicate (A B)
759 "Predicate to sort \"*Packages*\" buffer by the status column.
760This is used for `tabulated-list-format' in `package-menu-mode'."
761 (let ((sA (aref (cadr A) 2))
762 (sB (aref (cadr B) 2)))
763 (cond ((string= sA sB)
764 (package-menu--name-predicate A B))
765 ((string= sA "new") t)
766 ((string= sB "new") nil)
767 ((string-prefix-p "avail" sA)
768 (if (string-prefix-p "avail" sB)
769 (package-menu--name-predicate A B)
770 t))
771 ((string-prefix-p "avail" sB) nil)
772 ((string= sA "installed") t)
773 ((string= sB "installed") nil)
774 ((string= sA "dependency") t)
775 ((string= sB "dependency") nil)
776 ((string= sA "source") t)
777 ((string= sB "source") nil)
778 ((string= sA "unsigned") t)
779 ((string= sB "unsigned") nil)
780 ((string= sA "held") t)
781 ((string= sB "held") nil)
782 ((string= sA "external") t)
783 ((string= sB "external") nil)
784 ((string= sA "built-in") t)
785 ((string= sB "built-in") nil)
786 ((string= sA "obsolete") t)
787 ((string= sB "obsolete") nil)
788 ((string= sA "incompat") t)
789 ((string= sB "incompat") nil)
790 (t (string< sA sB)))))
791
792(defun package-menu--description-predicate (A B)
793 "Predicate to sort \"*Packages*\" buffer by the description column.
794This is used for `tabulated-list-format' in `package-menu-mode'."
795 (let ((dA (aref (cadr A) (if (cdr package-archives) 4 3)))
796 (dB (aref (cadr B) (if (cdr package-archives) 4 3))))
797 (if (string= dA dB)
798 (package-menu--name-predicate A B)
799 (string< dA dB))))
800
801(defun package-menu--name-predicate (A B)
802 "Predicate to sort \"*Packages*\" buffer by the name column.
803This is used for `tabulated-list-format' in `package-menu-mode'."
804 (string< (symbol-name (package-desc-name (car A)))
805 (symbol-name (package-desc-name (car B)))))
806
807(defun package-menu--archive-predicate (A B)
808 "Predicate to sort \"*Packages*\" buffer by the archive column.
809This is used for `tabulated-list-format' in `package-menu-mode'."
810 (let ((a (or (package-desc-archive (car A)) ""))
811 (b (or (package-desc-archive (car B)) "")))
812 (if (string= a b)
813 (package-menu--name-predicate A B)
814 (string< a b))))
815
816(defun package-menu--populate-new-package-list ()
817 "Decide which packages are new in `package-archive-contents'.
818Store this list in `package-menu--new-package-list'."
819 ;; Find which packages are new.
820 (when package-menu--old-archive-contents
821 (dolist (elt package-archive-contents)
822 (unless (assq (car elt) package-menu--old-archive-contents)
823 (push (car elt) package-menu--new-package-list)))
824 (setq package-menu--old-archive-contents nil)))
825
826(defun package-menu--find-and-notify-upgrades ()
827 "Notify the user of upgradable packages."
828 (when-let* ((upgrades (package-menu--find-upgrades)))
829 (message "Packages that can be upgraded: %d; type `%s' to mark for upgrading."
830 (length upgrades)
831 (substitute-command-keys "\\[package-menu-mark-upgrades]"))))
832
833
834(defun package-menu--post-refresh ()
835 "Revert \"*Packages*\" buffer and check for new packages and upgrades.
836Do nothing if there's no *Packages* buffer.
837
838This function is called after `package-refresh-contents' and it
839is added to `post-command-hook' by any function which alters the
840package database (`package-install' and `package-delete'). When
841run, it removes itself from `post-command-hook'."
842 (remove-hook 'post-command-hook #'package-menu--post-refresh)
843 (let ((buf (get-buffer "*Packages*")))
844 (when (buffer-live-p buf)
845 (with-current-buffer buf
846 (package-menu--populate-new-package-list)
847 (run-hooks 'tabulated-list-revert-hook)
848 (tabulated-list-print 'remember 'update)))))
849
850(defun package-menu--mark-or-notify-upgrades ()
851 "If there's a *Packages* buffer, check for upgrades and possibly mark them.
852Do nothing if there's no *Packages* buffer. If there are
853upgrades, mark them if `package-menu--mark-upgrades-pending' is
854non-nil, otherwise just notify the user that there are upgrades.
855This function is called after `package-refresh-contents'."
856 (let ((buf (get-buffer "*Packages*")))
857 (when (buffer-live-p buf)
858 (with-current-buffer buf
859 (if package-menu--mark-upgrades-pending
860 (package-menu--mark-upgrades-1)
861 (package-menu--find-and-notify-upgrades))))))
862
863;;;###autoload
864(defun list-packages (&optional no-fetch)
865 "Display a list of packages.
866This first fetches the updated list of packages before
867displaying, unless a prefix argument NO-FETCH is specified.
868The list is displayed in a buffer named `*Packages*', and
869includes the package's version, availability status, and a
870short description."
871 (interactive "P")
872 (require 'finder-inf nil t)
873 ;; Initialize the package system if necessary.
874 (unless package--initialized
875 (package-initialize t))
876 ;; Integrate the package-menu with updating the archives.
877 (add-hook 'package--post-download-archives-hook
878 #'package-menu--post-refresh)
879 (add-hook 'package--post-download-archives-hook
880 #'package-menu--mark-or-notify-upgrades 'append)
881 (add-hook 'package--post-download-archives-hook
882 #'package-menu--set-mode-line-format 'append)
883
884 ;; Generate the Package Menu.
885 (let ((buf (get-buffer-create "*Packages*")))
886 (with-current-buffer buf
887 ;; Since some packages have their descriptions include non-ASCII
888 ;; characters...
889 (setq buffer-file-coding-system 'utf-8)
890 (package-menu-mode)
891
892 ;; Fetch the remote list of packages.
893 (unless no-fetch (package-menu--refresh-contents))
894
895 ;; If we're not async, this would be redundant.
896 (when package-menu-async
897 (package-menu--generate nil t)))
898 ;; The package menu buffer has keybindings. If the user types
899 ;; `M-x list-packages', that suggests it should become current.
900 (pop-to-buffer-same-window buf)))
901
902;;;###autoload
903(defalias 'package-list-packages 'list-packages)
904
905;; Used in finder.el
906;;;###autoload
907(defun package-show-package-list (&optional packages keywords)
908 "Display PACKAGES in a *Packages* buffer.
909This is similar to `list-packages', but it does not fetch the
910updated list of packages, and it only displays packages with
911names in PACKAGES (which should be a list of symbols).
912
913When KEYWORDS are given, only packages with those KEYWORDS are
914shown."
915 (interactive)
916 (require 'finder-inf nil t)
917 (let* ((buf (get-buffer-create "*Packages*"))
918 (win (get-buffer-window buf)))
919 (with-current-buffer buf
920 (package-menu-mode)
921 (package-menu--generate nil packages keywords))
922 (if win
923 (select-window win)
924 (switch-to-buffer buf))))
925
926(defun package-menu--filter-by (predicate suffix)
927 "Filter \"*Packages*\" buffer by PREDICATE and add SUFFIX to header.
928PREDICATE is a function which will be called with one argument, a
929`package-desc' object, and returns t if that object should be
930listed in the Package Menu.
931
932SUFFIX is passed on to `package-menu--display' and is added to
933the header line of the first column."
934 ;; Update `tabulated-list-entries' so that it contains all
935 ;; packages before searching.
936 (package-menu--refresh t nil)
937 (let (found-entries)
938 (dolist (entry tabulated-list-entries)
939 (when (funcall predicate (car entry))
940 (push entry found-entries)))
941 (if found-entries
942 (progn
943 (setq tabulated-list-entries found-entries)
944 (package-menu--display t suffix))
945 (user-error "No packages found"))))
946
947(defun package-menu-filter-by-archive (archive)
948 "Filter the \"*Packages*\" buffer by ARCHIVE.
949Display only packages from package archive ARCHIVE.
950ARCHIVE can be the name of a single archive (a string), or
951a list of archive names. If ARCHIVE is nil or an empty
952string, show all packages.
953
954When called interactively, prompt for ARCHIVE. To specify
955several archives, type their names separated by commas."
956 (interactive (list (completing-read-multiple
957 "Filter by archive: "
958 (mapcar #'car package-archives)))
959 package-menu-mode)
960 (package--ensure-package-menu-mode)
961 (let ((archives (ensure-list archive)))
962 (package-menu--filter-by
963 (lambda (pkg-desc)
964 (let ((pkg-archive (package-desc-archive pkg-desc)))
965 (or (null archives)
966 (and pkg-archive
967 (member pkg-archive archives)))))
968 (concat "archive:" (string-join archives ",")))))
969
970(defun package-menu-filter-by-description (description)
971 "Filter the \"*Packages*\" buffer by the regexp DESCRIPTION.
972Display only packages whose description matches the regexp
973given as DESCRIPTION.
974
975When called interactively, prompt for DESCRIPTION.
976
977If DESCRIPTION is nil or the empty string, show all packages."
978 (interactive (list (read-regexp "Filter by description (regexp)"))
979 package-menu-mode)
980 (package--ensure-package-menu-mode)
981 (if (or (not description) (string-empty-p description))
982 (package-menu--generate t t)
983 (package-menu--filter-by (lambda (pkg-desc)
984 (string-match description
985 (package-desc-summary pkg-desc)))
986 (format "desc:%s" description))))
987
988(defun package--has-keyword-p (desc &optional keywords)
989 "Test if package DESC has any of the given KEYWORDS.
990When none are given, the package matches."
991 (if keywords
992 (let ((desc-keywords (and desc (package-desc--keywords desc)))
993 found)
994 (while (and (not found) keywords)
995 (let ((k (pop keywords)))
996 (setq found
997 (or (string= k (concat "arc:" (package-desc-archive desc)))
998 (string= k (concat "status:" (package-desc-status desc)))
999 (member k desc-keywords)))))
1000 found)
1001 t))
1002
1003(defun package-all-keywords ()
1004 "Collect all package keywords."
1005 (let ((key-list))
1006 (package--mapc (lambda (desc)
1007 (setq key-list (append (package-desc--keywords desc)
1008 key-list))))
1009 key-list))
1010
1011(defun package-menu-filter-by-keyword (keyword)
1012 "Filter the \"*Packages*\" buffer by KEYWORD.
1013Display only packages whose keywords match the specified KEYWORD.
1014KEYWORD can be a string or a list of strings. If KEYWORD is nil
1015or the empty string, show all packages.
1016
1017In addition to package keywords, KEYWORD can include the name(s)
1018of archive(s) and the package status, such as \"available\"
1019or \"built-in\" or \"obsolete\".
1020
1021When called interactively, prompt for KEYWORD. To specify several
1022keywords, type them separated by commas."
1023 (interactive (list (completing-read-multiple
1024 "Keywords: "
1025 (package-all-keywords)))
1026 package-menu-mode)
1027 (package--ensure-package-menu-mode)
1028 (when (stringp keyword)
1029 (setq keyword (list keyword)))
1030 (if (not keyword)
1031 (package-menu--generate t t)
1032 (package-menu--filter-by (lambda (pkg-desc)
1033 (package--has-keyword-p pkg-desc keyword))
1034 (concat "keyword:" (string-join keyword ",")))))
1035
1036(define-obsolete-function-alias
1037 'package-menu-filter #'package-menu-filter-by-keyword "27.1")
1038
1039(defun package-menu-filter-by-name-or-description (name-or-description)
1040 "Filter the \"*Packages*\" buffer by the regexp NAME-OR-DESCRIPTION.
1041Display only packages whose name or description matches the regexp
1042NAME-OR-DESCRIPTION.
1043
1044When called interactively, prompt for NAME-OR-DESCRIPTION.
1045
1046If NAME-OR-DESCRIPTION is nil or the empty string, show all
1047packages."
1048 (interactive (list (read-regexp "Filter by name or description (regexp)"))
1049 package-menu-mode)
1050 (package--ensure-package-menu-mode)
1051 (if (or (not name-or-description) (string-empty-p name-or-description))
1052 (package-menu--generate t t)
1053 (package-menu--filter-by (lambda (pkg-desc)
1054 (or (string-match name-or-description
1055 (package-desc-summary pkg-desc))
1056 (string-match name-or-description
1057 (symbol-name
1058 (package-desc-name pkg-desc)))))
1059 (format "name-or-desc:%s" name-or-description))))
1060
1061(defun package-menu-filter-by-name (name)
1062 "Filter the \"*Packages*\" buffer by the regexp NAME.
1063Display only packages whose name matches the regexp NAME.
1064
1065When called interactively, prompt for NAME.
1066
1067If NAME is nil or the empty string, show all packages."
1068 (interactive (list (read-regexp "Filter by name (regexp)"))
1069 package-menu-mode)
1070 (package--ensure-package-menu-mode)
1071 (if (or (not name) (string-empty-p name))
1072 (package-menu--generate t t)
1073 (package-menu--filter-by (lambda (pkg-desc)
1074 (string-match-p name (symbol-name
1075 (package-desc-name pkg-desc))))
1076 (format "name:%s" name))))
1077
1078(defun package-menu-filter-by-status (status)
1079 "Filter the \"*Packages*\" buffer by STATUS.
1080Display only packages with specified STATUS.
1081STATUS can be a single status, a string, or a list of strings.
1082If STATUS is nil or the empty string, show all packages.
1083
1084When called interactively, prompt for STATUS. To specify
1085several possible status values, type them separated by commas."
1086 (interactive (list (completing-read "Filter by status: "
1087 '("avail-obso"
1088 "available"
1089 "built-in"
1090 "dependency"
1091 "disabled"
1092 "external"
1093 "held"
1094 "incompat"
1095 "installed"
1096 "source"
1097 "new"
1098 "unsigned")))
1099 package-menu-mode)
1100 (package--ensure-package-menu-mode)
1101 (if (or (not status) (string-empty-p status))
1102 (package-menu--generate t t)
1103 (let ((status-list
1104 (if (listp status)
1105 status
1106 (split-string status ","))))
1107 (package-menu--filter-by
1108 (lambda (pkg-desc)
1109 (member (package-desc-status pkg-desc) status-list))
1110 (format "status:%s" (string-join status-list ","))))))
1111
1112(defun package-menu-filter-by-version (version predicate)
1113 "Filter the \"*Packages*\" buffer by VERSION and PREDICATE.
1114Display only packages whose version satisfies the condition
1115defined by VERSION and PREDICATE.
1116
1117When called interactively, prompt for one of the comparison operators
1118`<', `>' or `=', and for a version. Show only packages whose version
1119is lower (`<'), equal (`=') or higher (`>') than the specified VERSION.
1120
1121When called from Lisp, VERSION should be a version string and
1122PREDICATE should be the symbol `=', `<' or `>'.
1123
1124If VERSION is nil or the empty string, show all packages."
1125 (interactive (let ((choice (intern
1126 (char-to-string
1127 (read-char-choice
1128 "Filter by version? [Type =, <, > or q] "
1129 '(?< ?> ?= ?q))))))
1130 (if (eq choice 'q)
1131 '(quit nil)
1132 (list (read-from-minibuffer
1133 (concat "Filter by version ("
1134 (pcase choice
1135 ('= "= equal to")
1136 ('< "< less than")
1137 ('> "> greater than"))
1138 "): "))
1139 choice)))
1140 package-menu-mode)
1141 (package--ensure-package-menu-mode)
1142 (unless (equal predicate 'quit)
1143 (if (or (not version) (string-empty-p version))
1144 (package-menu--generate t t)
1145 (package-menu--filter-by
1146 (let ((fun (pcase predicate
1147 ('= #'version-list-=)
1148 ('< #'version-list-<)
1149 ('> (lambda (a b) (not (version-list-<= a b))))
1150 (_ (error "Unknown predicate: %s" predicate))))
1151 (ver (version-to-list version)))
1152 (lambda (pkg-desc)
1153 (funcall fun (package-desc-version pkg-desc) ver)))
1154 (format "versions:%s%s" predicate version)))))
1155
1156(defun package-menu-filter-marked ()
1157 "Filter \"*Packages*\" buffer by non-empty mark.
1158Show only the packages that have been marked for installation or deletion.
1159Unlike other filters, this leaves the marks intact."
1160 (interactive nil package-menu-mode)
1161 (package--ensure-package-menu-mode)
1162 (widen)
1163 (let (found-entries mark pkg-id entry marks)
1164 (save-excursion
1165 (goto-char (point-min))
1166 (while (not (eobp))
1167 (setq mark (char-after))
1168 (unless (eq mark ?\s)
1169 (setq pkg-id (tabulated-list-get-id))
1170 (setq entry (package-menu--print-info-simple pkg-id))
1171 (push entry found-entries)
1172 ;; remember the mark
1173 (push (cons pkg-id mark) marks))
1174 (forward-line))
1175 (if found-entries
1176 (progn
1177 (setq tabulated-list-entries found-entries)
1178 (package-menu--display t nil)
1179 ;; redo the marks, but we must remember the marks!!
1180 (goto-char (point-min))
1181 (while (not (eobp))
1182 (setq mark (cdr (assq (tabulated-list-get-id) marks)))
1183 (tabulated-list-put-tag (char-to-string mark) t)))
1184 (user-error "No packages found")))))
1185
1186(defun package-menu-filter-upgradable ()
1187 "Filter \"*Packages*\" buffer to show only upgradable packages."
1188 (interactive nil package-menu-mode)
1189 (let ((pkgs (mapcar #'car (package-menu--find-upgrades))))
1190 (package-menu--filter-by
1191 (lambda (pkg)
1192 (memql (package-desc-name pkg) pkgs))
1193 "upgradable")))
1194
1195(defun package-menu-clear-filter ()
1196 "Clear any filter currently applied to the \"*Packages*\" buffer."
1197 (interactive nil package-menu-mode)
1198 (package--ensure-package-menu-mode)
1199 (package-menu--generate t t))
1200
1201(defun package-list-packages-no-fetch ()
1202 "Display a list of packages.
1203Does not fetch the updated list of packages before displaying.
1204The list is displayed in a buffer named `*Packages*'."
1205 (interactive)
1206 (list-packages t))
1207
1208;;;###autoload
1209(defun package-get-version ()
1210 "Return the version number of the package in which this is used.
1211Assumes it is used from an Elisp file placed inside the top-level directory
1212of an installed ELPA package.
1213The return value is a string (or nil in case we can't find it).
1214It works in more cases if the call is in the file which contains
1215the `Version:' header."
1216 ;; In a sense, this is a lie, but it does just what we want: precomputes
1217 ;; the version at compile time and hardcodes it into the .elc file!
1218 (declare (pure t))
1219 ;; Hack alert!
1220 (let ((file (or (macroexp-file-name) buffer-file-name)))
1221 (cond
1222 ((null file) nil)
1223 ;; Packages are normally installed into directories named "<pkg>-<vers>",
1224 ;; so get the version number from there.
1225 ((string-match "/[^/]+-\\([0-9]\\(?:[0-9.]\\|pre\\|beta\\|alpha\\|snapshot\\)+\\)/[^/]+\\'" file)
1226 (match-string 1 file))
1227 ;; For packages run straight from the an elpa.git clone, there's no
1228 ;; "-<vers>" in the directory name, so we have to fetch the version
1229 ;; the hard way.
1230 (t
1231 (let* ((pkgdir (file-name-directory file))
1232 (pkgname (file-name-nondirectory (directory-file-name pkgdir)))
1233 (mainfile (expand-file-name (concat pkgname ".el") pkgdir)))
1234 (unless (file-readable-p mainfile) (setq mainfile file))
1235 (when (file-readable-p mainfile)
1236 (lm-package-version mainfile)))))))
1237
1238
1239
1240;;;; Package menu mode.
1241
1242(defvar-keymap package-menu-mode-map
1243 :doc "Local keymap for `package-menu-mode' buffers."
1244 :parent tabulated-list-mode-map
1245 "C-m" #'package-menu-describe-package
1246 "u" #'package-menu-mark-unmark
1247 "DEL" #'package-menu-backup-unmark
1248 "d" #'package-menu-mark-delete
1249 "i" #'package-menu-mark-install
1250 "U" #'package-menu-mark-upgrades
1251 "r" #'revert-buffer
1252 "~" #'package-menu-mark-obsolete-for-deletion
1253 "w" #'package-browse-url
1254 "b" #'package-report-bug
1255 "x" #'package-menu-execute
1256 "h" #'package-menu-quick-help
1257 "H" #'package-menu-hide-package
1258 "?" #'package-menu-describe-package
1259 "(" #'package-menu-toggle-hiding
1260 "/ /" #'package-menu-clear-filter
1261 "/ a" #'package-menu-filter-by-archive
1262 "/ d" #'package-menu-filter-by-description
1263 "/ k" #'package-menu-filter-by-keyword
1264 "/ N" #'package-menu-filter-by-name-or-description
1265 "/ n" #'package-menu-filter-by-name
1266 "/ s" #'package-menu-filter-by-status
1267 "/ v" #'package-menu-filter-by-version
1268 "/ m" #'package-menu-filter-marked
1269 "/ u" #'package-menu-filter-upgradable)
1270
1271(easy-menu-define package-menu-mode-menu package-menu-mode-map
1272 "Menu for `package-menu-mode'."
1273 '("Package"
1274 ["Describe Package" package-menu-describe-package :help "Display information about this package"]
1275 ["Open Package Website" package-browse-url
1276 :help "Open the website of this package"]
1277 ["Help" package-menu-quick-help :help "Show short key binding help for package-menu-mode"]
1278 "--"
1279 ["Refresh Package List" revert-buffer
1280 :help "Redownload the package archive(s)"
1281 :active (not package--downloads-in-progress)]
1282 ["Execute Marked Actions" package-menu-execute :help "Perform all the marked actions"]
1283
1284 "--"
1285 ["Mark All Available Upgrades" package-menu-mark-upgrades
1286 :help "Mark packages that have a newer version for upgrading"
1287 :active (not package--downloads-in-progress)]
1288 ["Mark All Obsolete for Deletion" package-menu-mark-obsolete-for-deletion :help "Mark all obsolete packages for deletion"]
1289 ["Mark for Install" package-menu-mark-install :help "Mark a package for installation and move to the next line"]
1290 ["Mark for Deletion" package-menu-mark-delete :help "Mark a package for deletion and move to the next line"]
1291 ["Unmark" package-menu-mark-unmark :help "Clear any marks on a package and move to the next line"]
1292
1293 "--"
1294 ("Filter Packages"
1295 ["Filter by Archive" package-menu-filter-by-archive
1296 :help
1297 "Prompt for archive(s), display only packages from those archives"]
1298 ["Filter by Description" package-menu-filter-by-description
1299 :help
1300 "Prompt for regexp, display only packages with matching description"]
1301 ["Filter by Keyword" package-menu-filter-by-keyword
1302 :help
1303 "Prompt for keyword(s), display only packages with matching keywords"]
1304 ["Filter by Name" package-menu-filter-by-name
1305 :help
1306 "Prompt for regexp, display only packages whose names match the regexp"]
1307 ["Filter by Name or Description" package-menu-filter-by-name-or-description
1308 :help
1309 "Prompt for regexp, display only packages whose name or description matches"]
1310 ["Filter by Status" package-menu-filter-by-status
1311 :help
1312 "Prompt for status(es), display only packages with those statuses"]
1313 ["Filter by Upgrades available" package-menu-filter-upgradable
1314 :help "Display only installed packages for which upgrades are available"]
1315 ["Filter by Version" package-menu-filter-by-version
1316 :help
1317 "Prompt for version and comparison operator, display only packages of matching versions"]
1318 ["Filter Marked" package-menu-filter-marked
1319 :help "Display only packages marked for installation or deletion"]
1320 ["Clear Filter" package-menu-clear-filter
1321 :help "Clear package list filtering, display the entire list again"])
1322
1323 ["Hide by Regexp" package-menu-hide-package
1324 :help "Toggle visibility of obsolete and unwanted packages"]
1325 ["Display Older Versions" package-menu-toggle-hiding
1326 :style toggle :selected (not package-menu--hide-packages)
1327 :help "Display package even if a newer version is already installed"]
1328
1329 "--"
1330 ["Quit" quit-window :help "Quit package selection"]
1331 ["Customize" (customize-group 'package)]))
1332
1333(defconst package-menu-mode-line-format
1334 '((package-menu-mode-line-info
1335 (:eval (symbol-value 'package-menu-mode-line-info)))))
1336
1337(defvar-local package-menu-mode-line-info nil
1338 "Variable which stores package-menu mode-line format.")
1339
1340(defun package-menu--set-mode-line-format ()
1341 "Display package-menu mode-line."
1342 (when-let* ((buf (get-buffer "*Packages*"))
1343 ((buffer-live-p buf)))
1344 (with-current-buffer buf
1345 (setq package-menu-mode-line-info
1346 (let ((installed 0)
1347 (new 0)
1348 (total (length package-archive-contents))
1349 (to-upgrade (length (package-menu--find-upgrades)))
1350 (total-help "Total number of packages of all package archives")
1351 (installed-help "Total number of packages installed")
1352 (upgrade-help "Total number of packages to upgrade")
1353 (new-help "Total number of packages added recently"))
1354
1355 (save-excursion
1356 (goto-char (point-min))
1357 (while (not (eobp))
1358 (let ((status (package-menu-get-status)))
1359 (cond
1360 ((member status
1361 '("installed" "dependency" "unsigned"))
1362 (setq installed (1+ installed)))
1363 ((equal status "new")
1364 (setq new (1+ new)))))
1365 (forward-line)))
1366
1367 (setq installed (number-to-string installed))
1368 (setq total (number-to-string total))
1369 (setq to-upgrade (number-to-string to-upgrade))
1370
1371 (list
1372 " ["
1373 (propertize "Total: " 'help-echo total-help)
1374 (propertize total
1375 'help-echo total-help
1376 'face 'package-mode-line-total)
1377 " / "
1378 (propertize "Installed: " 'help-echo installed-help)
1379 (propertize installed
1380 'help-echo installed-help
1381 'face 'package-mode-line-installed)
1382 " / "
1383 (propertize "To Upgrade: " 'help-echo upgrade-help)
1384 (propertize to-upgrade
1385 'help-echo upgrade-help
1386 'face 'package-mode-line-to-upgrade)
1387 (when (> new 0)
1388 (concat
1389 " / "
1390 (propertize "New: " 'help-echo new-help)
1391 (propertize (number-to-string new)
1392 'help-echo new-help
1393 'face 'package-mode-line-new)))
1394 "] "))))))
1395(defvar package-menu--tool-bar-map
1396 (let ((map (make-sparse-keymap)))
1397 (tool-bar-local-item-from-menu
1398 #'package-menu-execute "package-menu/execute"
1399 map package-menu-mode-map)
1400 (define-key-after map [separator-1] menu-bar-separator)
1401 (tool-bar-local-item-from-menu
1402 #'package-menu-mark-unmark "package-menu/unmark"
1403 map package-menu-mode-map)
1404 (tool-bar-local-item-from-menu
1405 #'package-menu-mark-install "package-menu/install"
1406 map package-menu-mode-map)
1407 (tool-bar-local-item-from-menu
1408 #'package-menu-mark-delete "package-menu/delete"
1409 map package-menu-mode-map)
1410 (tool-bar-local-item-from-menu
1411 #'package-menu-describe-package "package-menu/info"
1412 map package-menu-mode-map)
1413 (tool-bar-local-item-from-menu
1414 #'package-browse-url "package-menu/url"
1415 map package-menu-mode-map)
1416 (tool-bar-local-item
1417 "package-menu/upgrade" 'package-upgrade-all
1418 'package-upgrade-all
1419 map :help "Upgrade all the packages")
1420 (define-key-after map [separator-2] menu-bar-separator)
1421 (tool-bar-local-item
1422 "search" 'isearch-forward 'search map
1423 :help "Search" :vert-only t)
1424 (tool-bar-local-item-from-menu
1425 #'revert-buffer "refresh"
1426 map package-menu-mode-map)
1427 (tool-bar-local-item-from-menu
1428 #'quit-window "close"
1429 map package-menu-mode-map)
1430 map))
1431
1432(define-derived-mode package-menu-mode tabulated-list-mode "Package Menu"
1433 "Major mode for browsing a list of packages.
1434The most useful commands here are:
1435
1436 `x': Install the package under point if it isn't already installed,
1437 and delete it if it's already installed,
1438 `i': mark a package for installation, and
1439 `d': mark a package for deletion. Use the `x' command to perform the
1440 actions on the marked files.
1441\\<package-menu-mode-map>
1442\\{package-menu-mode-map}"
1443 :interactive nil
1444 (setq mode-line-process '((package--downloads-in-progress ":Loading")
1445 (package-menu--transaction-status
1446 package-menu--transaction-status)))
1447 (setq-local mode-line-misc-info
1448 (append
1449 mode-line-misc-info
1450 package-menu-mode-line-format))
1451 (setq-local tool-bar-map package-menu--tool-bar-map)
1452 (setq tabulated-list-format
1453 `[("Package" ,package-name-column-width package-menu--name-predicate)
1454 ("Version" ,package-version-column-width package-menu--version-predicate)
1455 ("Status" ,package-status-column-width package-menu--status-predicate)
1456 ("Archive" ,package-archive-column-width package-menu--archive-predicate)
1457 ("Description" 0 package-menu--description-predicate)])
1458 (setq tabulated-list-padding 2)
1459 (setq tabulated-list-sort-key (cons "Status" nil))
1460 (add-hook 'tabulated-list-revert-hook #'package-menu--refresh nil t)
1461 (tabulated-list-init-header)
1462 (setq revert-buffer-function 'package-menu--refresh-contents)
1463 (setf imenu-prev-index-position-function
1464 #'package--imenu-prev-index-position-function)
1465 (setf imenu-extract-index-name-function
1466 #'package--imenu-extract-index-name-function))
1467
1468(defvar package-menu--hide-packages t
1469 "Whether available obsolete packages should be hidden.
1470Can be toggled with \\<package-menu-mode-map> \\[package-menu-toggle-hiding].
1471Installed obsolete packages are always displayed.")
1472
1473(defun package-menu--refresh (&optional packages keywords)
1474 "Re-populate the `tabulated-list-entries'.
1475PACKAGES should be nil or t, which means to display all known packages.
1476KEYWORDS should be nil or a list of keywords."
1477 ;; Construct list of (PKG-DESC . STATUS).
1478 (unless packages (setq packages t))
1479 (let ((hidden-names (mapconcat #'identity package-hidden-regexps "\\|"))
1480 info-list)
1481 ;; Installed packages:
1482 (dolist (elt package-alist)
1483 (let ((name (car elt)))
1484 (when (or (eq packages t) (memq name packages))
1485 (dolist (pkg (cdr elt))
1486 (when (package--has-keyword-p pkg keywords)
1487 (push pkg info-list))))))
1488
1489 ;; Built-in packages:
1490 (dolist (elt package--builtins)
1491 (let ((pkg (package--from-builtin elt))
1492 (name (car elt)))
1493 (when (not (eq name 'emacs)) ; Hide the `emacs' package.
1494 (when (and (package--has-keyword-p pkg keywords)
1495 (or package-list-unversioned
1496 (package--bi-desc-version (cdr elt)))
1497 (or (eq packages t) (memq name packages)))
1498 (push pkg info-list)))))
1499
1500 ;; Available and disabled packages:
1501 (unless (equal package--old-archive-priorities package-archive-priorities)
1502 (package-read-all-archive-contents))
1503 (dolist (elt package-archive-contents)
1504 (let ((name (car elt)))
1505 ;; To be displayed it must be in PACKAGES;
1506 (when (and (or (eq packages t) (memq name packages))
1507 ;; and we must either not be hiding anything,
1508 (or (not package-menu--hide-packages)
1509 (not package-hidden-regexps)
1510 ;; or just not hiding this specific package.
1511 (not (string-match hidden-names (symbol-name name)))))
1512 ;; Hide available-obsolete or low-priority packages.
1513 (dolist (pkg (package--remove-hidden (cdr elt)))
1514 (when (package--has-keyword-p pkg keywords)
1515 (push pkg info-list))))))
1516
1517 ;; Print the result.
1518 (tabulated-list-init-header)
1519 (setq tabulated-list-entries
1520 (mapcar #'package-menu--print-info-simple info-list))))
1521
1522(defun package--remove-hidden (pkg-list)
1523 "Filter PKG-LIST according to `package-archive-priorities'.
1524PKG-LIST must be a list of `package-desc' objects, all with the
1525same name, sorted by decreasing `package-desc-priority-version'.
1526Return a list of packages tied for the highest priority according
1527to their archives."
1528 (when pkg-list
1529 ;; Variable toggled with `package-menu-toggle-hiding'.
1530 (if (not package-menu--hide-packages)
1531 pkg-list
1532 (let ((installed (cadr (assq (package-desc-name (car pkg-list))
1533 package-alist))))
1534 (when installed
1535 (setq pkg-list
1536 (let ((ins-version (package-desc-version installed)))
1537 (cl-remove-if (lambda (p) (version-list-< (package-desc-version p)
1538 ins-version))
1539 pkg-list))))
1540 (let ((filtered-by-priority
1541 (cond
1542 ((not package-menu-hide-low-priority)
1543 pkg-list)
1544 ((eq package-menu-hide-low-priority 'archive)
1545 (let (max-priority out)
1546 (while pkg-list
1547 (let ((p (pop pkg-list)))
1548 (let ((priority (package-desc-priority p)))
1549 (if (and max-priority (< priority max-priority))
1550 (setq pkg-list nil)
1551 (push p out)
1552 (setq max-priority priority)))))
1553 (nreverse out)))
1554 (pkg-list
1555 (list (car pkg-list))))))
1556 (if (not installed)
1557 filtered-by-priority
1558 (let ((ins-version (package-desc-version installed)))
1559 (cl-remove-if (lambda (p) (or (version-list-= (package-desc-version p)
1560 ins-version)
1561 (package-vc-p installed)))
1562 filtered-by-priority))))))))
1563
1564(defun package-menu-toggle-hiding ()
1565 "In Package Menu, toggle visibility of obsolete available packages.
1566
1567Also hide packages whose name matches a regexp in user option
1568`package-hidden-regexps' (a list). To add regexps to this list,
1569use `package-menu-hide-package'."
1570 (interactive nil package-menu-mode)
1571 (package--ensure-package-menu-mode)
1572 (setq package-menu--hide-packages
1573 (not package-menu--hide-packages))
1574 (if package-menu--hide-packages
1575 (message "Hiding obsolete or unwanted packages")
1576 (message "Displaying all packages"))
1577 (revert-buffer nil 'no-confirm))
1578
1579(provide 'package-menu)
1580;;; package-menu.el ends here