Mercurial > hg > xemacs-beta
diff lisp/package-ui.el @ 428:3ecd8885ac67 r21-2-22
Import from CVS: tag r21-2-22
author | cvs |
---|---|
date | Mon, 13 Aug 2007 11:28:15 +0200 |
parents | |
children | 8de8e3f6228a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/package-ui.el Mon Aug 13 11:28:15 2007 +0200 @@ -0,0 +1,704 @@ +;;; package-ui.el --- + +;; Copyright (C) 1998 by Darryl Okahata + +;; Author: Darryl Okahata <darrylo@sr.hp.com> +;; Keywords: internal + +;; This file is part of XEmacs. + +;; XEmacs is free software; you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; XEmacs is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with XEmacs; see the file COPYING. If not, write to the Free +;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +;; 02111-1307, USA. + +;;; Synched up with: Not in FSF + +(require 'package-get) ;; which, in turn, requires 'package-admin + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; User-changeable variables: +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup pui nil + "Conventient interface to the package system." + :group 'package-tools + :tag "Package User interface" + :prefix "pui-") + +(defcustom pui-package-install-dest-dir nil + "*If non-nil (Automatic) path to package tree to install packages in. +Otherwise, use old path for installed packages and make a guess for +new ones." + :group 'pui + :tag "Install Location" + :type '(choice (const :tag "Automatic" nil) + (directory))) + +(defcustom pui-list-verbose t + "*If non-nil, display verbose info in the package list buffer." + :group 'pui + :tag "Verbose Listing" + :type 'boolean) + +(defcustom pui-up-to-date-package-face nil + "*The face to use for packages that are up-to-date." + :group 'pui + :type 'face) + +(defcustom pui-selected-package-face 'bold + "*The face to use for selected packages. +Set this to `nil' to use the `default' face." + :group 'pui + :type 'face) + +(defcustom pui-deleted-package-face 'blue + "*The face to use for packages marked for removal. +Set this to `nil' to use the `default' face." + :group 'pui + :type 'face) + +(defcustom pui-outdated-package-face 'red + "*The face to use for outdated packages. +Set this to `nil' to use the `default' face." + :group 'pui + :type 'face) + +(defcustom pui-uninstalled-package-face 'italic + "*The face to use for uninstalled packages. +Set this to `nil' to use the `default' face." + :group 'pui + :type 'face) + + + + +(defvar pui-info-buffer "*Packages*" + "Buffer to use for displaying package information.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; End of user-changeable variables. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar pui-selected-packages nil + "The list of user-selected packages to install.") + +(defvar pui-deleted-packages nil + "The list of user-selected packages to remove.") + +(defvar pui-actual-package "") + +(defvar pui-display-keymap + (let ((m (make-keymap))) + (suppress-keymap m) + (set-keymap-name m 'pui-display-keymap) + (define-key m "q" 'pui-quit) + (define-key m "g" 'pui-list-packages) + (define-key m "i" 'pui-display-info) + (define-key m "?" 'describe-mode) + (define-key m "v" 'pui-toggle-verbosity-redisplay) + (define-key m "d" 'pui-toggle-package-delete-key) + (define-key m "D" 'pui-toggle-package-delete-key) + (define-key m [return] 'pui-toggle-package-key) + (define-key m "x" 'pui-install-selected-packages) + (define-key m "I" 'pui-install-selected-packages) + (define-key m "r" 'pui-add-required-packages) + (define-key m "n" 'next-line) + (define-key m "+" 'pui-toggle-package-key) + (define-key m "p" 'previous-line) + (define-key m " " 'scroll-up-command) + (define-key m [delete] 'scroll-down-command) + m) + "Keymap to use in the `pui-info-buffer' buffer") + +(defvar pui-package-keymap + (let ((m (make-sparse-keymap))) + (set-keymap-name m 'pui-package-keymap) + (define-key m 'button2 'pui-toggle-package-event) +;; We use a popup menu + (define-key m 'button3 'pui-popup-context-sensitive) + m) + "Keymap to use over package names/descriptions.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; End of variables + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration routines + +(defun pui-directory-exists (dir) + "Check to see if DIR exists in `package-get-remote'." + (let (found) + (mapcar '(lambda (item) + (if (and (null (car item)) + (string-equal (file-name-as-directory (car (cdr item))) + (file-name-as-directory dir))) + (setq found t)) + ) package-get-remote) + found + )) + +(defun pui-package-dir-list (buffer) + "In BUFFER, format the list of package binary paths." + (let ( (count 1) paths sys dir) + (set-buffer buffer) + (buffer-disable-undo buffer) + (erase-buffer buffer) + (insert "Existing package binary paths:\n\n") + (setq paths package-get-remote) + (while paths + (setq sys (car (car paths)) + dir (car (cdr (car paths)))) + (insert (format "%2s. " count)) + (if (null sys) + (insert dir) + (insert sys ":" dir)) + (insert "\n") + (setq count (1+ count)) + (setq paths (cdr paths)) + ) + (insert "\nThese are the places that will be searched for package binaries.\n") + (goto-char (point-min)) + )) + +;;;###autoload +(defun package-ui-add-site (site) + "Add site to package-get-remote and possibly offer to update package list." + (let ((had-none (null package-get-remote))) + (push site package-get-remote) + (when (and had-none package-get-was-current + (y-or-n-p "Update Package list?")) + (setq package-get-was-current nil) + (package-get-require-base t) + (if (get-buffer pui-info-buffer) + (save-window-excursion + (pui-list-packages)))) + (set-menubar-dirty-flag))) + + +;;;###autoload +(defun pui-add-install-directory (dir) + "Add a new package binary directory to the head of `package-get-remote'. +Note that no provision is made for saving any changes made by this function. +It exists mainly as a convenience for one-time package installations from +disk." + (interactive (let ( (tmpbuf (get-buffer-create + "*Existing Package Binary Paths*")) + dir) + (save-window-excursion + (save-excursion + (unwind-protect + (progn + (pui-package-dir-list tmpbuf) + (display-buffer tmpbuf) + (setq dir (read-directory-name + "New package binary directory to add? " + nil nil t)) + ) + (kill-buffer tmpbuf) + ))) + (list dir) + )) + (progn + (if (not (pui-directory-exists dir)) + (progn + (setq package-get-remote (cons (list nil dir) package-get-remote)) + (message "Package directory \"%s\" added." dir) + ) + (message "Directory \"%s\" already exists in `package-get-remote'." dir)) + )) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Package list/installer routines + +(defun pui-quit () + (interactive) + (kill-buffer nil)) + +(defun pui-package-symbol-char (pkg-sym version) + (progn + (if (package-get-info-find-package packages-package-list pkg-sym) + (let ((installed (package-get-key pkg-sym :version))) + (if (>= (if (stringp installed) + (string-to-number installed) + installed) + (if (stringp version) + (string-to-number version) + version)) + (list " " pui-up-to-date-package-face) + (list "*" pui-outdated-package-face))) + (list "-" pui-uninstalled-package-face)) + )) + +(defun pui-update-package-display (extent &optional pkg-sym version) + "Update the package status for EXTENT. +If PKG-SYM or VERSION are not given, they are read from the extent. +These are used to determine whether or not the package is installed, +and whether or not it is up-to-date." + (let (buffer-read-only disp sym-char) + (if (not pkg-sym) + (setq pkg-sym (extent-property extent 'pui-package))) + (if (not version) + (setq version (package-get-info-prop (extent-property extent 'pui-info) + 'version))) + (cond ((member pkg-sym pui-selected-packages) + (if pui-selected-package-face + (set-extent-face extent (get-face pui-selected-package-face)) + (set-extent-face extent (get-face 'default))) + (setq sym-char "+")) + ((member pkg-sym pui-deleted-packages) + (if pui-deleted-package-face + (set-extent-face extent (get-face pui-deleted-package-face)) + (set-extent-face extent (get-face 'default))) + (setq sym-char "D")) + (t + (setq disp (pui-package-symbol-char pkg-sym version)) + (setq sym-char (car disp)) + (if (car (cdr disp)) + (set-extent-face extent (get-face (car (cdr disp)))) + (set-extent-face extent (get-face 'default))))) + (save-excursion + (goto-char (extent-start-position extent)) + (delete-char 1) + (insert sym-char) + (set-buffer-modified-p nil) + ) + )) + +(defun pui-toggle-package (extent) + (let (pkg-sym) + (setq pkg-sym (extent-property extent 'pui-package)) + (if (member pkg-sym pui-selected-packages) + (setq pui-selected-packages + (delete pkg-sym pui-selected-packages)) + (setq pui-selected-packages + (cons pkg-sym pui-selected-packages)) + (setq pui-deleted-packages + (delete pkg-sym pui-deleted-packages))) + (pui-update-package-display extent pkg-sym) + )) + +(defun pui-toggle-package-key () + "Select/unselect package for installation, using the keyboard." + (interactive) + (let (extent) + (if (setq extent (extent-at (point) (current-buffer) 'pui)) + (progn + (pui-toggle-package extent) + (forward-line 1) + ) + (error "No package under cursor!")) + )) + +(defun pui-toggle-package-delete (extent) + (let (pkg-sym) + (setq pkg-sym (extent-property extent 'pui-package)) + (if (member pkg-sym pui-deleted-packages) + (setq pui-deleted-packages + (delete pkg-sym pui-deleted-packages)) + (setq pui-deleted-packages + (cons pkg-sym pui-deleted-packages)) + (setq pui-seleted-packages + (delete pkg-sym pui-selected-packages))) + (pui-update-package-display extent pkg-sym) + )) + + +(defun pui-toggle-package-delete-key () + "Select/unselect package for removal, using the keyboard." + (interactive) + (let (extent) + (if (setq extent (extent-at (point) (current-buffer) 'pui)) + (progn + (pui-toggle-package-delete extent) + (forward-line 1) + ) + (error "No package under cursor!")) + )) + +(defun pui-current-package () + (let ((extent (extent-at (point) (current-buffer) 'pui))) + (if extent + (extent-property extent 'pui-package)))) + +(defun pui-toggle-package-event (event) + "Select/unselect package for installation, using the mouse." + (interactive "e") + (let* ( (ep (event-point event)) + (buffer (window-buffer (event-window event))) + (extent (extent-at ep buffer 'pui-package)) + ) + (pui-toggle-package extent) + )) + +(defun pui-toggle-verbosity-redisplay () + "Toggle verbose package info." + (interactive) + (progn + (setq pui-list-verbose (not pui-list-verbose)) + (pui-list-packages) + )) + +(defun pui-install-selected-packages () + "Install selected packages." + (interactive) + (let ( (tmpbuf "*Packages-To-Remove*") do-delete) + (when pui-deleted-packages + (save-window-excursion + (with-output-to-temp-buffer tmpbuf + (display-completion-list (sort + (mapcar '(lambda (pkg) + (symbol-name pkg) + ) + pui-deleted-packages) + 'string<) + :activate-callback nil + :help-string "Packages selected for removal:\n" + :completion-string t + )) + (setq tmpbuf (get-buffer-create tmpbuf)) + (display-buffer tmpbuf) + (setq do-delete (yes-or-no-p "Remove these packages? ")) + (kill-buffer tmpbuf)) + (when do-delete + (message "Deleting selected packages ...") (sit-for 0) + (mapcar (lambda (pkg) + (package-admin-delete-binary-package + pkg (package-admin-get-install-dir pkg nil))) + pui-deleted-packages) + (message "Packages deleted")))) + + (let ( (tmpbuf "*Packages-To-Install*") do-install) + (if pui-selected-packages + (progn + ;; Don't change window config when asking the user if he really + ;; wants to install the packages. We do this to avoid messing up + ;; the window configuration if errors occur (we don't want to + ;; display random buffers in addition to the error buffer, if + ;; errors occur, which would normally be caused by display-buffer). + (save-window-excursion + (with-output-to-temp-buffer tmpbuf + (display-completion-list (sort + (mapcar '(lambda (pkg) + (symbol-name pkg) + ) + pui-selected-packages) + 'string<) + :activate-callback nil + :help-string "Packages selected for installation:\n" + :completion-string t + )) + (setq tmpbuf (get-buffer-create tmpbuf)) + (display-buffer tmpbuf) + (setq do-install (y-or-n-p "Install these packages? ")) + (kill-buffer tmpbuf) + ) + (if do-install + (progn + (save-excursion + ;; Clear old temp buffer history + (set-buffer (get-buffer-create package-admin-temp-buffer)) + (buffer-disable-undo package-admin-temp-buffer) + (erase-buffer package-admin-temp-buffer) + ) + (message "Installing selected packages ...") (sit-for 0) + (if (catch 'done + (mapcar (lambda (pkg) + (if (not (package-get pkg nil nil + pui-package-install-dest-dir)) + (throw 'done nil))) + pui-selected-packages) + t) + (progn + (pui-list-packages) + (message "Packages installed") + )) + ) + (clear-message) + ) + ) + (if pui-deleted-packages + (pui-list-packages) + (error "No packages have been selected!"))) + )) + +(defun pui-add-required-packages () + "Select packages required by those already selected for installation." + (interactive) + (let ((tmpbuf "*Required-Packages*") do-select) + (if pui-selected-packages + (let ((dependencies + (delq nil (mapcar + (lambda (pkg) + (let ((installed + (package-get-key pkg :version)) + (current + (package-get-info-prop + (package-get-info-version + (package-get-info-find-package + package-get-base pkg) nil) + 'version))) + (if (or (null installed) + (< (if (stringp installed) + (string-to-number installed) + installed) + (if (stringp current) + (string-to-number current) + current))) + pkg + nil))) + (package-get-dependencies pui-selected-packages))))) + ;; Don't change window config when asking the user if he really + ;; wants to add the packages. We do this to avoid messing up + ;; the window configuration if errors occur (we don't want to + ;; display random buffers in addition to the error buffer, if + ;; errors occur, which would normally be caused by display-buffer). + (save-window-excursion + (with-output-to-temp-buffer tmpbuf + (display-completion-list (sort + (mapcar #'(lambda (pkg) + (symbol-name pkg)) + dependencies) + 'string<) + :activate-callback nil + :help-string "Required packages:\n" + :completion-string t)) + (setq tmpbuf (get-buffer-create tmpbuf)) + (display-buffer tmpbuf) + (setq do-select (y-or-n-p "Select these packages? ")) + (kill-buffer tmpbuf)) + (if do-select + (progn + (setq pui-selected-packages + (union pui-selected-packages dependencies)) + (map-extents #'(lambda (extent maparg) + (pui-update-package-display extent)) + nil nil nil nil nil 'pui) + (message "added dependencies")) + (clear-message))) + (error "No packages have been selected!")))) + +(defun pui-help-echo (extent &optional force-update) + "Display additional package info in the modeline. +EXTENT determines the package to display (the package information is +attached to the extent as properties)." + (let (pkg-sym info inst-ver auth-ver date maintainer) + (if (or force-update (not (current-message)) + (string-match ".*: .*: " (current-message)) + ) + (progn + (setq pkg-sym (extent-property extent 'pui-package) + info (extent-property extent 'pui-info) + inst-ver (package-get-key pkg-sym :version) + auth-ver (package-get-info-prop info 'author-version) + date (package-get-info-prop info 'date) + maintainer (package-get-info-prop info 'maintainer)) + (if (not inst-ver) + (setq inst-ver "")) + (if pui-list-verbose + (format "Author version: %-8s %11s: %s" + auth-ver date maintainer) + (format "%-6s: %-8s %11s: %s" + inst-ver auth-ver date maintainer)) + )) + )) + +(defun pui-display-info (&optional no-error event) + "Display additional package info in the modeline. +Designed to be called interactively (from a keypress)." + (interactive) + (let (extent) + (save-excursion + (beginning-of-line) + (if (setq extent (extent-at (point) (current-buffer) 'pui)) + (message (pui-help-echo extent t)) + (if no-error + (clear-message nil) + (error "No package under cursor!"))) + ))) + +;;; "Why is there no standard function to do this?" +(defun pui-popup-context-sensitive (event) + (interactive "e") + (save-excursion + (set-buffer (event-buffer event)) + (goto-char (event-point event)) + (popup-menu pui-menu event) + ;; I agreee with dired.el this is seriously bogus. + (while (popup-menu-up-p) + (dispatch-event (next-event))))) + +(defvar pui-menu + '("Packages" + ["Toggle install " pui-toggle-package-key :active (pui-current-package) :suffix (format "`%s'" (or (pui-current-package) "..."))] + ["Toggle delete " pui-toggle-package-delete-key :active (pui-current-package) :suffix (format "`%s'" (or (pui-current-package) "..."))] + ["Info on" pui-display-info :active (pui-current-package) :suffix (format "`%s'" (or (pui-current-package) "..."))] + "---" + ["Add Required" pui-add-required-packages t] + ["Install/Remove Selected" pui-install-selected-packages t] + "---" + ["Verbose" pui-toggle-verbosity-redisplay + :active t :style toggle :selected pui-list-verbose] + ["Refresh" pui-list-packages t] + ["Help" pui-help t] + ["Quit" pui-quit t])) + + +(defun list-packages-mode () + "Symbols in the leftmost column: + + + The package is marked for installation. + - The package has not been installed. + D The package has been marked for deletion. + * The currently installed package is old, and a newer version is + available. + +Useful keys: + + `\\[pui-toggle-package-key]' to select/unselect the current package for installation. + `\\[pui-toggle-package-delete-key]' to select/unselect the current package for removal. + `\\[pui-add-required-packages]' to add any packages required by those selected. + `\\[pui-install-selected-packages]' to install/delete selected packages. + `\\[pui-display-info]' to display additional information about the package in the modeline. + `\\[pui-list-packages]' to refresh the package list. + `\\[pui-toggle-verbosity-redisplay]' to toggle between a verbose and non-verbose display. + `\\[pui-quit]' to kill this buffer. +" + (error "You cannot enter this mode directly. Use `pui-list-packages'")) + +(put 'list-packages-mode 'mode-class 'special) + +;;;###autoload +(defun pui-list-packages () + "List all packages and package information. +The package name, version, and description are displayed. From the displayed +buffer, the user can see which packages are installed, which are not, and +which are out-of-date (a newer version is available). The user can then +select packages for installation via the keyboard or mouse." + (interactive) + (package-get-require-base t) + (let ( (outbuf (get-buffer-create pui-info-buffer)) + (sep-string "===============================================================================\n") + start ) + (message "Creating package list ...") (sit-for 0) + (set-buffer outbuf) + (setq buffer-read-only nil) + (buffer-disable-undo outbuf) + (erase-buffer outbuf) + (kill-all-local-variables) + (use-local-map pui-display-keymap) + (setq major-mode 'list-packages-mode) + (setq mode-name "Packages") + (setq truncate-lines t) + + (unless package-get-remote + (insert " +Warning: No download sites specified. Package index may be out of date. + If you intend to install packages, specify download sites first. + +")) + + (if pui-list-verbose + (insert " Latest Installed + Package name Vers. Vers. Description +") + (insert " Latest + Package name Vers. Description +")) + (insert sep-string) + (setq start (point)) + (mapcar '(lambda (pkg) + (let (pkg-sym info version desc + b e extent current-vers disp) + (setq pkg-sym (car pkg) + info (package-get-info-version (cdr pkg) nil)) + (setq version (package-get-info-prop info 'version) + desc (package-get-info-prop info 'description)) + + (setq disp (pui-package-symbol-char pkg-sym + version)) + (setq b (point)) + (if pui-list-verbose + (progn + (setq current-vers (package-get-key pkg-sym :version)) + (cond + ( (not current-vers) + (setq current-vers "-----") ) + ( (stringp current-vers) + (setq current-vers + (format "%.2f" + (string-to-number current-vers))) ) + ( (numberp current-vers) + (setq current-vers (format "%.2f" current-vers)) ) + ) + (insert + (format "%s %-15s %-5.2f %-5s %s\n" + (car disp) pkg-sym + (if (stringp version) + (string-to-number version) + version) + current-vers desc)) +;; (insert +;; (format "\t\t %-12s %s\n" +;; (package-get-info-prop info 'author-version) +;; (package-get-info-prop info 'date) +;; )) + ) + (insert (format "%s %-15s %-5s %s\n" + (car disp) + pkg-sym version desc))) + (save-excursion + (setq e (progn + (forward-line -1) + (end-of-line) + (point))) + ) + (setq extent (make-extent b e)) + (if (car (cdr disp)) + (set-extent-face extent (get-face (car (cdr disp)))) + (set-extent-face extent (get-face 'default))) + (set-extent-property extent 'highlight t) + (set-extent-property extent 'pui t) + (set-extent-property extent 'pui-package pkg-sym) + (set-extent-property extent 'pui-info info) + (set-extent-property extent 'help-echo 'pui-help-echo) + (set-extent-property extent 'keymap pui-package-keymap) + )) (sort (copy-sequence package-get-base) + '(lambda (a b) + (string< (symbol-name (car a)) + (symbol-name (car b))) + ))) + (insert sep-string) + (insert (documentation 'list-packages-mode)) + (set-buffer-modified-p nil) + (setq buffer-read-only t) + (pop-to-buffer outbuf) + (delete-other-windows) + (goto-char start) + (setq pui-selected-packages nil) ; Reset list + (setq pui-deleted-packages nil) ; Reset list + (when (featurep 'menubar) + (set-buffer-menubar current-menubar) + (add-submenu '() pui-menu) + (setq mode-popup-menu pui-menu)) + (clear-message) +; (message (substitute-command-keys "Press `\\[pui-help]' for help.")) + )) + +;;;###autoload +(defalias 'list-packages 'pui-list-packages) + +(provide 'package-ui) + +;;; package-ui.el ends here