gnu: glade: Use 'glib-or-gtk-build-system'.
[jackhill/guix/guix.git] / emacs / guix-info.el
index d71d8f5..6aefd2f 100644 (file)
@@ -1,4 +1,4 @@
-;;; guix-info.el --- Info buffers for displaying entries   -*- lexical-binding: t -*-
+;;; guix-info.el --- 'Info' buffer interface for displaying data  -*- lexical-binding: t -*-
 
 ;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
 ;; Copyright © 2015 Ludovic Courtès <ludo@gnu.org>
 
 ;;; Commentary:
 
-;; This file provides a help-like buffer for displaying information
-;; about Guix packages and generations.
+;; This file provides 'info' (help-like) buffer interface for displaying
+;; an arbitrary data.
 
 ;;; Code:
 
-(require 'guix-base)
+(require 'guix-buffer)
 (require 'guix-entry)
 (require 'guix-utils)
-(require 'guix-ui)
 
-(defgroup guix-info nil
-  "General settings for info buffers."
-  :prefix "guix-info-"
-  :group 'guix)
-
-(defgroup guix-info-faces nil
-  "Faces for info buffers."
-  :group 'guix-info
-  :group 'guix-faces)
+(guix-define-buffer-type info)
 
 (defface guix-info-heading
   '((((type tty pc) (class color)) :weight bold)
@@ -51,9 +42,9 @@
   "Face used for titles of parameters."
   :group 'guix-info-faces)
 
-(defface guix-info-file-path
+(defface guix-info-file-name
   '((t :inherit link))
-  "Face used for file paths."
+  "Face used for file names."
   :group 'guix-info-faces)
 
 (defface guix-info-url
@@ -202,8 +193,7 @@ LEVEL is 1 by default."
   (insert (guix-info-get-indent level)))
 
 (defun guix-info-insert-entries (entries entry-type)
-  "Display ENTRIES of ENTRY-TYPE in the current info buffer.
-ENTRIES should have a form of `guix-entries'."
+  "Display ENTRY-TYPE ENTRIES in the current info buffer."
   (guix-mapinsert (lambda (entry)
                     (guix-info-insert-entry entry entry-type))
                   entries
@@ -347,7 +337,7 @@ BUTTON-OR-FACE is a button type)."
 
 (define-button-type 'guix-file
   :supertype 'guix
-  'face 'guix-info-file-path
+  'face 'guix-info-file-name
   'help-echo "Find file"
   'action (lambda (btn)
             (guix-find-file (button-label btn))))
@@ -359,21 +349,6 @@ BUTTON-OR-FACE is a button type)."
   'action (lambda (btn)
             (browse-url (button-label btn))))
 
-(define-button-type 'guix-package-location
-  :supertype 'guix
-  'face 'guix-package-info-location
-  'help-echo "Find location of this package"
-  'action (lambda (btn)
-            (guix-find-location (button-label btn))))
-
-(define-button-type 'guix-package-name
-  :supertype 'guix
-  'face 'guix-package-info-name-button
-  'help-echo "Describe this package"
-  'action (lambda (btn)
-            (guix-get-show-entries guix-profile 'info guix-package-info-type
-                                   'name (button-label btn))))
-
 (defun guix-info-button-copy-label (&optional pos)
   "Copy a label of the button at POS into kill ring.
 If POS is nil, use the current point position."
@@ -407,7 +382,17 @@ See `insert-text-button' for the meaning of PROPERTIES."
   "Keymap for `guix-info-mode' buffers.")
 
 (define-derived-mode guix-info-mode special-mode "Guix-Info"
-  "Parent mode for displaying information in info buffers.")
+  "Parent mode for displaying data in 'info' form."
+  (setq-local revert-buffer-function 'guix-buffer-revert))
+
+(defun guix-info-mode-initialize ()
+  "Set up the current 'info' buffer."
+  ;; Without this, syntactic fontification is performed, and it may
+  ;; break our highlighting.  For example, description of "emacs-typo"
+  ;; package contains a single " (double-quote) character, so the
+  ;; default syntactic fontification highlights the rest text after it
+  ;; as a string.  See (info "(elisp) Font Lock Basics") for details.
+  (setq font-lock-defaults '(nil t)))
 
 (defmacro guix-info-define-interface (entry-type &rest args)
   "Define 'info' interface for displaying ENTRY-TYPE entries.
@@ -426,7 +411,8 @@ The rest keyword arguments are passed to
          (group              (intern prefix))
          (format-var         (intern (concat prefix "-format"))))
     (guix-keyword-args-let args
-        ((format-val         :format))
+        ((show-entries-val   :show-entries-function)
+         (format-val         :format))
       `(progn
          (defcustom ,format-var ,format-val
            ,(format "\
@@ -464,430 +450,23 @@ After calling each METHOD, a new line is inserted."
           '((format . ,format-var))
           'guix-info-data ',entry-type)
 
-         (guix-buffer-define-interface info ,entry-type
-           ,@%foreign-args)))))
-
-\f
-;;; Displaying packages
-
-(guix-ui-info-define-interface package
-  :buffer-name "*Guix Package Info*"
-  :format '(guix-package-info-insert-heading
-            ignore
-            (synopsis ignore (simple guix-package-info-synopsis))
-            ignore
-            (description ignore (simple guix-package-info-description))
-            ignore
-            (outputs simple guix-package-info-insert-outputs)
-            (source simple guix-package-info-insert-source)
-            (location format (format guix-package-location))
-            (home-url format (format guix-url))
-            (license format (format guix-package-info-license))
-            (inputs format (format guix-package-input))
-            (native-inputs format (format guix-package-native-input))
-            (propagated-inputs format
-                               (format guix-package-propagated-input)))
-  :titles '((home-url . "Home page"))
-  :required '(id name version installed non-unique))
-
-(guix-info-define-interface installed-output
-  :format '((path simple (indent guix-file))
-            (dependencies simple (indent guix-file)))
-  :titles '((path . "Store directory"))
-  :reduced? t)
-
-(defface guix-package-info-heading
-  '((t :inherit guix-info-heading))
-  "Face for package name and version headings."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-name
-  '((t :inherit font-lock-keyword-face))
-  "Face used for a name of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-name-button
-  '((t :inherit button))
-  "Face used for a full name that can be used to describe a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-version
-  '((t :inherit font-lock-builtin-face))
-  "Face used for a version of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-synopsis
-  '((((type tty pc) (class color)) :weight bold)
-    (t :height 1.1 :weight bold :inherit variable-pitch))
-  "Face used for a synopsis of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-description
-  '((t))
-  "Face used for a description of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-license
-  '((t :inherit font-lock-string-face))
-  "Face used for a license of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-location
-  '((t :inherit link))
-  "Face used for a location of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-installed-outputs
-  '((default :weight bold)
-    (((class color) (min-colors 88) (background light))
-     :foreground "ForestGreen")
-    (((class color) (min-colors 88) (background dark))
-     :foreground "PaleGreen")
-    (((class color) (min-colors 8))
-     :foreground "green")
-    (t :underline t))
-  "Face used for installed outputs of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-uninstalled-outputs
-  '((t :weight bold))
-  "Face used for uninstalled outputs of a package."
-  :group 'guix-package-info-faces)
-
-(defface guix-package-info-obsolete
-  '((t :inherit error))
-  "Face used if a package is obsolete."
-  :group 'guix-package-info-faces)
-
-(defun guix-package-info-insert-heading (entry)
-  "Insert package ENTRY heading (name specification) at point."
-  (guix-insert-button
-   (guix-package-entry->name-specification entry)
-   'guix-package-name
-   'face 'guix-package-info-heading))
-
-(defmacro guix-package-info-define-insert-inputs (&optional type)
-  "Define a face and a function for inserting package inputs.
-TYPE is a type of inputs.
-Function name is `guix-package-info-insert-TYPE-inputs'.
-Face name is `guix-package-info-TYPE-inputs'."
-  (let* ((type-str (symbol-name type))
-         (type-name (and type (concat type-str "-")))
-         (type-desc (and type (concat type-str " ")))
-         (face (intern (concat "guix-package-info-" type-name "inputs")))
-         (btn  (intern (concat "guix-package-" type-name "input"))))
-    `(progn
-       (defface ,face
-         '((t :inherit guix-package-info-name-button))
-         ,(concat "Face used for " type-desc "inputs of a package.")
-         :group 'guix-package-info-faces)
-
-       (define-button-type ',btn
-         :supertype 'guix-package-name
-         'face ',face))))
-
-(guix-package-info-define-insert-inputs)
-(guix-package-info-define-insert-inputs native)
-(guix-package-info-define-insert-inputs propagated)
-
-\f
-;;; Inserting outputs and installed parameters
-
-(defvar guix-package-info-output-format "%-10s"
-  "String used to format output names of the packages.
-It should be a '%s'-sequence.  After inserting an output name
-formatted with this string, an action button is inserted.")
-
-(defvar guix-package-info-obsolete-string "(This package is obsolete)"
-  "String used if a package is obsolete.")
-
-(defun guix-package-info-insert-outputs (outputs entry)
-  "Insert OUTPUTS from package ENTRY at point."
-  (and (guix-entry-value entry 'obsolete)
-       (guix-package-info-insert-obsolete-text))
-  (and (guix-entry-value entry 'non-unique)
-       (guix-entry-value entry 'installed)
-       (guix-package-info-insert-non-unique-text
-        (guix-package-entry->name-specification entry)))
-  (insert "\n")
-  (mapc (lambda (output)
-          (guix-package-info-insert-output output entry))
-        outputs))
-
-(defun guix-package-info-insert-obsolete-text ()
-  "Insert a message about obsolete package at point."
-  (guix-info-insert-indent)
-  (guix-format-insert guix-package-info-obsolete-string
-                      'guix-package-info-obsolete))
-
-(defun guix-package-info-insert-non-unique-text (full-name)
-  "Insert a message about non-unique package with FULL-NAME at point."
-  (insert "\n")
-  (guix-info-insert-indent)
-  (insert "Installed outputs are displayed for a non-unique ")
-  (guix-insert-button full-name 'guix-package-name)
-  (insert " package."))
-
-(defun guix-package-info-insert-output (output entry)
-  "Insert OUTPUT at point.
-Make some fancy text with buttons and additional stuff if the
-current OUTPUT is installed (if there is such output in
-`installed' parameter of a package ENTRY)."
-  (let* ((installed (guix-entry-value entry 'installed))
-         (obsolete  (guix-entry-value entry 'obsolete))
-         (installed-entry (cl-find-if
-                           (lambda (entry)
-                             (string= (guix-entry-value entry 'output)
-                                      output))
-                           installed))
-         (action-type (if installed-entry 'delete 'install)))
-    (guix-info-insert-indent)
-    (guix-format-insert output
-                        (if installed-entry
-                            'guix-package-info-installed-outputs
-                          'guix-package-info-uninstalled-outputs)
-                        guix-package-info-output-format)
-    (guix-package-info-insert-action-button action-type entry output)
-    (when obsolete
-      (guix-info-insert-indent)
-      (guix-package-info-insert-action-button 'upgrade entry output))
-    (insert "\n")
-    (when installed-entry
-      (guix-info-insert-entry installed-entry 'installed-output 2))))
-
-(defun guix-package-info-insert-action-button (type entry output)
-  "Insert button to process an action on a package OUTPUT at point.
-TYPE is one of the following symbols: `install', `delete', `upgrade'.
-ENTRY is an alist with package info."
-  (let ((type-str (capitalize (symbol-name type)))
-        (full-name (guix-package-entry->name-specification entry output)))
-    (guix-info-insert-action-button
-     type-str
-     (lambda (btn)
-       (guix-process-package-actions
-        guix-profile
-        `((,(button-get btn 'action-type) (,(button-get btn 'id)
-                                           ,(button-get btn 'output))))
-        (current-buffer)))
-     (concat type-str " '" full-name "'")
-     'action-type type
-     'id (or (guix-entry-value entry 'package-id)
-             (guix-entry-id entry))
-     'output output)))
-
-\f
-;;; Inserting a source
-
-(defface guix-package-info-source
-  '((t :inherit link :underline nil))
-  "Face used for a source URL of a package."
-  :group 'guix-package-info-faces)
-
-(defcustom guix-package-info-auto-find-source nil
-  "If non-nil, find a source file after pressing a \"Show\" button.
-If nil, just display the source file path without finding."
-  :type 'boolean
-  :group 'guix-package-info)
-
-(defcustom guix-package-info-auto-download-source t
-  "If nil, do not automatically download a source file if it doesn't exist.
-After pressing a \"Show\" button, a derivation of the package
-source is calculated and a store file path is displayed.  If this
-variable is non-nil and the source file does not exist in the
-store, it will be automatically downloaded (with a possible
-prompt depending on `guix-operation-confirm' variable)."
-  :type 'boolean
-  :group 'guix-package-info)
-
-(defvar guix-package-info-download-buffer nil
-  "Buffer from which a current download operation was performed.")
-
-(define-button-type 'guix-package-source
-  :supertype 'guix
-  'face 'guix-package-info-source
-  'help-echo ""
-  'action (lambda (_)
-            ;; As a source may not be a real URL (e.g., "mirror://..."),
-            ;; no action is bound to a source button.
-            (message "Yes, this is the source URL. What did you expect?")))
-
-(defun guix-package-info-show-source (entry-id package-id)
-  "Show file name of a package source in the current info buffer.
-Find the file if needed (see `guix-package-info-auto-find-source').
-ENTRY-ID is an ID of the current entry (package or output).
-PACKAGE-ID is an ID of the package which source to show."
-  (let* ((entries guix-entries)
-         (entry   (guix-entry-by-id entry-id guix-entries))
-         (file    (guix-package-source-path package-id)))
-    (or file
-        (error "Couldn't define file name of the package source"))
-    (let* ((new-entry (cons (cons 'source-file file)
-                            entry))
-           (new-entries (guix-replace-entry entry-id new-entry entries)))
-      (setq guix-entries new-entries)
-      (guix-buffer-redisplay-goto-button)
-      (if (file-exists-p file)
-          (if guix-package-info-auto-find-source
-              (guix-find-file file)
-            (message "The source store path is displayed."))
-        (if guix-package-info-auto-download-source
-            (guix-package-info-download-source package-id)
-          (message "The source does not exist in the store."))))))
-
-(defun guix-package-info-download-source (package-id)
-  "Download a source of the package PACKAGE-ID."
-  (setq guix-package-info-download-buffer (current-buffer))
-  (guix-package-source-build-derivation
-   package-id
-   "The source does not exist in the store. Download it?"))
-
-(defun guix-package-info-insert-source (source entry)
-  "Insert SOURCE from package ENTRY at point.
-SOURCE is a list of URLs."
-  (if (null source)
-      (guix-format-insert nil)
-    (let* ((source-file (guix-entry-value entry 'source-file))
-           (entry-id    (guix-entry-id entry))
-           (package-id  (or (guix-entry-value entry 'package-id)
-                            entry-id)))
-      (if (null source-file)
-          (guix-info-insert-action-button
-           "Show"
-           (lambda (btn)
-             (guix-package-info-show-source (button-get btn 'entry-id)
-                                            (button-get btn 'package-id)))
-           "Show the source store directory of the current package"
-           'entry-id entry-id
-           'package-id package-id)
-        (unless (file-exists-p source-file)
-          (guix-info-insert-action-button
-           "Download"
-           (lambda (btn)
-             (guix-package-info-download-source
-              (button-get btn 'package-id)))
-           "Download the source into the store"
-           'package-id package-id))
-        (guix-info-insert-value-indent source-file 'guix-file))
-      (guix-info-insert-value-indent source 'guix-package-source))))
-
-(defun guix-package-info-redisplay-after-download ()
-  "Redisplay an 'info' buffer after downloading the package source.
-This function is used to hide a \"Download\" button if needed."
-  (when (buffer-live-p guix-package-info-download-buffer)
-    (with-current-buffer guix-package-info-download-buffer
-      (guix-buffer-redisplay-goto-button))
-    (setq guix-package-info-download-buffer nil)))
-
-(add-hook 'guix-after-source-download-hook
-          'guix-package-info-redisplay-after-download)
-
-\f
-;;; Displaying outputs
-
-(guix-ui-info-define-interface output
-  :buffer-name "*Guix Package Info*"
-  :format '((name format (format guix-package-info-name))
-            (version format guix-output-info-insert-version)
-            (output format guix-output-info-insert-output)
-            (synopsis simple (indent guix-package-info-synopsis))
-            (source simple guix-package-info-insert-source)
-            (path simple (indent guix-file))
-            (dependencies simple (indent guix-file))
-            (location format (format guix-package-location))
-            (home-url format (format guix-url))
-            (license format (format guix-package-info-license))
-            (inputs format (format guix-package-input))
-            (native-inputs format (format guix-package-native-input))
-            (propagated-inputs format
-                               (format guix-package-propagated-input))
-            (description simple (indent guix-package-info-description)))
-  :titles guix-package-info-titles
-  :required '(id package-id installed non-unique))
-
-(defun guix-output-info-insert-version (version entry)
-  "Insert output VERSION and obsolete text if needed at point."
-  (guix-info-insert-value-format version
-                                 'guix-package-info-version)
-  (and (guix-entry-value entry 'obsolete)
-       (guix-package-info-insert-obsolete-text)))
-
-(defun guix-output-info-insert-output (output entry)
-  "Insert OUTPUT and action buttons at point."
-  (let* ((installed (guix-entry-value entry 'installed))
-         (obsolete  (guix-entry-value entry 'obsolete))
-         (action-type (if installed 'delete 'install)))
-    (guix-info-insert-value-format
-     output
-     (if installed
-         'guix-package-info-installed-outputs
-       'guix-package-info-uninstalled-outputs))
-    (guix-info-insert-indent)
-    (guix-package-info-insert-action-button action-type entry output)
-    (when obsolete
-      (guix-info-insert-indent)
-      (guix-package-info-insert-action-button 'upgrade entry output))))
-
-\f
-;;; Displaying generations
-
-(guix-ui-info-define-interface generation
-  :buffer-name "*Guix Generation Info*"
-  :format '((number format guix-generation-info-insert-number)
-            (prev-number format (format))
-            (current format guix-generation-info-insert-current)
-            (path simple (indent guix-file))
-            (time format (time)))
-  :titles '((path . "File name")
-            (prev-number . "Previous number")))
-
-(defface guix-generation-info-number
-  '((t :inherit font-lock-keyword-face))
-  "Face used for a number of a generation."
-  :group 'guix-generation-info-faces)
-
-(defface guix-generation-info-current
-  '((t :inherit guix-package-info-installed-outputs))
-  "Face used if a generation is the current one."
-  :group 'guix-generation-info-faces)
-
-(defface guix-generation-info-not-current
-  '((t nil))
-  "Face used if a generation is not the current one."
-  :group 'guix-generation-info-faces)
-
-(defun guix-generation-info-insert-number (number &optional _)
-  "Insert generation NUMBER and action buttons."
-  (guix-info-insert-value-format number 'guix-generation-info-number)
-  (guix-info-insert-indent)
-  (guix-info-insert-action-button
-   "Packages"
-   (lambda (btn)
-     (guix-get-show-entries guix-profile 'list guix-package-list-type
-                            'generation (button-get btn 'number)))
-   "Show installed packages for this generation"
-   'number number)
-  (guix-info-insert-indent)
-  (guix-info-insert-action-button
-   "Delete"
-   (lambda (btn)
-     (guix-delete-generations guix-profile (list (button-get btn 'number))
-                              (current-buffer)))
-   "Delete this generation"
-   'number number))
-
-(defun guix-generation-info-insert-current (val entry)
-  "Insert boolean value VAL showing whether this generation is current."
-  (if val
-      (guix-info-insert-value-format "Yes" 'guix-generation-info-current)
-    (guix-info-insert-value-format "No" 'guix-generation-info-not-current)
-    (guix-info-insert-indent)
-    (guix-info-insert-action-button
-     "Switch"
-     (lambda (btn)
-       (guix-switch-to-generation guix-profile (button-get btn 'number)
-                                  (current-buffer)))
-     "Switch to this generation (make it the current one)"
-     'number (guix-entry-value entry 'number))))
+         ,(if show-entries-val
+              `(guix-buffer-define-interface info ,entry-type
+                 :show-entries-function ,show-entries-val
+                 ,@%foreign-args)
+
+            (let ((insert-fun (intern (concat prefix "-insert-entries"))))
+              `(progn
+                 (defun ,insert-fun (entries)
+                   ,(format "\
+Print '%s' ENTRIES in the current 'info' buffer."
+                            entry-type-str)
+                   (guix-info-insert-entries entries ',entry-type))
+
+                 (guix-buffer-define-interface info ,entry-type
+                   :insert-entries-function ',insert-fun
+                   :mode-init-function 'guix-info-mode-initialize
+                   ,@%foreign-args))))))))
 
 \f
 (defvar guix-info-font-lock-keywords