el-get/el-get-list-packages.el

452 lines
17 KiB
EmacsLisp
Raw Permalink Normal View History

2015-08-25 23:24:48 +08:00
;;; el-get-list-packages.el --- Manage the external elisp bits and pieces you depend upon
;;
;; Copyright (C) 2010-2011 Dimitri Fontaine
;;
;; Author: Dimitri Fontaine <dim@tapoueh.org>
;; URL: http://www.emacswiki.org/emacs/el-get
;; GIT: https://github.com/dimitri/el-get
;; Licence: WTFPL, grab your copy here: http://sam.zoy.org/wtfpl/
;;
;; This file is NOT part of GNU Emacs.
;;
;; Install
;; Please see the README.md file from the same distribution
;;
;;
;; Description of packages. (Code based on `describe-function').
;;
(require 'el-get-core)
(require 'cl-lib)
(declare-function el-get-install "el-get" (package))
(declare-function el-get-remove "el-get" (package))
(declare-function el-get-update "el-get" (package))
(declare-function el-get-read-package-name "el-get" (action &optional filtered))
(declare-function el-get-read-package-status "el-get-status" (package &optional package-status-alist))
(defvar el-get-package-menu-buffer nil
"Global var holding pointing to the package menu buffer, so
that it can be updated from `el-get-save-package-status'")
(define-button-type 'el-get-help-package-def
:supertype 'help-xref
'help-function (lambda (package) (find-file (el-get-recipe-filename package)))
'help-echo (purecopy "mouse-2, RET: find package's recipe"))
(define-button-type 'el-get-help-install
:supertype 'help-xref
'help-function (lambda (package)
(when (y-or-n-p
(format "Do you really want to install `%s'? "
package))
(el-get-install package)))
'help-echo (purecopy "mouse-2, RET: install package"))
(define-button-type 'el-get-help-remove
:supertype 'help-xref
'help-function (lambda (package)
(when (y-or-n-p
(format "Do you really want to uninstall `%s'? "
package))
(el-get-remove package)))
'help-echo (purecopy "mouse-2, RET: remove package"))
(define-button-type 'el-get-help-update
:supertype 'help-xref
'help-function (lambda (package)
(when (y-or-n-p
(format "Do you really want to update `%s'? "
package))
(el-get-update package)))
'help-echo (purecopy "mouse-2, RET: update package"))
(define-button-type 'el-get-help-cd
:supertype 'help-xref
'help-function #'dired
'help-echo (purecopy "mouse-2, RET: open directory"))
(define-button-type 'el-get-help-describe-package
:supertype 'help-xref
'help-function #'el-get-describe
'help-echo (purecopy "mouse-2, RET: describe package"))
(defun el-get-describe-princ-button (label regex type &rest args)
"Princ a new button with label LABEL.
The LABEL is made clickable by calling `help-xref-button' for a backwards
matching REGEX with TYPE and ARGS as parameter."
(princ label)
(with-current-buffer standard-output
(save-excursion
(re-search-backward regex nil t)
(apply #'help-xref-button 1 type args))))
(defun el-get-guess-website (package)
(let* ((type (el-get-package-type package))
(guesser (el-get-method type :guess-website)))
(when guesser
(funcall guesser package))))
(defun el-get-describe-1 (package)
(let* ((psym (el-get-as-symbol package))
(pname (symbol-name psym))
(status (el-get-read-package-status package))
(def (el-get-package-def pname))
(name (plist-get def :name))
(website (plist-get def :website))
(directory (el-get-package-directory package))
(descr (plist-get def :description))
(type (el-get-package-method def))
(builtin (plist-get def :builtin))
(minimum-version (plist-get def :minimum-emacs-version))
(url (plist-get def :url))
(depends (plist-get def :depends)))
(princ (format "%s is an `el-get' package. " name))
(if (eq type 'builtin)
(princ (format "It is built-in since Emacs %s" builtin))
(princ (format "It is currently %s "
(if status
status
"not installed")))
(cond
((string= status "installed")
(el-get-describe-princ-button "[update]" "\\[\\([^]]+\\)\\]"
'el-get-help-update package)
(el-get-describe-princ-button "[remove]" "\\[\\([^]]+\\)\\]"
'el-get-help-remove package))
((string= status "required")
(el-get-describe-princ-button "[update]" "\\[\\([^]]+\\)\\]"
'el-get-help-update package))
(t
(el-get-describe-princ-button "[install]" "\\[\\([^]]+\\)\\]"
'el-get-help-install package))))
(princ ".\n\n")
(let ((website (or website
(el-get-guess-website package))))
(when website
(el-get-describe-princ-button (format "Website: %s\n" website)
": \\(.+\\)" 'help-url website)))
(when descr
(princ (format "Description: %s\n" descr)))
(when depends
(if (listp depends)
(progn
(princ "Dependencies: ")
(cl-loop for i in depends
do (el-get-describe-princ-button
(format "`%s'" i) "`\\([^`']+\\)"
'el-get-help-describe-package i)))
(princ "Dependency: ")
(el-get-describe-princ-button
(format "`%s'" depends) "`\\([^`']+\\)"
'el-get-help-describe-package depends))
(princ ".\n"))
(when minimum-version
(princ (format "Requires minimum Emacs version: %s." minimum-version))
(when (version-list-< (version-to-list emacs-version)
(el-get-version-to-list minimum-version))
(princ (format " Warning: Your Emacs is too old (%s)!" emacs-version)))
(princ "\n"))
(if (eq type 'builtin)
(princ (format "The package is built-in since Emacs %s.\n" builtin))
(princ (format "The default installation method is %s%s.\n" type
(if url (format " from %s" url) ""))))
(when (string= status "installed")
(princ "Installed in ")
(el-get-describe-princ-button (format "`%s'" directory) "`\\([^']+\\)"
'el-get-help-cd directory)
(princ ".\n"))
(princ "\n")
(princ "Full definition")
(let ((file (el-get-recipe-filename package)))
(if (not file)
(princ ":\n")
(el-get-describe-princ-button (format " in `%s':\n" file)
"`\\([^`']+\\)"
'el-get-help-package-def package)))
(el-get-recipe-pprint def)))
(defun el-get-describe (package &optional interactive-p)
"Generate a description for PACKAGE."
(interactive
(list
(el-get-read-package-name "Describe") t))
(if (null package)
(message "You didn't specify a package")
(help-setup-xref (list #'el-get-describe package)
interactive-p)
(save-excursion
(with-help-window (help-buffer)
(with-current-buffer standard-output
(el-get-describe-1 package))))))
(defcustom el-get-package-menu-view-recipe-function
'find-file-other-window
"`find-file' compatible function used to display recipe content
in el-get package menu."
:group 'el-get
:type 'symbol)
;;
;; Package Menu
;;
(defvar el-get-package-menu-mode-hook nil
"Hooks to run after el-get package menu init.")
(defvar el-get-package-menu-mode-map nil
"Keymap for el-get-package-menu-mode")
(defvar el-get-package-menu-sort-key nil
"sort packages by key")
(defconst el-get-package-list-column-alist
'(("Package" . 2)
("Status" . 30)
("Type" . 41)
("Description" . 54))
"An alist of (NAME . COLUMN) entries.")
(defun el-get-package-menu-get-package-name ()
(save-excursion
(beginning-of-line)
(if (looking-at ". \\([^ \t]*\\)")
(match-string 1))))
(defun el-get-package-menu-view-recipe ()
"Show package recipe in a read-only mode."
(interactive)
(let* ((package (el-get-package-menu-get-package-name))
(recipe-file (el-get-recipe-filename package)))
(funcall el-get-package-menu-view-recipe-function recipe-file)
(view-mode)))
(defun el-get-package-menu-get-status ()
(save-excursion
(beginning-of-line)
(if (looking-at ". [^ \t]*[ \t]*\\([^ \t\n]*\\)")
(match-string 1))))
(defun el-get-package-menu-mark (what)
(unless (eobp)
(let ((buffer-read-only nil))
(beginning-of-line)
(delete-char 1)
(insert what)
(forward-line)
(setq buffer-read-only t))))
(defun el-get-package-menu-mark-install ()
(interactive)
(if (or (string= (el-get-package-menu-get-status) "available")
(string= (el-get-package-menu-get-status) "removed"))
(el-get-package-menu-mark "I")))
(defun el-get-package-menu-mark-update ()
(interactive)
(if (or (string= (el-get-package-menu-get-status) "installed")
(string= (el-get-package-menu-get-status) "required"))
(el-get-package-menu-mark "U")))
(defun el-get-package-menu-mark-delete ()
(interactive)
(if (or (string= (el-get-package-menu-get-status) "installed")
(string= (el-get-package-menu-get-status) "required"))
(el-get-package-menu-mark "D")))
(defun el-get-package-menu-mark-unmark ()
(interactive)
(el-get-package-menu-mark " "))
(defun el-get-package-menu-revert ()
(interactive)
(let ((current-point (point)))
(el-get-package-menu)
(goto-char current-point)
(beginning-of-line)))
(defun el-get-package-menu-execute ()
(interactive)
(let ((current-point (point)))
(goto-char (point-min))
(while (not (eobp))
(let ((command (char-after))
(package-name (el-get-package-menu-get-package-name)))
(cond
((eq command ?I)
(message "Installing %s..." package-name)
(el-get-install package-name)
(message "Installing %s...done" package-name))
((eq command ?U)
(message "Updating %s..." package-name)
(el-get-update package-name)
(message "Updating %s...done" package-name))
((eq command ?D)
(message "Deleting %s..." package-name)
(el-get-remove package-name)
(message "Deleting %s..." package-name))))
(forward-line))
(goto-char current-point)
(beginning-of-line)))
(defun el-get-package-menu-describe ()
(interactive)
(el-get-describe (el-get-package-menu-get-package-name)))
(defun el-get-package-menu-quick-help ()
(interactive)
(message "n-ext, p-revious, i-nstall, u-pdate, d-elete, SPC-unmark, g-revert, x-execute, ?-package describe, v-iew recipe, h-elp, q-uit"))
(unless el-get-package-menu-mode-map
(setq el-get-package-menu-mode-map (make-keymap))
(suppress-keymap el-get-package-menu-mode-map)
(define-key el-get-package-menu-mode-map "n" 'next-line)
(define-key el-get-package-menu-mode-map "p" 'previous-line)
(define-key el-get-package-menu-mode-map "i" 'el-get-package-menu-mark-install)
(define-key el-get-package-menu-mode-map "u" 'el-get-package-menu-mark-update)
(define-key el-get-package-menu-mode-map "d" 'el-get-package-menu-mark-delete)
(define-key el-get-package-menu-mode-map " " 'el-get-package-menu-mark-unmark)
(define-key el-get-package-menu-mode-map "g" 'el-get-package-menu-revert)
(define-key el-get-package-menu-mode-map "x" 'el-get-package-menu-execute)
(define-key el-get-package-menu-mode-map "?" 'el-get-package-menu-describe)
(define-key el-get-package-menu-mode-map "v" 'el-get-package-menu-view-recipe)
(define-key el-get-package-menu-mode-map "h" 'el-get-package-menu-quick-help)
(define-key el-get-package-menu-mode-map "q" 'quit-window))
(defun el-get-package-on-kill ()
"Add this to `kill-buffer-query-functions' to clear `el-get-package-menu-buffer'."
(setq el-get-package-menu-buffer nil)
t)
(defun el-get-package-menu-mode ()
"Major mode for browsing a list of packages.
\\{el-get-package-menu-mode-map}"
(kill-all-local-variables)
(use-local-map el-get-package-menu-mode-map)
(add-hook 'kill-buffer-query-functions #'el-get-package-on-kill t t)
(setq el-get-package-menu-buffer (current-buffer))
(setq major-mode 'el-get-package-menu-mode)
(setq mode-name "Package-Menu")
(setq buffer-read-only t)
(setq truncate-lines t)
(if (fboundp 'run-mode-hooks)
(run-mode-hooks 'el-get-package-menu-mode-hook)
(run-hooks 'el-get-package-menu-mode-hook)))
2015-08-25 23:33:59 +08:00
(defun el-get-print-package (package-name status &optional type desc)
(let ((face
(cond
((string= status "installed")
'font-lock-comment-face)
((string= status "required")
'font-lock-keyword-face)
((string= status "removed")
'font-lock-string-face)
(t
(setq status "available")
'default))))
(indent-to (cdr (assoc "Package" el-get-package-list-column-alist)) 1)
(insert package-name)
(indent-to (cdr (assoc "Status" el-get-package-list-column-alist)) 1)
(insert status)
(put-text-property (line-beginning-position) (line-end-position)
'font-lock-face face)
(indent-to (cdr (assoc "Type" el-get-package-list-column-alist)) 1)
2015-08-25 23:33:59 +08:00
(when type
(insert (propertize (replace-regexp-in-string "\n" " " type)
'font-lock-face face)))
(when desc
(indent-to (cdr (assoc "Description" el-get-package-list-column-alist)) 1)
(let ((eol (cl-position ?\n desc)))
(when eol (setq desc (substring desc 0 eol))))
(insert (propertize desc 'font-lock-face face) "\n"))))
(defun el-get-list-all-packages ()
(with-current-buffer (get-buffer-create "*el-get packages*")
(setq buffer-read-only nil)
(erase-buffer)
(let ((packages (el-get-read-all-recipes)))
(let ((selector (cond
((string= el-get-package-menu-sort-key "Status")
#'(lambda (package)
(let ((package-name (el-get-as-string (plist-get package :name))))
(el-get-read-package-status package-name))))
2015-08-25 23:24:48 +08:00
((string= el-get-package-menu-sort-key "Type")
#'(lambda (package)
(el-get-as-string (plist-get package :type))))
((string= el-get-package-menu-sort-key "Description")
#'(lambda (package)
(plist-get package :description)))
(t
#'(lambda (package)
(el-get-as-string (plist-get package :name)))))))
(setq packages
(sort packages
(lambda (left right)
(let ((vleft (funcall selector left))
(vright (funcall selector right)))
(string< vleft vright))))))
(mapc (lambda (package)
(let ((package-name (el-get-as-string (plist-get package :name))))
(el-get-print-package package-name
(el-get-read-package-status package-name)
2015-08-25 23:24:48 +08:00
(el-get-as-string (plist-get package :type))
(or (plist-get package :description) ""))))
packages))
(goto-char (point-min))
(current-buffer)))
(defun el-get-package-menu-sort-by-column (&optional e)
"Sort the package menu by the last column clicked on."
(interactive (list last-input-event))
;; On Emacs 24.3 and earlier, `mouse-select-window' is not defined
;; on tty only builds.
(if (and e (fboundp 'mouse-select-window)) (mouse-select-window e))
(let* ((pos (event-start e))
(obj (posn-object pos))
(col (if obj
(get-text-property (cdr obj) 'column-name (car obj))
(get-text-property (posn-point pos) 'column-name))))
(setq el-get-package-menu-sort-key col)
(el-get-package-menu)))
(defvar el-get-package-menu-sort-button-map
(let ((map (make-sparse-keymap)))
(define-key map [header-line mouse-1] 'el-get-package-menu-sort-by-column)
(define-key map [follow-link] 'mouse-face)
map)
"Local keymap for package menu sort buttons.")
(defun el-get-package-menu ()
(with-current-buffer (el-get-list-all-packages)
(el-get-package-menu-mode)
(setq header-line-format
(mapconcat
(lambda (pair)
(let ((name (car pair))
(column (cdr pair)))
(concat
;; Insert a space that aligns the button properly.
(propertize " " 'display (list 'space :align-to column)
'face 'fixed-pitch)
;; Set up the column button.
(propertize name
'column-name name
'help-echo "mouse-1: sort by column"
'mouse-face 'highlight
'keymap el-get-package-menu-sort-button-map))))
el-get-package-list-column-alist ""))
(pop-to-buffer (current-buffer))))
;;;###autoload
(defun el-get-list-packages ()
"Display a list of packages."
(interactive)
(el-get-package-menu))
(provide 'el-get-list-packages)