1 ;;; guix-info.el --- Info buffers for displaying entries
3 ;; Copyright © 2014 Alex Kost <alezost@gmail.com>
5 ;; This file is part of GNU Guix.
7 ;; GNU Guix 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.
12 ;; GNU Guix 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.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ;; This file provides a help-like buffer for displaying information
23 ;; about Guix packages and generations.
27 (require 'guix-history
)
31 (defgroup guix-info nil
32 "General settings for info buffers."
36 (defface guix-info-param-title
37 '((t :inherit font-lock-type-face
))
38 "Face used for titles of parameters."
41 (defface guix-info-file-path
43 "Face used for file paths."
46 (defface guix-info-url
51 (defface guix-info-time
52 '((t :inherit font-lock-constant-face
))
53 "Face used for timestamps."
56 (defface guix-info-action-button
57 '((((type x w32 ns
) (class color
))
58 :box
(:line-width
2 :style released-button
)
59 :background
"lightgrey" :foreground
"black")
61 "Face used for action buttons."
64 (defface guix-info-action-button-mouse
65 '((((type x w32 ns
) (class color
))
66 :box
(:line-width
2 :style released-button
)
67 :background
"grey90" :foreground
"black")
68 (t :inherit highlight
))
69 "Mouse face used for action buttons."
72 (defcustom guix-info-ignore-empty-vals nil
73 "If non-nil, do not display parameters with nil values."
77 (defvar guix-info-param-title-format
"%-18s: "
78 "String used to format a title of a parameter.
79 It should be a '%s'-sequence. After inserting a title formatted
80 with this string, a value of the parameter is inserted.
81 This string is used by `guix-info-insert-title-default'.")
83 (defvar guix-info-multiline-prefix
(make-string 20 ?\s
)
84 "String used to format multi-line parameter values.
85 If a value occupies more than one line, this string is inserted
86 in the beginning of each line after the first one.
87 This string is used by `guix-info-insert-val-default'.")
89 (defvar guix-info-indent
2
90 "Number of spaces used to indent various parts of inserted text.")
92 (defvar guix-info-fill-column
60
93 "Column used for filling (word wrapping) parameters with long lines.
94 If a value is not multi-line and it occupies more than this
95 number of characters, it will be split into several lines.")
97 (defvar guix-info-delimiter
"\n\f\n"
98 "String used to separate entries.")
100 (defvar guix-info-insert-methods
102 (name guix-package-info-name
)
103 (version guix-package-info-version
)
104 (license guix-package-info-license
)
105 (synopsis guix-package-info-synopsis
)
106 (description guix-package-info-insert-description
107 guix-info-insert-title-simple
)
108 (outputs guix-package-info-insert-outputs
109 guix-info-insert-title-simple
)
110 (home-url guix-info-insert-url
)
111 (inputs guix-package-info-insert-inputs
)
112 (native-inputs guix-package-info-insert-native-inputs
)
113 (propagated-inputs guix-package-info-insert-propagated-inputs
)
114 (location guix-package-info-insert-location
))
116 (path guix-package-info-insert-output-path
117 guix-info-insert-title-simple
)
118 (dependencies guix-package-info-insert-output-dependencies
119 guix-info-insert-title-simple
))
121 (number guix-generation-info-insert-number
)
122 (path guix-info-insert-file-path
)
123 (time guix-info-insert-time
)))
124 "Methods for inserting parameter values.
125 Each element of the list should have a form:
127 (ENTRY-TYPE . ((PARAM INSERT-VALUE [INSERT-TITLE]) ...))
129 INSERT-VALUE may be either nil, a face name or a function. If it
130 is nil or a face, `guix-info-insert-val-default' function is
131 called with parameter value and INSERT-VALUE as arguments. If it
132 is a function, this function is called with parameter value and
133 entry info (alist of parameters and their values) as arguments.
135 INSERT-TITLE may be either nil, a face name or a function. If it
136 is nil or a face, `guix-info-insert-title-default' function is
137 called with parameter title and INSERT-TITLE as arguments. If it
138 is a function, this function is called with parameter title as
141 (defvar guix-info-displayed-params
142 '((package name version synopsis outputs location home-url
143 license inputs native-inputs propagated-inputs description
)
144 (installed path dependencies
)
145 (generation number prev-number time path
))
146 "List of displayed entry parameters.
147 Each element of the list should have a form:
149 (ENTRY-TYPE . (PARAM ...))
151 The order of displayed parameters is the same as in this list.")
153 (defun guix-info-get-insert-methods (entry-type param
)
154 "Return list of insert methods for parameter PARAM of ENTRY-TYPE.
155 See `guix-info-insert-methods' for details."
156 (guix-get-key-val guix-info-insert-methods
159 (defun guix-info-get-displayed-params (entry-type)
160 "Return parameters of ENTRY-TYPE that should be displayed."
161 (guix-get-key-val guix-info-displayed-params
164 (defun guix-info-get-indent (&optional level
)
165 "Return `guix-info-indent' \"multiplied\" by LEVEL spaces.
166 LEVEL is 1 by default."
167 (make-string (* guix-info-indent
(or level
1)) ?\s
))
169 (defun guix-info-insert-indent (&optional level
)
170 "Insert `guix-info-indent' spaces LEVEL times (1 by default)."
171 (insert (guix-info-get-indent level
)))
173 (defun guix-info-insert-entries (entries entry-type
)
174 "Display ENTRIES of ENTRY-TYPE in the current info buffer.
175 ENTRIES should have a form of `guix-entries'."
176 (guix-mapinsert (lambda (entry)
177 (guix-info-insert-entry entry entry-type
))
179 guix-info-delimiter
))
181 (defun guix-info-insert-entry (entry entry-type
&optional indent-level
)
182 "Insert ENTRY of ENTRY-TYPE into the current info buffer.
183 If INDENT-LEVEL is non-nil, indent displayed information by this
184 number of `guix-info-indent' spaces."
185 (let ((region-beg (point)))
186 (mapc (lambda (param)
187 (guix-info-insert-param param entry entry-type
))
188 (guix-info-get-displayed-params entry-type
))
190 (indent-rigidly region-beg
(point)
191 (* indent-level guix-info-indent
)))))
193 (defun guix-info-insert-param (param entry entry-type
)
194 "Insert title and value of a PARAM at point.
195 ENTRY is alist with parameters and their values.
196 ENTRY-TYPE is a type of ENTRY."
197 (let ((val (guix-get-key-val entry param
)))
198 (unless (and guix-info-ignore-empty-vals
(null val
))
199 (let* ((title (guix-get-param-title entry-type param
))
200 (insert-methods (guix-info-get-insert-methods entry-type param
))
201 (val-method (car insert-methods
))
202 (title-method (cadr insert-methods
)))
203 (guix-info-method-funcall title title-method
204 #'guix-info-insert-title-default
)
205 (guix-info-method-funcall val val-method
206 #'guix-info-insert-val-default
210 (defun guix-info-method-funcall (val method default-fun
&rest args
)
211 "Call METHOD or DEFAULT-FUN.
213 If METHOD is a function and VAL is non-nil, call this
214 function by applying it to VAL and ARGS.
216 If METHOD is a face, propertize inserted VAL with this face."
217 (cond ((or (null method
)
219 (funcall default-fun val method
))
221 (apply method val args
))
222 (t (error "Unknown method '%S'" method
))))
224 (defun guix-info-insert-title-default (title &optional face format
)
225 "Insert TITLE formatted with `guix-info-param-title-format' at point."
226 (guix-format-insert title
227 (or face
'guix-info-param-title
)
228 (or format guix-info-param-title-format
)))
230 (defun guix-info-insert-title-simple (title &optional face
)
231 "Insert TITLE at point."
232 (guix-info-insert-title-default title face
"%s:"))
234 (defun guix-info-insert-val-default (val &optional face
)
235 "Format and insert parameter value VAL at point.
237 This function is intended to be called after
238 `guix-info-insert-title-default'.
240 If VAL is a one-line string longer than `guix-info-fill-column',
241 split it into several short lines. See also
242 `guix-info-multiline-prefix'.
244 If FACE is non-nil, propertize inserted line(s) with this FACE."
245 (guix-split-insert val face
246 guix-info-fill-column
247 (concat "\n" guix-info-multiline-prefix
)))
249 (defun guix-info-insert-val-simple (val &optional face-or-fun
)
250 "Format and insert parameter value VAL at point.
252 This function is intended to be called after
253 `guix-info-insert-title-simple'.
255 If VAL is a one-line string longer than `guix-info-fill-column',
256 split it into several short lines and indent each line with
257 `guix-info-indent' spaces.
259 If FACE-OR-FUN is a face, propertize inserted line(s) with this FACE.
261 If FACE-OR-FUN is a function, call it with VAL as argument. If
262 VAL is a list, call the function on each element of this list."
264 (progn (guix-info-insert-indent)
265 (guix-format-insert nil
))
266 (let ((prefix (concat "\n" (guix-info-get-indent))))
268 (if (functionp face-or-fun
)
269 (guix-mapinsert face-or-fun
270 (if (listp val
) val
(list val
))
272 (guix-split-insert val face-or-fun
273 guix-info-fill-column prefix
)))))
275 (defun guix-info-insert-action-button (label action
&optional message
277 "Make action button with LABEL and insert it at point.
278 For the meaning of ACTION, MESSAGE and PROPERTIES, see
279 `guix-insert-button'."
280 (apply #'guix-insert-button
281 label
'guix-info-action-button action message
282 'mouse-face
'guix-info-action-button-mouse
285 (defun guix-info-insert-file-path (path &optional _
)
286 "Make button from file PATH and insert it at point."
288 path
'guix-info-file-path
289 (lambda (btn) (find-file (button-label btn
)))
292 (defun guix-info-insert-url (url &optional _
)
293 "Make button from URL and insert it at point."
296 (lambda (btn) (browse-url (button-label btn
)))
299 (defun guix-info-insert-time (seconds &optional _
)
300 "Insert formatted time string using SECONDS at point."
301 (guix-info-insert-val-default (guix-get-time-string seconds
)
305 (defvar guix-info-mode-map
306 (let ((map (make-sparse-keymap)))
308 map
(make-composed-keymap button-buffer-map
311 "Parent keymap for info buffers.")
313 (define-derived-mode guix-info-mode special-mode
"Guix-Info"
314 "Parent mode for displaying information in info buffers.")
317 ;;; Displaying packages
319 (guix-define-buffer-type info package
320 :required
(id installed non-unique
))
322 (defface guix-package-info-name
323 '((t :inherit font-lock-keyword-face
))
324 "Face used for a name of a package."
325 :group
'guix-package-info
)
327 (defface guix-package-info-version
328 '((t :inherit font-lock-builtin-face
))
329 "Face used for a version of a package."
330 :group
'guix-package-info
)
332 (defface guix-package-info-synopsis
333 '((t :inherit font-lock-doc-face
))
334 "Face used for a synopsis of a package."
335 :group
'guix-package-info
)
337 (defface guix-package-info-description
339 "Face used for a description of a package."
340 :group
'guix-package-info
)
342 (defface guix-package-info-license
343 '((t :inherit font-lock-string-face
))
344 "Face used for a license of a package."
345 :group
'guix-package-info
)
347 (defface guix-package-info-location
349 "Face used for a location of a package."
350 :group
'guix-package-info
)
352 (defface guix-package-info-installed-outputs
353 '((default :weight bold
)
354 (((class color
) (min-colors 88) (background light
))
355 :foreground
"ForestGreen")
356 (((class color
) (min-colors 88) (background dark
))
357 :foreground
"PaleGreen")
358 (((class color
) (min-colors 8))
361 "Face used for installed outputs of a package."
362 :group
'guix-package-info
)
364 (defface guix-package-info-uninstalled-outputs
366 "Face used for uninstalled outputs of a package."
367 :group
'guix-package-info
)
369 (defface guix-package-info-obsolete
370 '((t :inherit error
))
371 "Face used if a package is obsolete."
372 :group
'guix-package-info
)
374 (defun guix-package-info-insert-description (desc &optional _
)
375 "Insert description DESC at point."
376 (guix-info-insert-val-simple desc
'guix-package-info-description
))
378 (defun guix-package-info-insert-location (location &optional _
)
379 "Make button from file LOCATION and insert it at point."
381 location
'guix-package-info-location
382 (lambda (btn) (guix-find-location (button-label btn
)))
383 "Find location of this package"))
385 (defmacro guix-package-info-define-insert-inputs
(&optional type
)
386 "Define a face and a function for inserting package inputs.
387 TYPE is a type of inputs.
388 Function name is `guix-package-info-insert-TYPE-inputs'.
389 Face name is `guix-package-info-TYPE-inputs'."
390 (let* ((type-str (symbol-name type
))
391 (type-name (and type
(concat type-str
"-")))
392 (type-desc (and type
(concat type-str
" ")))
393 (face (intern (concat "guix-package-info-" type-name
"inputs")))
394 (fun (intern (concat "guix-package-info-insert-" type-name
"inputs"))))
397 '((t :inherit button
))
398 ,(concat "Face used for " type-desc
"inputs of a package.")
399 :group
'guix-package-info
)
401 (defun ,fun
(inputs &optional _
)
402 ,(concat "Make buttons from " type-desc
"INPUTS and insert them at point.")
403 (guix-package-info-insert-full-names inputs
',face
)))))
405 (guix-package-info-define-insert-inputs)
406 (guix-package-info-define-insert-inputs native
)
407 (guix-package-info-define-insert-inputs propagated
)
409 (defun guix-package-info-insert-full-names (names face
)
410 "Make buttons from package NAMES and insert them at point.
411 NAMES is a list of strings.
412 Propertize buttons with FACE."
414 (guix-info-insert-val-default
416 (guix-mapinsert (lambda (name)
417 (guix-package-info-insert-full-name
421 (buffer-substring (point-min) (point-max))))
422 (guix-format-insert nil
)))
424 (defun guix-package-info-insert-full-name (name face
)
425 "Make button and insert package NAME at point.
426 Propertize package button with FACE."
430 (guix-package-info-get-show 'name
(button-label btn
)))
431 "Describe this package"))
434 ;;; Inserting outputs and installed parameters
436 (defvar guix-package-info-output-format
"%-10s"
437 "String used to format output names of the packages.
438 It should be a '%s'-sequence. After inserting an output name
439 formatted with this string, an action button is inserted.")
441 (defvar guix-package-info-obsolete-string
"(This package is obsolete)"
442 "String used if a package is obsolete.")
444 (defun guix-package-info-insert-outputs (outputs entry
)
445 "Insert OUTPUTS from package ENTRY at point."
446 (and (guix-get-key-val entry
'obsolete
)
447 (guix-package-info-insert-obsolete-text))
448 (and (guix-get-key-val entry
'non-unique
)
449 (guix-get-key-val entry
'installed
)
450 (guix-package-info-insert-non-unique-text
451 (guix-get-full-name entry
)))
453 (mapc (lambda (output)
454 (guix-package-info-insert-output output entry
))
457 (defun guix-package-info-insert-obsolete-text ()
458 "Insert a message about obsolete package at point."
459 (guix-info-insert-indent)
460 (guix-format-insert guix-package-info-obsolete-string
461 'guix-package-info-obsolete
))
463 (defun guix-package-info-insert-non-unique-text (full-name)
464 "Insert a message about non-unique package with FULL-NAME at point."
466 (guix-info-insert-indent)
467 (insert "Installed outputs are displayed for a non-unique ")
468 (guix-package-info-insert-full-name full-name
469 'guix-package-info-inputs
)
470 (insert " package."))
472 (defun guix-package-info-insert-output (output entry
)
473 "Insert OUTPUT at point.
474 Make some fancy text with buttons and additional stuff if the
475 current OUTPUT is installed (if there is such output in
476 `installed' parameter of a package ENTRY)."
477 (let* ((installed (guix-get-key-val entry
'installed
))
478 (obsolete (guix-get-key-val entry
'obsolete
))
479 (installed-entry (cl-find-if
481 (string= (guix-get-key-val entry
'output
)
484 (action-type (if installed-entry
'delete
'install
)))
485 (guix-info-insert-indent)
486 (guix-format-insert output
488 'guix-package-info-installed-outputs
489 'guix-package-info-uninstalled-outputs
)
490 guix-package-info-output-format
)
491 (guix-package-info-insert-action-button action-type entry output
)
493 (guix-info-insert-indent)
494 (guix-package-info-insert-action-button 'upgrade entry output
))
496 (when installed-entry
497 (guix-info-insert-entry installed-entry
'installed
2))))
499 (defun guix-package-info-insert-action-button (type entry output
)
500 "Insert button to process an action on a package OUTPUT at point.
501 TYPE is one of the following symbols: `install', `delete', `upgrade'.
502 ENTRY is an alist with package info."
503 (let ((type-str (capitalize (symbol-name type
)))
504 (full-name (guix-get-full-name entry output
)))
505 (guix-info-insert-action-button
508 (guix-process-package-actions
509 (list (button-get btn
'action-type
)
510 (list (button-get btn
'id
)
511 (button-get btn
'output
)))))
512 (concat type-str
" '" full-name
"'")
514 'id
(guix-get-key-val entry
'id
)
517 (defun guix-package-info-insert-output-path (path &optional _
)
518 "Insert PATH of the installed output."
519 (guix-info-insert-val-simple path
#'guix-info-insert-file-path
))
521 (defun guix-package-info-insert-output-dependencies (deps &optional _
)
522 "Insert dependencies DEPS of the installed output."
523 (guix-info-insert-val-simple deps
#'guix-info-insert-file-path
))
526 ;;; Displaying generations
528 (guix-define-buffer-type info generation
)
530 (defface guix-generation-info-number
531 '((t :inherit font-lock-keyword-face
))
532 "Face used for a number of a generation."
533 :group
'guix-generation-info
)
535 (declare-function guix-package-list-get-show
"guix-list" t t
)
537 (defun guix-generation-info-insert-number (number &optional _
)
538 "Insert generation NUMBER and action buttons."
539 (guix-info-insert-val-default number
'guix-generation-info-number
)
540 (guix-info-insert-indent)
541 (guix-info-insert-action-button
544 (guix-package-list-get-show 'generation
545 (button-get btn
'number
)))
546 "Show installed packages for this generation"
548 (guix-info-insert-indent)
549 (guix-info-insert-action-button
551 (lambda (btn) (error "Sorry, not implemented yet"))
552 "Delete this generation"))
556 ;;; guix-info.el ends here