diff options
| author | Philip Kaludercic | 2025-08-06 15:12:55 +0200 |
|---|---|---|
| committer | Philip Kaludercic | 2025-08-07 15:55:01 +0200 |
| commit | bdd0220f6571906b0618924274ec12fbb876a09e (patch) | |
| tree | 8c7a38e924ddb329f226a98eeec43dc331e8a67d /lisp/package/package-menu.el | |
| parent | 5c153cfb9620baf44dd388bb509c5aca82e377e9 (diff) | |
| download | emacs-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.el | 1580 |
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. | ||
| 40 | Currently, only the refreshing of archive contents supports | ||
| 41 | asynchronous operations. Package transactions are still done | ||
| 42 | synchronously." | ||
| 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. | ||
| 48 | A package is considered low priority if there's another version | ||
| 49 | of 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 | |||
| 56 | This 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 | |||
| 61 | This variable has no effect if `package-menu--hide-packages' is | ||
| 62 | nil, 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. | ||
| 71 | If the name of a package matches any of these regexps it is | ||
| 72 | omitted from the package menu. To toggle this, type \\[package-menu-toggle-hiding]. | ||
| 73 | |||
| 74 | Values 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 | |||
| 82 | If non-nil, and no packages are marked for installation or | ||
| 83 | deletion, \\<package-menu-mode-map>\\[package-menu-execute] will operate on the current package at point, | ||
| 84 | see `package-menu-execute' for details. | ||
| 85 | The default is t. Set to nil to get back the original behavior | ||
| 86 | of having `package-menu-execute' signal an error when no packages | ||
| 87 | are 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 | ||
| 114 | SECONDARY (interactively, the prefix), use the secondary browser. | ||
| 115 | DESC 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. | ||
| 130 | This 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. | ||
| 137 | This function is used as a value for | ||
| 138 | `imenu-extract-index-name-function'. Point should be at the | ||
| 139 | beginning 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. | ||
| 148 | If REMEMBER-POS is non-nil, keep point on the same entry. | ||
| 149 | |||
| 150 | If SUFFIX is non-nil, append that to \"Package\" for the first | ||
| 151 | column 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. | ||
| 161 | If REMEMBER-POS is non-nil, keep point on the same entry. | ||
| 162 | PACKAGES should be t, which means to display all known packages, | ||
| 163 | or a list of package names (symbols) to display. | ||
| 164 | |||
| 165 | With KEYWORDS given, only packages with those keywords are | ||
| 166 | shown." | ||
| 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'. | ||
| 175 | PKG has the form (PKG-DESC . STATUS). | ||
| 176 | Return (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'. | ||
| 293 | PKG is a `package-desc' object. | ||
| 294 | Return (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. | ||
| 342 | Fetch 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 | ||
| 346 | function. 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. | ||
| 370 | Prompt for the regexp to match against package names. | ||
| 371 | The default regexp will hide only the package whose name is at point. | ||
| 372 | |||
| 373 | The regexp is added to the list in the user option | ||
| 374 | `package-hidden-regexps' and saved for future sessions. | ||
| 375 | |||
| 376 | To unhide a package, type | ||
| 377 | `\\[customize-variable] RET package-hidden-regexps', and then modify | ||
| 378 | the regexp such that it no longer matches the package's name. | ||
| 379 | |||
| 380 | Type \\[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. | ||
| 405 | The current package is the package at point. | ||
| 406 | If optional arg BUTTON is non-nil, describe its associated | ||
| 407 | package(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. | ||
| 418 | The 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. | ||
| 429 | The 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. | ||
| 439 | The 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'. | ||
| 487 | You 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. | ||
| 504 | The alist has the same form as `package-alist', namely a list | ||
| 505 | of elements of the form (PKG . DESCS), but where DESCS is the `package-desc' | ||
| 506 | object 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. | ||
| 534 | Implementation 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. | ||
| 557 | For each installed package for which a newer version is available, | ||
| 558 | place an (I)nstall flag on the available version and a (D)elete flag | ||
| 559 | on the installed version. A subsequent \\[package-menu-execute] command will upgrade | ||
| 560 | the marked packages. | ||
| 561 | |||
| 562 | If there's an async refresh operation in progress, the flags will | ||
| 563 | be placed as part of `package-menu--post-refresh' instead of | ||
| 564 | immediately." | ||
| 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. | ||
| 574 | PACKAGES is a list of `package-desc' objects. | ||
| 575 | Formats the returned string to be usable in a minibuffer | ||
| 576 | prompt (see `package-menu--prompt-transaction-p'). | ||
| 577 | |||
| 578 | If INCLUDE-DEPENDENCIES, also include the number of uninstalled | ||
| 579 | dependencies." | ||
| 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. | ||
| 603 | DELETE, INSTALL, and UPGRADE are lists of `package-desc' objects. | ||
| 604 | Either 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. | ||
| 621 | Alist contains three entries, upgrade, delete, and install, each | ||
| 622 | with a list of package names. | ||
| 623 | |||
| 624 | The upgrade entry contains any `package-desc' objects in INSTALL | ||
| 625 | whose name coincides with an object in DELETE. The delete and | ||
| 626 | the install entries are the same as DELETE and INSTALL with such | ||
| 627 | objects 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. | ||
| 638 | Return 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. | ||
| 669 | ADD and REMOVE must be disjoint lists of package names (or | ||
| 670 | `package-desc' objects) to be added and removed to the selected | ||
| 671 | packages 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. | ||
| 684 | Packages marked for installation are downloaded and installed, | ||
| 685 | packages marked for deletion are removed, and packages marked for | ||
| 686 | upgrading are downloaded and upgraded. | ||
| 687 | |||
| 688 | If no packages are marked, the action taken depends on the state | ||
| 689 | of the current package, the one at point. If it's not already | ||
| 690 | installed, this command will install the package; if it's installed, | ||
| 691 | the command will delete the package. | ||
| 692 | |||
| 693 | Optional argument NOQUERY non-nil means do not ask the user to | ||
| 694 | confirm the installations/deletions; this is always nil in interactive | ||
| 695 | invocations." | ||
| 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. | ||
| 751 | This 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. | ||
| 760 | This 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. | ||
| 794 | This 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. | ||
| 803 | This 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. | ||
| 809 | This 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'. | ||
| 818 | Store 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. | ||
| 836 | Do nothing if there's no *Packages* buffer. | ||
| 837 | |||
| 838 | This function is called after `package-refresh-contents' and it | ||
| 839 | is added to `post-command-hook' by any function which alters the | ||
| 840 | package database (`package-install' and `package-delete'). When | ||
| 841 | run, 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. | ||
| 852 | Do nothing if there's no *Packages* buffer. If there are | ||
| 853 | upgrades, mark them if `package-menu--mark-upgrades-pending' is | ||
| 854 | non-nil, otherwise just notify the user that there are upgrades. | ||
| 855 | This 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. | ||
| 866 | This first fetches the updated list of packages before | ||
| 867 | displaying, unless a prefix argument NO-FETCH is specified. | ||
| 868 | The list is displayed in a buffer named `*Packages*', and | ||
| 869 | includes the package's version, availability status, and a | ||
| 870 | short 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. | ||
| 909 | This is similar to `list-packages', but it does not fetch the | ||
| 910 | updated list of packages, and it only displays packages with | ||
| 911 | names in PACKAGES (which should be a list of symbols). | ||
| 912 | |||
| 913 | When KEYWORDS are given, only packages with those KEYWORDS are | ||
| 914 | shown." | ||
| 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. | ||
| 928 | PREDICATE is a function which will be called with one argument, a | ||
| 929 | `package-desc' object, and returns t if that object should be | ||
| 930 | listed in the Package Menu. | ||
| 931 | |||
| 932 | SUFFIX is passed on to `package-menu--display' and is added to | ||
| 933 | the 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. | ||
| 949 | Display only packages from package archive ARCHIVE. | ||
| 950 | ARCHIVE can be the name of a single archive (a string), or | ||
| 951 | a list of archive names. If ARCHIVE is nil or an empty | ||
| 952 | string, show all packages. | ||
| 953 | |||
| 954 | When called interactively, prompt for ARCHIVE. To specify | ||
| 955 | several 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. | ||
| 972 | Display only packages whose description matches the regexp | ||
| 973 | given as DESCRIPTION. | ||
| 974 | |||
| 975 | When called interactively, prompt for DESCRIPTION. | ||
| 976 | |||
| 977 | If 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. | ||
| 990 | When 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. | ||
| 1013 | Display only packages whose keywords match the specified KEYWORD. | ||
| 1014 | KEYWORD can be a string or a list of strings. If KEYWORD is nil | ||
| 1015 | or the empty string, show all packages. | ||
| 1016 | |||
| 1017 | In addition to package keywords, KEYWORD can include the name(s) | ||
| 1018 | of archive(s) and the package status, such as \"available\" | ||
| 1019 | or \"built-in\" or \"obsolete\". | ||
| 1020 | |||
| 1021 | When called interactively, prompt for KEYWORD. To specify several | ||
| 1022 | keywords, 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. | ||
| 1041 | Display only packages whose name or description matches the regexp | ||
| 1042 | NAME-OR-DESCRIPTION. | ||
| 1043 | |||
| 1044 | When called interactively, prompt for NAME-OR-DESCRIPTION. | ||
| 1045 | |||
| 1046 | If NAME-OR-DESCRIPTION is nil or the empty string, show all | ||
| 1047 | packages." | ||
| 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. | ||
| 1063 | Display only packages whose name matches the regexp NAME. | ||
| 1064 | |||
| 1065 | When called interactively, prompt for NAME. | ||
| 1066 | |||
| 1067 | If 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. | ||
| 1080 | Display only packages with specified STATUS. | ||
| 1081 | STATUS can be a single status, a string, or a list of strings. | ||
| 1082 | If STATUS is nil or the empty string, show all packages. | ||
| 1083 | |||
| 1084 | When called interactively, prompt for STATUS. To specify | ||
| 1085 | several 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. | ||
| 1114 | Display only packages whose version satisfies the condition | ||
| 1115 | defined by VERSION and PREDICATE. | ||
| 1116 | |||
| 1117 | When called interactively, prompt for one of the comparison operators | ||
| 1118 | `<', `>' or `=', and for a version. Show only packages whose version | ||
| 1119 | is lower (`<'), equal (`=') or higher (`>') than the specified VERSION. | ||
| 1120 | |||
| 1121 | When called from Lisp, VERSION should be a version string and | ||
| 1122 | PREDICATE should be the symbol `=', `<' or `>'. | ||
| 1123 | |||
| 1124 | If 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. | ||
| 1158 | Show only the packages that have been marked for installation or deletion. | ||
| 1159 | Unlike 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. | ||
| 1203 | Does not fetch the updated list of packages before displaying. | ||
| 1204 | The 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. | ||
| 1211 | Assumes it is used from an Elisp file placed inside the top-level directory | ||
| 1212 | of an installed ELPA package. | ||
| 1213 | The return value is a string (or nil in case we can't find it). | ||
| 1214 | It works in more cases if the call is in the file which contains | ||
| 1215 | the `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. | ||
| 1434 | The 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. | ||
| 1470 | Can be toggled with \\<package-menu-mode-map> \\[package-menu-toggle-hiding]. | ||
| 1471 | Installed obsolete packages are always displayed.") | ||
| 1472 | |||
| 1473 | (defun package-menu--refresh (&optional packages keywords) | ||
| 1474 | "Re-populate the `tabulated-list-entries'. | ||
| 1475 | PACKAGES should be nil or t, which means to display all known packages. | ||
| 1476 | KEYWORDS 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'. | ||
| 1524 | PKG-LIST must be a list of `package-desc' objects, all with the | ||
| 1525 | same name, sorted by decreasing `package-desc-priority-version'. | ||
| 1526 | Return a list of packages tied for the highest priority according | ||
| 1527 | to 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 | |||
| 1567 | Also hide packages whose name matches a regexp in user option | ||
| 1568 | `package-hidden-regexps' (a list). To add regexps to this list, | ||
| 1569 | use `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 | ||