summaryrefslogtreecommitdiff
path: root/emacs/guix-list.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs/guix-list.el')
-rw-r--r--emacs/guix-list.el960
1 files changed, 316 insertions, 644 deletions
diff --git a/emacs/guix-list.el b/emacs/guix-list.el
index 560ae6a86f..7e57f42cb2 100644
--- a/emacs/guix-list.el
+++ b/emacs/guix-list.el
@@ -1,4 +1,4 @@
-;;; guix-list.el --- List buffers for displaying entries -*- lexical-binding: t -*-
+;;; guix-list.el --- 'List' buffer interface for displaying data -*- lexical-binding: t -*-
;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
@@ -19,26 +19,19 @@
;;; Commentary:
-;; This file provides a list-like buffer for displaying information
-;; about Guix packages and generations.
+;; This file provides 'list' buffer interface for displaying an arbitrary
+;; data.
;;; Code:
(require 'cl-lib)
(require 'tabulated-list)
+(require 'guix-buffer)
(require 'guix-info)
-(require 'guix-base)
+(require 'guix-entry)
(require 'guix-utils)
-(defgroup guix-list nil
- "General settings for list buffers."
- :prefix "guix-list-"
- :group 'guix)
-
-(defgroup guix-list-faces nil
- "Faces for list buffers."
- :group 'guix-list
- :group 'guix-faces)
+(guix-define-buffer-type list)
(defface guix-list-file-path
'((t :inherit guix-info-file-path))
@@ -50,153 +43,165 @@
"Face used for time stamps."
:group 'guix-list-faces)
-(defcustom guix-list-describe-warning-count 10
- "The maximum number of entries for describing without a warning.
-If a user wants to describe more than this number of marked
-entries, he will be prompted for confirmation."
- :type 'integer
- :group 'guix-list)
-
-(defvar guix-list-column-format
- `((package
- (name 20 t)
- (version 10 nil)
- (outputs 13 t)
- (installed 13 t)
- (synopsis 30 nil))
- (output
- (name 20 t)
- (version 10 nil)
- (output 9 t)
- (installed 12 t)
- (synopsis 30 nil))
- (generation
- (number 5
- ,(lambda (a b) (guix-list-sort-numerically 0 a b))
- :right-align t)
- (current 10 t)
- (time 20 t)
- (path 30 t)))
- "Columns displayed in list buffers.
-Each element of the list has a form:
-
- (ENTRY-TYPE . ((PARAM WIDTH SORT . PROPS) ...))
-
-PARAM is the name of an entry parameter of ENTRY-TYPE. For the
-meaning of WIDTH, SORT and PROPS, see `tabulated-list-format'.")
-
-(defvar guix-list-column-titles
- '((generation
- (number . "N.")))
- "Column titles for list buffers.
-Has the same structure as `guix-param-titles', but titles from
-this list have a priority.")
-
-(defvar guix-list-column-value-methods
- '((package
- (name . guix-package-list-get-name)
- (synopsis . guix-list-get-one-line)
- (description . guix-list-get-one-line)
- (installed . guix-package-list-get-installed-outputs))
- (output
- (name . guix-package-list-get-name)
- (synopsis . guix-list-get-one-line)
- (description . guix-list-get-one-line))
- (generation
- (current . guix-generation-list-get-current)
- (time . guix-list-get-time)
- (path . guix-list-get-file-path)))
- "Methods for inserting parameter values in columns.
-Each element of the list has a form:
+(defun guix-list-describe (&optional mark-names)
+ "Describe entries marked with a general mark.
+'Describe' means display entries in 'info' buffer.
+If no entries are marked, describe the current entry.
+With prefix argument, describe entries marked with any mark."
+ (interactive (list (unless current-prefix-arg '(general))))
+ (let* ((ids (or (apply #'guix-list-get-marked-id-list mark-names)
+ (list (guix-list-current-id))))
+ (count (length ids))
+ (entry-type (guix-buffer-current-entry-type)))
+ (when (or (<= count (guix-list-describe-warning-count entry-type))
+ (y-or-n-p (format "Do you really want to describe %d entries? "
+ count)))
+ (guix-list-describe-entries entry-type ids))))
- (ENTRY-TYPE . ((PARAM . FUN) ...))
+
+;;; Wrappers for 'list' variables
-PARAM is the name of an entry parameter of ENTRY-TYPE.
+(defvar guix-list-data nil
+ "Alist with 'list' data.
+This alist is filled by `guix-list-define-interface' macro.")
-FUN is a function returning a value that will be inserted. The
-function is called with 2 arguments: the first one is the value
-of the parameter; the second argument is an entry info (alist of
-parameters and their values).")
+(defun guix-list-value (entry-type symbol)
+ "Return SYMBOL's value for ENTRY-TYPE from `guix-list-data'."
+ (symbol-value (guix-assq-value guix-list-data entry-type symbol)))
-(defun guix-list-get-param-title (entry-type param)
- "Return title of an ENTRY-TYPE entry parameter PARAM."
- (or (guix-assq-value guix-list-column-titles
- entry-type param)
- (guix-get-param-title entry-type param)))
+(defun guix-list-param-title (entry-type param)
+ "Return column title of an ENTRY-TYPE parameter PARAM."
+ (guix-buffer-param-title 'list entry-type param))
-(defun guix-list-get-column-format (entry-type)
+(defun guix-list-format (entry-type)
"Return column format for ENTRY-TYPE."
- (guix-assq-value guix-list-column-format entry-type))
+ (guix-list-value entry-type 'format))
+
+(defun guix-list-displayed-params (entry-type)
+ "Return a list of ENTRY-TYPE parameters that should be displayed."
+ (mapcar #'car (guix-list-format entry-type)))
-(defun guix-list-get-displayed-params (entry-type)
- "Return list of parameters of ENTRY-TYPE that should be displayed."
- (mapcar #'car
- (guix-list-get-column-format entry-type)))
+(defun guix-list-sort-key (entry-type)
+ "Return sort key for ENTRY-TYPE."
+ (guix-list-value entry-type 'sort-key))
-(defun guix-list-get-sort-key (entry-type param &optional invert)
- "Return suitable sort key for `tabulated-list-sort-key'.
-Define column title by ENTRY-TYPE and PARAM. If INVERT is
-non-nil, invert the sort."
- (when (memq param (guix-list-get-displayed-params entry-type))
- (cons (guix-list-get-param-title entry-type param) invert)))
+(defun guix-list-additional-marks (entry-type)
+ "Return alist of additional marks for ENTRY-TYPE."
+ (guix-list-value entry-type 'marks))
+
+(defun guix-list-single-entry? (entry-type)
+ "Return non-nil, if a single entry of ENTRY-TYPE should be listed."
+ (guix-list-value entry-type 'list-single))
+
+(defun guix-list-describe-warning-count (entry-type)
+ "Return the maximum number of ENTRY-TYPE entries to describe."
+ (guix-list-value entry-type 'describe-count))
+
+(defun guix-list-describe-entries (entry-type ids)
+ "Describe ENTRY-TYPE entries with IDS in 'info' buffer"
+ (funcall (guix-list-value entry-type 'describe)
+ ids))
+
+
+;;; Tabulated list internals
(defun guix-list-sort-numerically (column a b)
"Compare COLUMN of tabulated entries A and B numerically.
-It is a sort predicate for `tabulated-list-format'.
+This function is used for sort predicates for `tabulated-list-format'.
Return non-nil, if B is bigger than A."
(cl-flet ((num (entry)
(string-to-number (aref (cadr entry) column))))
(> (num b) (num a))))
-(defun guix-list-make-tabulated-vector (entry-type fun)
+(defmacro guix-list-define-numerical-sorter (column)
+ "Define numerical sort predicate for COLUMN.
+See `guix-list-sort-numerically' for details."
+ (let ((name (intern (format "guix-list-sort-numerically-%d" column)))
+ (doc (format "\
+Predicate to sort tabulated list by column %d numerically.
+See `guix-list-sort-numerically' for details."
+ column)))
+ `(defun ,name (a b)
+ ,doc
+ (guix-list-sort-numerically ,column a b))))
+
+(defmacro guix-list-define-numerical-sorters (n)
+ "Define numerical sort predicates for columns from 0 to N.
+See `guix-list-define-numerical-sorter' for details."
+ `(progn
+ ,@(mapcar (lambda (i)
+ `(guix-list-define-numerical-sorter ,i))
+ (number-sequence 0 n))))
+
+(guix-list-define-numerical-sorters 9)
+
+(defun guix-list-tabulated-sort-key (entry-type)
+ "Return ENTRY-TYPE sort key for `tabulated-list-sort-key'."
+ (let ((sort-key (guix-list-sort-key entry-type)))
+ (and sort-key
+ (cons (guix-list-param-title entry-type (car sort-key))
+ (cdr sort-key)))))
+
+(defun guix-list-tabulated-vector (entry-type fun)
"Call FUN on each column specification for ENTRY-TYPE.
-FUN is called with 2 argument: parameter name and column
-specification (see `guix-list-column-format').
+FUN is applied to column specification as arguments (see
+`guix-list-format').
Return a vector made of values of FUN calls."
(apply #'vector
(mapcar (lambda (col-spec)
- (funcall fun (car col-spec) (cdr col-spec)))
- (guix-list-get-column-format entry-type))))
+ (apply fun col-spec))
+ (guix-list-format entry-type))))
-(defun guix-list-get-list-format (entry-type)
+(defun guix-list-tabulated-format (entry-type)
"Return ENTRY-TYPE list specification for `tabulated-list-format'."
- (guix-list-make-tabulated-vector
+ (guix-list-tabulated-vector
entry-type
- (lambda (param spec)
- (cons (guix-list-get-param-title entry-type param)
- spec))))
+ (lambda (param _ &rest rest-spec)
+ (cons (guix-list-param-title entry-type param)
+ rest-spec))))
-(defun guix-list-insert-entries (entries entry-type)
- "Display ENTRIES of ENTRY-TYPE in the current list buffer.
-ENTRIES should have a form of `guix-entries'."
- (setq tabulated-list-entries
- (guix-list-get-tabulated-entries entries entry-type))
- (tabulated-list-print))
-
-(defun guix-list-get-tabulated-entries (entries entry-type)
- "Return list of values of ENTRY-TYPE for `tabulated-list-entries'.
-Values are taken from ENTRIES which should have the form of
-`guix-entries'."
+(defun guix-list-tabulated-entries (entries entry-type)
+ "Return a list of ENTRY-TYPE values for `tabulated-list-entries'."
(mapcar (lambda (entry)
- (list (guix-assq-value entry 'id)
- (guix-list-get-tabulated-entry entry entry-type)))
+ (list (guix-entry-id entry)
+ (guix-list-tabulated-entry entry entry-type)))
entries))
-(defun guix-list-get-tabulated-entry (entry entry-type)
+(defun guix-list-tabulated-entry (entry entry-type)
"Return array of values for `tabulated-list-entries'.
-Parameters are taken from ENTRY of ENTRY-TYPE."
- (guix-list-make-tabulated-vector
+Parameters are taken from ENTRY-TYPE ENTRY."
+ (guix-list-tabulated-vector
entry-type
- (lambda (param _)
- (let ((val (guix-assq-value entry param))
- (fun (guix-assq-value guix-list-column-value-methods
- entry-type param)))
+ (lambda (param fun &rest _)
+ (let ((val (guix-entry-value entry param)))
(if fun
(funcall fun val entry)
(guix-get-string val))))))
+
+;;; Displaying entries
+
+(defun guix-list-get-display-entries (entry-type &rest args)
+ "Search for entries and show them in a 'list' buffer preferably."
+ (let ((entries (guix-buffer-get-entries 'list entry-type args)))
+ (if (or (null entries) ; = 0
+ (cdr entries) ; > 1
+ (guix-list-single-entry? entry-type)
+ (null (guix-buffer-value 'info entry-type 'show-entries)))
+ (guix-buffer-display-entries entries 'list entry-type args 'add)
+ (if (equal (guix-buffer-value 'info entry-type 'get-entries)
+ (guix-buffer-value 'list entry-type 'get-entries))
+ (guix-buffer-display-entries entries 'info entry-type args 'add)
+ (guix-buffer-get-display-entries 'info entry-type args 'add)))))
+
+(defun guix-list-insert-entries (entries entry-type)
+ "Print ENTRY-TYPE ENTRIES in the current buffer."
+ (setq tabulated-list-entries
+ (guix-list-tabulated-entries entries entry-type))
+ (tabulated-list-print))
+
(defun guix-list-get-one-line (val &optional _)
"Return one-line string from a multi-line string VAL.
VAL may be nil."
@@ -217,22 +222,18 @@ VAL may be nil."
'follow-link t
'help-echo "Find file"))
+
+;;; 'List' lines
+
(defun guix-list-current-id ()
- "Return ID of the current entry."
+ "Return ID of the entry at point."
(or (tabulated-list-get-id)
(user-error "No entry here")))
(defun guix-list-current-entry ()
- "Return alist of the current entry info."
- (guix-get-entry-by-id (guix-list-current-id) guix-entries))
-
-(defun guix-list-current-package-id ()
- "Return ID of the current package."
- (cl-ecase major-mode
- (guix-package-list-mode
- (guix-list-current-id))
- (guix-output-list-mode
- (guix-assq-value (guix-list-current-entry) 'package-id))))
+ "Return entry at point."
+ (guix-entry-by-id (guix-list-current-id)
+ (guix-buffer-current-entries)))
(defun guix-list-for-each-line (fun &rest args)
"Call FUN with ARGS for each entry line."
@@ -263,20 +264,28 @@ Each element of the list has a form:
(ID MARK-NAME . ARGS)
ID is an entry ID.
-MARK-NAME is a symbol from `guix-list-mark-alist'.
+MARK-NAME is a symbol from `guix-list-marks'.
ARGS is a list of additional values.")
-(defvar guix-list-mark-alist
+(defvar-local guix-list-marks nil
+ "Alist of available mark names and mark characters.")
+
+(defvar guix-list-default-marks
'((empty . ?\s)
(general . ?*))
- "Alist of available mark names and mark characters.")
+ "Alist of default mark names and mark characters.")
+
+(defun guix-list-marks (entry-type)
+ "Return alist of available marks for ENTRY-TYPE."
+ (append guix-list-default-marks
+ (guix-list-additional-marks entry-type)))
-(defsubst guix-list-get-mark (name)
+(defun guix-list-get-mark (name)
"Return mark character by its NAME."
- (or (guix-assq-value guix-list-mark-alist name)
+ (or (guix-assq-value guix-list-marks name)
(error "Mark '%S' not found" name)))
-(defsubst guix-list-get-mark-string (name)
+(defun guix-list-get-mark-string (name)
"Return mark string by its NAME."
(string (guix-list-get-mark name)))
@@ -288,11 +297,11 @@ ARGS is a list of additional values.")
"Return list of specs of entries marked with any mark from MARK-NAMES.
Entry specs are elements from `guix-list-marked' list.
If MARK-NAMES are not specified, use all marks from
-`guix-list-mark-alist' except the `empty' one."
+`guix-list-marks' except the `empty' one."
(or mark-names
(setq mark-names
(delq 'empty
- (mapcar #'car guix-list-mark-alist))))
+ (mapcar #'car guix-list-marks))))
(cl-remove-if-not (lambda (assoc)
(memq (cadr assoc) mark-names))
guix-list-marked))
@@ -314,7 +323,7 @@ See `guix-list-get-marked' for details."
(defun guix-list--mark (mark-name &optional advance &rest args)
"Put a mark on the current line.
Also add the current entry to `guix-list-marked' using its ID and ARGS.
-MARK-NAME is a symbol from `guix-list-mark-alist'.
+MARK-NAME is a symbol from `guix-list-marks'.
If ADVANCE is non-nil, move forward by one line after marking."
(let ((id (guix-list-current-id)))
(if (eq mark-name 'empty)
@@ -337,7 +346,7 @@ With ARG, mark all lines."
(defun guix-list-mark-all (&optional mark-name)
"Mark all lines with MARK-NAME mark.
-MARK-NAME is a symbol from `guix-list-mark-alist'.
+MARK-NAME is a symbol from `guix-list-marks'.
Interactively, put a general mark on all lines."
(interactive)
(or mark-name (setq mark-name 'general))
@@ -363,7 +372,7 @@ With ARG, unmark all lines."
(guix-list-mark-all 'empty))
(defun guix-list-restore-marks ()
- "Put marks according to `guix-list-mark-alist'."
+ "Put marks according to `guix-list-marked'."
(guix-list-for-each-line
(lambda ()
(let ((mark-name (car (guix-assq-value guix-list-marked
@@ -380,520 +389,183 @@ Same as `tabulated-list-sort', but also restore marks after sorting."
(guix-list-restore-marks))
+;;; Major mode and interface definer
+
(defvar guix-list-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent
- map (make-composed-keymap guix-root-map
+ map (make-composed-keymap guix-buffer-map
tabulated-list-mode-map))
(define-key map (kbd "RET") 'guix-list-describe)
+ (define-key map (kbd "i") 'guix-list-describe)
(define-key map (kbd "m") 'guix-list-mark)
(define-key map (kbd "*") 'guix-list-mark)
(define-key map (kbd "u") 'guix-list-unmark)
(define-key map (kbd "DEL") 'guix-list-unmark-backward)
(define-key map [remap tabulated-list-sort] 'guix-list-sort)
map)
- "Parent keymap for list buffers.")
+ "Keymap for `guix-list-mode' buffers.")
(define-derived-mode guix-list-mode tabulated-list-mode "Guix-List"
- "Parent mode for displaying information in list buffers."
- (setq tabulated-list-padding 2))
-
-(defmacro guix-list-define-entry-type (entry-type &rest args)
- "Define common stuff for displaying ENTRY-TYPE entries in list buffers.
-
-Remaining argument (ARGS) should have a form [KEYWORD VALUE] ... The
-following keywords are available:
-
- - `:sort-key' - default sort key for the tabulated list buffer.
-
- - `:invert-sort' - if non-nil, invert initial sort.
-
- - `:marks' - default value for the defined
- `guix-ENTRY-TYPE-mark-alist' variable.
-
-This macro defines the following functions:
-
- - `guix-ENTRY-TYPE-mark-MARK-NAME' functions for each mark
- specified in `:marks' argument."
- (let* ((entry-type-str (symbol-name entry-type))
- (prefix (concat "guix-" entry-type-str "-list"))
- (mode-str (concat prefix "-mode"))
- (init-fun (intern (concat prefix "-mode-initialize")))
- (marks-var (intern (concat prefix "-mark-alist")))
- (marks-val nil)
- (sort-key nil)
- (invert-sort nil))
-
- ;; Process the keyword args.
- (while (keywordp (car args))
- (pcase (pop args)
- (`:sort-key (setq sort-key (pop args)))
- (`:invert-sort (setq invert-sort (pop args)))
- (`:marks (setq marks-val (pop args)))
- (_ (pop args))))
-
- `(progn
- (defvar ,marks-var ',marks-val
- ,(concat "Alist of additional marks for `" mode-str "'.\n"
- "Marks from this list are added to `guix-list-mark-alist'."))
-
- ,@(mapcar (lambda (mark-spec)
- (let* ((mark-name (car mark-spec))
- (mark-name-str (symbol-name mark-name)))
- `(defun ,(intern (concat prefix "-mark-" mark-name-str "-simple")) ()
- ,(concat "Put '" mark-name-str "' mark and move to the next line.\n"
- "Also add the current entry to `guix-list-marked'.")
- (interactive)
- (guix-list--mark ',mark-name t))))
- marks-val)
-
- (defun ,init-fun ()
- ,(concat "Initial settings for `" mode-str "'.")
- ,(when sort-key
- `(setq tabulated-list-sort-key
- (guix-list-get-sort-key
- ',entry-type ',sort-key ,invert-sort)))
- (setq tabulated-list-format
- (guix-list-get-list-format ',entry-type))
- (setq-local guix-list-mark-alist
- (append guix-list-mark-alist ,marks-var))
- (tabulated-list-init-header)))))
-
-(put 'guix-list-define-entry-type 'lisp-indent-function 'defun)
-
-(defun guix-list-describe-maybe (entry-type ids)
- "Describe ENTRY-TYPE entries in info buffer using list of IDS."
- (let ((count (length ids)))
- (when (or (<= count guix-list-describe-warning-count)
- (y-or-n-p (format "Do you really want to describe %d entries? "
- count)))
- (apply #'guix-get-show-entries
- guix-profile 'info entry-type 'id ids))))
-
-(defun guix-list-describe (&optional arg)
- "Describe entries marked with a general mark.
-If no entries are marked, describe the current entry.
-With prefix (if ARG is non-nil), describe entries marked with any mark."
- (interactive "P")
- (let ((ids (or (apply #'guix-list-get-marked-id-list
- (unless arg '(general)))
- (list (guix-list-current-id)))))
- (guix-list-describe-maybe guix-entry-type ids)))
-
-(defun guix-list-edit-package ()
- "Go to the location of the current package."
- (interactive)
- (guix-edit (guix-list-current-package-id)))
-
-
-;;; Displaying packages
-
-(guix-define-buffer-type list package)
-
-(guix-list-define-entry-type package
- :sort-key name
- :marks ((install . ?I)
- (upgrade . ?U)
- (delete . ?D)))
-
-(defface guix-package-list-installed
- '((t :inherit guix-package-info-installed-outputs))
- "Face used if there are installed outputs for the current package."
- :group 'guix-package-list-faces)
-
-(defface guix-package-list-obsolete
- '((t :inherit guix-package-info-obsolete))
- "Face used if a package is obsolete."
- :group 'guix-package-list-faces)
-
-(defcustom guix-package-list-generation-marking-enabled nil
- "If non-nil, allow putting marks in a list with 'generation packages'.
-
-By default this is disabled, because it may be confusing. For
-example a package is installed in some generation, so a user can
-mark it for deletion in the list of packages from this
-generation, but the package may not be installed in the latest
-generation, so actually it cannot be deleted.
-
-If you managed to understand the explanation above or if you
-really know what you do or if you just don't care, you can set
-this variable to t. It should not do much harm anyway (most
-likely)."
- :type 'boolean
- :group 'guix-package-list)
-
-(let ((map guix-package-list-mode-map))
- (define-key map (kbd "e") 'guix-list-edit-package)
- (define-key map (kbd "x") 'guix-package-list-execute)
- (define-key map (kbd "i") 'guix-package-list-mark-install)
- (define-key map (kbd "d") 'guix-package-list-mark-delete)
- (define-key map (kbd "U") 'guix-package-list-mark-upgrade)
- (define-key map (kbd "^") 'guix-package-list-mark-upgrades))
-
-(defun guix-package-list-get-name (name entry)
- "Return NAME of the package ENTRY.
-Colorize it with `guix-package-list-installed' or
-`guix-package-list-obsolete' if needed."
- (guix-get-string name
- (cond ((guix-assq-value entry 'obsolete)
- 'guix-package-list-obsolete)
- ((guix-assq-value entry 'installed)
- 'guix-package-list-installed))))
-
-(defun guix-package-list-get-installed-outputs (installed &optional _)
- "Return string with outputs from INSTALLED entries."
- (guix-get-string
- (mapcar (lambda (entry)
- (guix-assq-value entry 'output))
- installed)))
-
-(defun guix-package-list-marking-check ()
- "Signal an error if marking is disabled for the current buffer."
- (when (and (not guix-package-list-generation-marking-enabled)
- (or (derived-mode-p 'guix-package-list-mode)
- (derived-mode-p 'guix-output-list-mode))
- (eq guix-search-type 'generation))
- (error "Action marks are disabled for lists of 'generation packages'")))
-
-(defun guix-package-list-mark-outputs (mark default
- &optional prompt available)
- "Mark the current package with MARK and move to the next line.
-If PROMPT is non-nil, use it to ask a user for outputs from
-AVAILABLE list, otherwise mark all DEFAULT outputs."
- (let ((outputs (if prompt
- (guix-completing-read-multiple
- prompt available nil t)
- default)))
- (apply #'guix-list--mark mark t outputs)))
-
-(defun guix-package-list-mark-install (&optional arg)
- "Mark the current package for installation and move to the next line.
-With ARG, prompt for the outputs to install (several outputs may
-be separated with \",\")."
- (interactive "P")
- (guix-package-list-marking-check)
- (let* ((entry (guix-list-current-entry))
- (all (guix-assq-value entry 'outputs))
- (installed (guix-get-installed-outputs entry))
- (available (cl-set-difference all installed :test #'string=)))
- (or available
- (user-error "This package is already installed"))
- (guix-package-list-mark-outputs
- 'install '("out")
- (and arg "Output(s) to install: ")
- available)))
-
-(defun guix-package-list-mark-delete (&optional arg)
- "Mark the current package for deletion and move to the next line.
-With ARG, prompt for the outputs to delete (several outputs may
-be separated with \",\")."
- (interactive "P")
- (guix-package-list-marking-check)
- (let* ((entry (guix-list-current-entry))
- (installed (guix-get-installed-outputs entry)))
- (or installed
- (user-error "This package is not installed"))
- (guix-package-list-mark-outputs
- 'delete installed
- (and arg "Output(s) to delete: ")
- installed)))
-
-(defun guix-package-list-mark-upgrade (&optional arg)
- "Mark the current package for upgrading and move to the next line.
-With ARG, prompt for the outputs to upgrade (several outputs may
-be separated with \",\")."
- (interactive "P")
- (guix-package-list-marking-check)
- (let* ((entry (guix-list-current-entry))
- (installed (guix-get-installed-outputs entry)))
- (or installed
- (user-error "This package is not installed"))
- (when (or (guix-assq-value entry 'obsolete)
- (y-or-n-p "This package is not obsolete. Try to upgrade it anyway? "))
- (guix-package-list-mark-outputs
- 'upgrade installed
- (and arg "Output(s) to upgrade: ")
- installed))))
-
-(defun guix-list-mark-package-upgrades (fun)
- "Mark all obsolete packages for upgrading.
-Use FUN to perform marking of the current line. FUN should
-accept an entry as argument."
- (guix-package-list-marking-check)
- (let ((obsolete (cl-remove-if-not
- (lambda (entry)
- (guix-assq-value entry 'obsolete))
- guix-entries)))
- (guix-list-for-each-line
- (lambda ()
- (let* ((id (guix-list-current-id))
- (entry (cl-find-if
- (lambda (entry)
- (equal id (guix-assq-value entry 'id)))
- obsolete)))
- (when entry
- (funcall fun entry)))))))
-
-(defun guix-package-list-mark-upgrades ()
- "Mark all obsolete packages for upgrading."
- (interactive)
- (guix-list-mark-package-upgrades
- (lambda (entry)
- (apply #'guix-list--mark
- 'upgrade nil
- (guix-get-installed-outputs entry)))))
-
-(defun guix-list-execute-package-actions (fun)
- "Perform actions on the marked packages.
-Use FUN to define actions suitable for `guix-process-package-actions'.
-FUN should accept action-type as argument."
- (let ((actions (delq nil
- (mapcar fun '(install delete upgrade)))))
- (if actions
- (guix-process-package-actions
- guix-profile actions (current-buffer))
- (user-error "No operations specified"))))
-
-(defun guix-package-list-execute ()
- "Perform actions on the marked packages."
- (interactive)
- (guix-list-execute-package-actions #'guix-package-list-make-action))
+ "Parent mode for displaying data in 'list' form.")
+
+(defun guix-list-mode-initialize (entry-type)
+ "Set up the current 'list' buffer for displaying ENTRY-TYPE entries."
+ (setq tabulated-list-padding 2
+ tabulated-list-format (guix-list-tabulated-format entry-type)
+ tabulated-list-sort-key (guix-list-tabulated-sort-key entry-type))
+ (setq-local guix-list-marks (guix-list-marks entry-type))
+ (tabulated-list-init-header))
+
+(defmacro guix-list-define-interface (entry-type &rest args)
+ "Define 'list' interface for displaying ENTRY-TYPE entries.
+Remaining arguments (ARGS) should have a form [KEYWORD VALUE] ...
+
+Required keywords:
+
+ - `:format' - default value of the generated
+ `guix-ENTRY-TYPE-list-format' variable.
+
+Optional keywords:
+
+ - `:sort-key' - default value of the generated
+ `guix-ENTRY-TYPE-list-sort-key' variable.
+
+ - `:describe-function' - default value of the generated
+ `guix-ENTRY-TYPE-describe-function' variable.
+
+ - `:list-single?' - default value of the generated
+ `guix-ENTRY-TYPE-list-single' variable.
+
+ - `:marks' - default value of the generated
+ `guix-ENTRY-TYPE-list-marks' variable.
+
+The rest keyword arguments are passed to
+`guix-buffer-define-interface' macro."
+ (declare (indent 1))
+ (let* ((entry-type-str (symbol-name entry-type))
+ (prefix (concat "guix-" entry-type-str "-list"))
+ (group (intern prefix))
+ (describe-var (intern (concat prefix "-describe-function")))
+ (describe-count-var (intern (concat prefix
+ "-describe-warning-count")))
+ (format-var (intern (concat prefix "-format")))
+ (sort-key-var (intern (concat prefix "-sort-key")))
+ (list-single-var (intern (concat prefix "-single")))
+ (marks-var (intern (concat prefix "-marks"))))
+ (guix-keyword-args-let args
+ ((show-entries-val :show-entries-function)
+ (describe-val :describe-function)
+ (describe-count-val :describe-count 10)
+ (format-val :format)
+ (sort-key-val :sort-key)
+ (list-single-val :list-single?)
+ (marks-val :marks))
+ `(progn
+ (defcustom ,format-var ,format-val
+ ,(format "\
+List of format values of the displayed columns.
+Each element of the list has a form:
-(defun guix-package-list-make-action (action-type)
- "Return action specification for the packages marked with ACTION-TYPE.
-Return nil, if there are no packages marked with ACTION-TYPE.
-The specification is suitable for `guix-process-package-actions'."
- (let ((specs (guix-list-get-marked-args action-type)))
- (and specs (cons action-type specs))))
+ (PARAM VALUE-FUN WIDTH SORT . PROPS)
+
+PARAM is a name of '%s' entry parameter.
+
+VALUE-FUN may be either nil or a function returning a value that
+will be inserted. The function is called with 2 arguments: the
+first one is the value of the parameter; the second one is an
+entry (alist of parameter names and values).
+
+For the meaning of WIDTH, SORT and PROPS, see
+`tabulated-list-format'."
+ entry-type-str)
+ :type 'sexp
+ :group ',group)
+
+ (defcustom ,sort-key-var ,sort-key-val
+ ,(format "\
+Default sort key for 'list' buffer with '%s' entries.
+Should be nil (no sort) or have a form:
+
+ (PARAM . FLIP)
+
+PARAM is the name of '%s' entry parameter. For the meaning of
+FLIP, see `tabulated-list-sort-key'."
+ entry-type-str entry-type-str)
+ :type '(choice (const :tag "No sort" nil)
+ (cons symbol boolean))
+ :group ',group)
+
+ (defvar ,marks-var ,marks-val
+ ,(format "\
+Alist of additional marks for 'list' buffer with '%s' entries.
+Marks from this list are used along with `guix-list-default-marks'."
+ entry-type-str))
+
+ (defcustom ,list-single-var ,list-single-val
+ ,(format "\
+If non-nil, list '%s' entry even if it is the only matching result.
+If nil, show a single '%s' entry in the 'info' buffer."
+ entry-type-str entry-type-str)
+ :type 'boolean
+ :group ',group)
+
+ (defcustom ,describe-count-var ,describe-count-val
+ ,(format "\
+The maximum number of '%s' entries to describe without a warning.
+If a user wants to describe more than this number of marked
+entries, he will be prompted for confirmation.
+See also `guix-list-describe'."
+ entry-type-str)
+ :type 'integer
+ :group ',group)
+
+ (defvar ,describe-var ,describe-val
+ ,(format "Function used to describe '%s' entries."
+ entry-type-str))
+
+ (guix-alist-put!
+ '((describe . ,describe-var)
+ (describe-count . ,describe-count-var)
+ (format . ,format-var)
+ (sort-key . ,sort-key-var)
+ (list-single . ,list-single-var)
+ (marks . ,marks-var))
+ 'guix-list-data ',entry-type)
+
+ ,(if show-entries-val
+ `(guix-buffer-define-interface list ,entry-type
+ :show-entries-function ,show-entries-val
+ ,@%foreign-args)
+
+ (let ((insert-fun (intern (concat prefix "-insert-entries")))
+ (mode-init-fun (intern (concat prefix "-mode-initialize"))))
+ `(progn
+ (defun ,insert-fun (entries)
+ ,(format "\
+Print '%s' ENTRIES in the current 'list' buffer."
+ entry-type-str)
+ (guix-list-insert-entries entries ',entry-type))
+
+ (defun ,mode-init-fun ()
+ ,(format "\
+Set up the current 'list' buffer for displaying '%s' entries."
+ entry-type-str)
+ (guix-list-mode-initialize ',entry-type))
+
+ (guix-buffer-define-interface list ,entry-type
+ :insert-entries-function ',insert-fun
+ :mode-init-function ',mode-init-fun
+ ,@%foreign-args))))))))
-;;; Displaying outputs
-
-(guix-define-buffer-type list output
- :buffer-name "*Guix Package List*"
- :required (package-id))
-
-(guix-list-define-entry-type output
- :sort-key name
- :marks ((install . ?I)
- (upgrade . ?U)
- (delete . ?D)))
-
-(let ((map guix-output-list-mode-map))
- (define-key map (kbd "RET") 'guix-output-list-describe)
- (define-key map (kbd "e") 'guix-list-edit-package)
- (define-key map (kbd "x") 'guix-output-list-execute)
- (define-key map (kbd "i") 'guix-output-list-mark-install)
- (define-key map (kbd "d") 'guix-output-list-mark-delete)
- (define-key map (kbd "U") 'guix-output-list-mark-upgrade)
- (define-key map (kbd "^") 'guix-output-list-mark-upgrades))
-
-(defun guix-output-list-mark-install ()
- "Mark the current output for installation and move to the next line."
- (interactive)
- (guix-package-list-marking-check)
- (let* ((entry (guix-list-current-entry))
- (installed (guix-assq-value entry 'installed)))
- (if installed
- (user-error "This output is already installed")
- (guix-list--mark 'install t))))
-
-(defun guix-output-list-mark-delete ()
- "Mark the current output for deletion and move to the next line."
- (interactive)
- (guix-package-list-marking-check)
- (let* ((entry (guix-list-current-entry))
- (installed (guix-assq-value entry 'installed)))
- (if installed
- (guix-list--mark 'delete t)
- (user-error "This output is not installed"))))
-
-(defun guix-output-list-mark-upgrade ()
- "Mark the current output for deletion and move to the next line."
- (interactive)
- (guix-package-list-marking-check)
- (let* ((entry (guix-list-current-entry))
- (installed (guix-assq-value entry 'installed)))
- (or installed
- (user-error "This output is not installed"))
- (when (or (guix-assq-value entry 'obsolete)
- (y-or-n-p "This output is not obsolete. Try to upgrade it anyway? "))
- (guix-list--mark 'upgrade t))))
-
-(defun guix-output-list-mark-upgrades ()
- "Mark all obsolete package outputs for upgrading."
- (interactive)
- (guix-list-mark-package-upgrades
- (lambda (_) (guix-list--mark 'upgrade))))
-
-(defun guix-output-list-execute ()
- "Perform actions on the marked outputs."
- (interactive)
- (guix-list-execute-package-actions #'guix-output-list-make-action))
-
-(defun guix-output-list-make-action (action-type)
- "Return action specification for the outputs marked with ACTION-TYPE.
-Return nil, if there are no outputs marked with ACTION-TYPE.
-The specification is suitable for `guix-process-output-actions'."
- (let ((ids (guix-list-get-marked-id-list action-type)))
- (and ids (cons action-type
- (mapcar #'guix-get-package-id-and-output-by-output-id
- ids)))))
-
-(defun guix-output-list-describe (&optional arg)
- "Describe outputs or packages marked with a general mark.
-If no entries are marked, describe the current output or package.
-With prefix (if ARG is non-nil), describe entries marked with any mark.
-Also see `guix-package-info-type'."
- (interactive "P")
- (if (eq guix-package-info-type 'output)
- (guix-list-describe arg)
- (let* ((oids (or (apply #'guix-list-get-marked-id-list
- (unless arg '(general)))
- (list (guix-list-current-id))))
- (pids (mapcar (lambda (oid)
- (car (guix-get-package-id-and-output-by-output-id
- oid)))
- oids)))
- (guix-list-describe-maybe 'package (cl-remove-duplicates pids)))))
+(defvar guix-list-font-lock-keywords
+ (eval-when-compile
+ `((,(rx "(" (group "guix-list-define-interface")
+ symbol-end)
+ . 1))))
-
-;;; Displaying generations
-
-(guix-define-buffer-type list generation)
-
-(guix-list-define-entry-type generation
- :sort-key number
- :invert-sort t
- :marks ((delete . ?D)))
-
-(let ((map guix-generation-list-mode-map))
- (define-key map (kbd "RET") 'guix-generation-list-show-packages)
- (define-key map (kbd "+") 'guix-generation-list-show-added-packages)
- (define-key map (kbd "-") 'guix-generation-list-show-removed-packages)
- (define-key map (kbd "=") 'guix-generation-list-diff)
- (define-key map (kbd "D") 'guix-generation-list-diff)
- (define-key map (kbd "e") 'guix-generation-list-ediff)
- (define-key map (kbd "x") 'guix-generation-list-execute)
- (define-key map (kbd "i") 'guix-list-describe)
- (define-key map (kbd "s") 'guix-generation-list-switch)
- (define-key map (kbd "d") 'guix-generation-list-mark-delete))
-
-(defun guix-generation-list-get-current (val &optional _)
- "Return string from VAL showing whether this generation is current.
-VAL is a boolean value."
- (if val "(current)" ""))
-
-(defun guix-generation-list-switch ()
- "Switch current profile to the generation at point."
- (interactive)
- (let* ((entry (guix-list-current-entry))
- (current (guix-assq-value entry 'current))
- (number (guix-assq-value entry 'number)))
- (if current
- (user-error "This generation is already the current one")
- (guix-switch-to-generation guix-profile number (current-buffer)))))
-
-(defun guix-generation-list-show-packages ()
- "List installed packages for the generation at point."
- (interactive)
- (guix-get-show-entries guix-profile 'list guix-package-list-type
- 'generation (guix-list-current-id)))
-
-(defun guix-generation-list-generations-to-compare ()
- "Return a sorted list of 2 marked generations for comparing."
- (let ((numbers (guix-list-get-marked-id-list 'general)))
- (if (/= (length numbers) 2)
- (user-error "2 generations should be marked for comparing")
- (sort numbers #'<))))
-
-(defun guix-generation-list-show-added-packages ()
- "List package outputs added to the latest marked generation.
-If 2 generations are marked with \\[guix-list-mark], display
-outputs installed in the latest marked generation that were not
-installed in the other one."
- (interactive)
- (apply #'guix-get-show-entries
- guix-profile 'list 'output 'generation-diff
- (reverse (guix-generation-list-generations-to-compare))))
-
-(defun guix-generation-list-show-removed-packages ()
- "List package outputs removed from the latest marked generation.
-If 2 generations are marked with \\[guix-list-mark], display
-outputs not installed in the latest marked generation that were
-installed in the other one."
- (interactive)
- (apply #'guix-get-show-entries
- guix-profile 'list 'output 'generation-diff
- (guix-generation-list-generations-to-compare)))
-
-(defun guix-generation-list-compare (diff-fun gen-fun)
- "Run GEN-FUN on the 2 marked generations and run DIFF-FUN on the results."
- (cl-multiple-value-bind (gen1 gen2)
- (guix-generation-list-generations-to-compare)
- (funcall diff-fun
- (funcall gen-fun gen1)
- (funcall gen-fun gen2))))
-
-(defun guix-generation-list-ediff-manifests ()
- "Run Ediff on manifests of the 2 marked generations."
- (interactive)
- (guix-generation-list-compare
- #'ediff-files
- #'guix-profile-generation-manifest-file))
-
-(defun guix-generation-list-diff-manifests ()
- "Run Diff on manifests of the 2 marked generations."
- (interactive)
- (guix-generation-list-compare
- #'guix-diff
- #'guix-profile-generation-manifest-file))
-
-(defun guix-generation-list-ediff-packages ()
- "Run Ediff on package outputs installed in the 2 marked generations."
- (interactive)
- (guix-generation-list-compare
- #'ediff-buffers
- #'guix-profile-generation-packages-buffer))
-
-(defun guix-generation-list-diff-packages ()
- "Run Diff on package outputs installed in the 2 marked generations."
- (interactive)
- (guix-generation-list-compare
- #'guix-diff
- #'guix-profile-generation-packages-buffer))
-
-(defun guix-generation-list-ediff (arg)
- "Run Ediff on package outputs installed in the 2 marked generations.
-With ARG, run Ediff on manifests of the marked generations."
- (interactive "P")
- (if arg
- (guix-generation-list-ediff-manifests)
- (guix-generation-list-ediff-packages)))
-
-(defun guix-generation-list-diff (arg)
- "Run Diff on package outputs installed in the 2 marked generations.
-With ARG, run Diff on manifests of the marked generations."
- (interactive "P")
- (if arg
- (guix-generation-list-diff-manifests)
- (guix-generation-list-diff-packages)))
-
-(defun guix-generation-list-mark-delete (&optional arg)
- "Mark the current generation for deletion and move to the next line.
-With ARG, mark all generations for deletion."
- (interactive "P")
- (if arg
- (guix-list-mark-all 'delete)
- (guix-list--mark 'delete t)))
-
-(defun guix-generation-list-execute ()
- "Delete marked generations."
- (interactive)
- (let ((marked (guix-list-get-marked-id-list 'delete)))
- (or marked
- (user-error "No generations marked for deletion"))
- (guix-delete-generations guix-profile marked (current-buffer))))
+(font-lock-add-keywords 'emacs-lisp-mode guix-list-font-lock-keywords)
(provide 'guix-list)