summary refs log tree commit diff
path: root/emacs
diff options
context:
space:
mode:
Diffstat (limited to 'emacs')
-rw-r--r--emacs/guix-backend.el14
-rw-r--r--emacs/guix-base.el121
-rw-r--r--emacs/guix-command.el671
-rw-r--r--emacs/guix-config.el.in40
-rw-r--r--emacs/guix-external.el72
-rw-r--r--emacs/guix-help-vars.el108
-rw-r--r--emacs/guix-info.el48
-rw-r--r--emacs/guix-init.el (renamed from emacs/guix-init.el.in)4
-rw-r--r--emacs/guix-list.el52
-rw-r--r--emacs/guix-main.scm78
-rw-r--r--emacs/guix-messages.el8
-rw-r--r--emacs/guix-pcomplete.el91
-rw-r--r--emacs/guix-popup.el48
-rw-r--r--emacs/guix-prettify.el17
-rw-r--r--emacs/guix-profiles.el (renamed from emacs/guix-profiles.el.in)4
-rw-r--r--emacs/guix-read.el176
-rw-r--r--emacs/guix-utils.el120
-rw-r--r--emacs/guix.el12
18 files changed, 1497 insertions, 187 deletions
diff --git a/emacs/guix-backend.el b/emacs/guix-backend.el
index 73a429b9ee..7db1daacf0 100644
--- a/emacs/guix-backend.el
+++ b/emacs/guix-backend.el
@@ -52,26 +52,16 @@
 ;;; Code:
 
 (require 'geiser-mode)
+(require 'guix-config)
 (require 'guix-emacs)
 
-(defvar guix-load-path
-  (file-name-directory (or load-file-name
-                           (locate-library "guix")))
+(defvar guix-load-path guix-emacs-interface-directory
   "Directory with scheme files for \"guix.el\" package.")
 
 (defvar guix-helper-file
   (expand-file-name "guix-helper.scm" guix-load-path)
   "Auxiliary scheme file for loading.")
 
-(defvar guix-guile-program (or geiser-guile-binary "guile")
-  "Name of the guile executable used for Guix REPL.
-May be either a string (the name of the executable) or a list of
-strings of the form:
-
-  (NAME . ARGS)
-
-Where ARGS is a list of arguments to the guile program.")
-
 
 ;;; REPL
 
diff --git a/emacs/guix-base.el b/emacs/guix-base.el
index fe89584f18..3bee910b05 100644
--- a/emacs/guix-base.el
+++ b/emacs/guix-base.el
@@ -1,6 +1,6 @@
 ;;; guix-base.el --- Common definitions   -*- lexical-binding: t -*-
 
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
 
 ;; This file is part of GNU Guix.
 
@@ -89,8 +89,8 @@ Each element of the list has a form:
 
 (defun guix-get-param-title (entry-type param)
   "Return title of an ENTRY-TYPE entry parameter PARAM."
-  (or (guix-get-key-val guix-param-titles
-                        entry-type param)
+  (or (guix-assq-value guix-param-titles
+                       entry-type param)
       (prog1 (symbol-name param)
         (message "Couldn't find title for '%S %S'."
                  entry-type param))))
@@ -102,15 +102,15 @@ Each element of the list has a form:
 
 (defun guix-get-full-name (entry &optional output)
   "Return name specification of the package ENTRY and OUTPUT."
-  (guix-get-name-spec (guix-get-key-val entry 'name)
-                      (guix-get-key-val entry 'version)
+  (guix-get-name-spec (guix-assq-value entry 'name)
+                      (guix-assq-value entry 'version)
                       output))
 
 (defun guix-entry-to-specification (entry)
   "Return name specification by the package or output ENTRY."
-  (guix-get-name-spec (guix-get-key-val entry 'name)
-                      (guix-get-key-val entry 'version)
-                      (guix-get-key-val entry 'output)))
+  (guix-get-name-spec (guix-assq-value entry 'name)
+                      (guix-assq-value entry 'version)
+                      (guix-assq-value entry 'output)))
 
 (defun guix-entries-to-specifications (entries)
   "Return name specifications by the package or output ENTRIES."
@@ -120,13 +120,13 @@ Each element of the list has a form:
 (defun guix-get-installed-outputs (entry)
   "Return list of installed outputs for the package ENTRY."
   (mapcar (lambda (installed-entry)
-            (guix-get-key-val installed-entry 'output))
-          (guix-get-key-val entry 'installed)))
+            (guix-assq-value installed-entry 'output))
+          (guix-assq-value entry 'installed)))
 
 (defun guix-get-entry-by-id (id entries)
   "Return entry from ENTRIES by entry ID."
   (cl-find-if (lambda (entry)
-                (equal id (guix-get-key-val entry 'id)))
+                (equal id (guix-assq-value entry 'id)))
               entries))
 
 (defun guix-get-package-id-and-output-by-output-id (oid)
@@ -172,13 +172,36 @@ If PATH is relative, it is considered to be relative to
       (move-to-column col)
       (recenter 1))))
 
-(defun guix-edit-package (id)
-  "Edit (go to location of) package with ID."
-  (let ((loc (guix-eval-read (guix-make-guile-expression
-                              'package-location-string id))))
-    (if loc
-        (guix-find-location loc)
-      (message "Couldn't find package location."))))
+(defun guix-package-location (id-or-name)
+  "Return location of a package with ID-OR-NAME.
+For the meaning of location, see `guix-find-location'."
+  (guix-eval-read (guix-make-guile-expression
+                   'package-location-string id-or-name)))
+
+
+;;; Receivable lists of packages, lint checkers, etc.
+
+(guix-memoized-defun guix-graph-type-names ()
+  "Return a list of names of available graph node types."
+  (guix-eval-read (guix-make-guile-expression 'graph-type-names)))
+
+(guix-memoized-defun guix-lint-checker-names ()
+  "Return a list of names of available lint checkers."
+  (guix-eval-read (guix-make-guile-expression 'lint-checker-names)))
+
+(guix-memoized-defun guix-package-names ()
+  "Return a list of names of available packages."
+  (sort
+   ;; Work around <https://github.com/jaor/geiser/issues/64>:
+   ;; list of strings is parsed much slower than list of lists,
+   ;; so we use 'package-names-lists' instead of 'package-names'.
+
+   ;; (guix-eval-read (guix-make-guile-expression 'package-names))
+
+   (mapcar #'car
+           (guix-eval-read (guix-make-guile-expression
+                            'package-names-lists)))
+   #'string<))
 
 
 ;;; Buffers and auto updating.
@@ -392,7 +415,6 @@ following keywords are available:
          (prefix         (concat "guix-" entry-type-str "-" buf-type-str))
          (group          (intern prefix))
          (mode-map-str   (concat prefix "-mode-map"))
-         (mode-map       (intern mode-map-str))
          (parent-mode    (intern (concat "guix-" buf-type-str "-mode")))
          (mode           (intern (concat prefix "-mode")))
          (mode-init-fun  (intern (concat prefix "-mode-initialize")))
@@ -910,11 +932,11 @@ ENTRIES is a list of package entries to get info about packages."
                   (outputs (cdr spec))
                   (entry (guix-get-entry-by-id id entries)))
              (when entry
-               (let ((location (guix-get-key-val entry 'location)))
+               (let ((location (guix-assq-value entry 'location)))
                  (concat (guix-get-full-name entry)
                          (when outputs
                            (concat ":"
-                                   (mapconcat #'identity outputs ",")))
+                                   (guix-concat-strings outputs ",")))
                          (when location
                            (concat "\t(" location ")")))))))
          specs)))
@@ -1061,6 +1083,63 @@ FILE.  With a prefix argument, also prompt for PROFILE."
      operation-buffer)))
 
 
+;;; Executing guix commands
+
+(defcustom guix-run-in-shell-function #'guix-run-in-shell
+  "Function used to run guix command.
+The function is called with a single argument - a command line string."
+  :type '(choice (function-item guix-run-in-shell)
+                 (function-item guix-run-in-eshell)
+                 (function :tag "Other function"))
+  :group 'guix)
+
+(defcustom guix-shell-buffer-name "*shell*"
+  "Default name of a shell buffer used for running guix commands."
+  :type 'string
+  :group 'guix)
+
+(declare-function comint-send-input "comint" t)
+
+(defun guix-run-in-shell (string)
+  "Run command line STRING in `guix-shell-buffer-name' buffer."
+  (shell guix-shell-buffer-name)
+  (goto-char (point-max))
+  (insert string)
+  (comint-send-input))
+
+(declare-function eshell-send-input "esh-mode" t)
+
+(defun guix-run-in-eshell (string)
+  "Run command line STRING in eshell buffer."
+  (eshell)
+  (goto-char (point-max))
+  (insert string)
+  (eshell-send-input))
+
+(defun guix-run-command-in-shell (args)
+  "Execute 'guix ARGS ...' command in a shell buffer."
+  (funcall guix-run-in-shell-function
+           (guix-command-string args)))
+
+(defun guix-run-command-in-repl (args)
+  "Execute 'guix ARGS ...' command in Guix REPL."
+  (guix-eval-in-repl
+   (apply #'guix-make-guile-expression
+          'guix-command args)))
+
+(defun guix-command-output (args)
+  "Return string with 'guix ARGS ...' output."
+  (guix-eval-read
+   (apply #'guix-make-guile-expression
+          'guix-command-output args)))
+
+(defun guix-help-string (&optional commands)
+  "Return string with 'guix COMMANDS ... --help' output."
+  (guix-eval-read
+   (apply #'guix-make-guile-expression
+          'help-string commands)))
+
+
 ;;; Pull
 
 (defcustom guix-update-after-pull t
diff --git a/emacs/guix-command.el b/emacs/guix-command.el
new file mode 100644
index 0000000000..81f619f434
--- /dev/null
+++ b/emacs/guix-command.el
@@ -0,0 +1,671 @@
+;;; guix-command.el --- Popup interface for guix commands  -*- lexical-binding: t -*-
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a magit-like popup interface for running guix
+;; commands in Guix REPL.  The entry point is "M-x guix".  When it is
+;; called the first time, "guix --help" output is parsed and
+;; `guix-COMMAND-action' functions are generated for each available guix
+;; COMMAND.  Then a window with these commands is popped up.  When a
+;; particular COMMAND is called, "guix COMMAND --help" output is parsed,
+;; and a user get a new popup window with available options for this
+;; command and so on.
+
+;; To avoid hard-coding all guix options, actions, etc., as much data is
+;; taken from "guix ... --help" outputs as possible.  But this data is
+;; still incomplete: not all long options have short analogs, also
+;; special readers should be used for some options (for example, to
+;; complete package names while prompting for a package).  So after
+;; parsing --help output, the arguments are "improved".  All arguments
+;; (switches, options and actions) are `guix-command-argument'
+;; structures.
+
+;; Only "M-x guix" command is available after this file is loaded.  The
+;; rest commands/actions/popups are generated on the fly only when they
+;; are needed (that's why there is a couple of `eval'-s in this file).
+
+;; COMMANDS argument is used by many functions in this file.  It means a
+;; list of guix commands without "guix" itself, e.g.: ("build"),
+;; ("import" "gnu").  The empty list stands for the plain "guix" without
+;; subcommands.
+
+;; All actions in popup windows are divided into 2 groups:
+;;
+;; - 'Popup' actions - used to pop up another window.  For example, every
+;;   action in the 'guix' or 'guix import' window is a popup action.  They
+;;   are defined by `guix-command-define-popup-action' macro.
+;;
+;; - 'Execute' actions - used to do something with the command line (to
+;;   run a command in Guix REPL or to copy it into kill-ring) constructed
+;;   with the current popup.  They are defined by
+;;   `guix-command-define-execute-action' macro.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'guix-popup)
+(require 'guix-utils)
+(require 'guix-help-vars)
+(require 'guix-read)
+(require 'guix-base)
+(require 'guix-external)
+
+(defgroup guix-commands nil
+  "Settings for guix popup windows."
+  :group 'guix)
+
+(defvar guix-command-complex-with-shared-arguments
+  '("system")
+  "List of guix commands which have subcommands with shared options.
+I.e., 'guix foo --help' is the same as 'guix foo bar --help'.")
+
+(defun guix-command-action-name (&optional commands &rest name-parts)
+  "Return name of action function for guix COMMANDS."
+  (guix-command-symbol (append commands name-parts (list "action"))))
+
+
+;;; Command arguments
+
+(cl-defstruct (guix-command-argument
+               (:constructor guix-command-make-argument)
+               (:copier      guix-command-copy-argument))
+  name char doc fun switch? option? action?)
+
+(cl-defun guix-command-modify-argument
+    (argument &key
+              (name    nil name-bound?)
+              (char    nil char-bound?)
+              (doc     nil doc-bound?)
+              (fun     nil fun-bound?)
+              (switch? nil switch?-bound?)
+              (option? nil option?-bound?)
+              (action? nil action?-bound?))
+  "Return a modified version of ARGUMENT."
+  (declare (indent 1))
+  (let ((copy (guix-command-copy-argument argument)))
+    (and name-bound?    (setf (guix-command-argument-name    copy) name))
+    (and char-bound?    (setf (guix-command-argument-char    copy) char))
+    (and doc-bound?     (setf (guix-command-argument-doc     copy) doc))
+    (and fun-bound?     (setf (guix-command-argument-fun     copy) fun))
+    (and switch?-bound? (setf (guix-command-argument-switch? copy) switch?))
+    (and option?-bound? (setf (guix-command-argument-option? copy) option?))
+    (and action?-bound? (setf (guix-command-argument-action? copy) action?))
+    copy))
+
+(defun guix-command-modify-argument-from-alist (argument alist)
+  "Return a modified version of ARGUMENT or nil if it wasn't modified.
+Each assoc from ALIST have a form (NAME . PLIST).  NAME is an
+argument name.  PLIST is a property list of argument parameters
+to be modified."
+  (let* ((name  (guix-command-argument-name argument))
+         (plist (guix-assoc-value alist name)))
+    (when plist
+      (apply #'guix-command-modify-argument
+             argument plist))))
+
+(defmacro guix-command-define-argument-improver (name alist)
+  "Define NAME variable and function to modify an argument from ALIST."
+  (declare (indent 1))
+  `(progn
+     (defvar ,name ,alist)
+     (defun ,name (argument)
+       (guix-command-modify-argument-from-alist argument ,name))))
+
+(guix-command-define-argument-improver
+    guix-command-improve-action-argument
+  '(("graph"       :char ?G)
+    ("environment" :char ?E)
+    ("publish"     :char ?u)
+    ("pull"        :char ?P)
+    ("size"        :char ?z)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-common-argument
+  '(("--help"    :switch? nil)
+    ("--version" :switch? nil)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-target-argument
+  '(("--target" :char ?T)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-system-type-argument
+  '(("--system" :fun guix-read-system-type)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-load-path-argument
+  '(("--load-path" :fun read-directory-name)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-search-paths-argument
+  '(("--search-paths" :char ?P)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-substitute-urls-argument
+  '(("--substitute-urls" :char ?U)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-hash-argument
+  '(("--format" :fun guix-read-hash-format)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-key-policy-argument
+  '(("--key-download" :fun guix-read-key-policy)))
+
+(defvar guix-command-improve-common-build-argument
+  '(("--no-substitutes"  :char ?s)
+    ("--no-build-hook"   :char ?h)
+    ("--max-silent-time" :char ?x)))
+
+(defun guix-command-improve-common-build-argument (argument)
+  (guix-command-modify-argument-from-alist
+   argument
+   (append guix-command-improve-load-path-argument
+           guix-command-improve-substitute-urls-argument
+           guix-command-improve-common-build-argument)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-archive-argument
+  '(("--generate-key" :char ?k)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-build-argument
+  '(("--no-grafts"   :char ?g)
+    ("--root"        :fun guix-read-file-name)
+    ("--sources"     :char ?S :fun guix-read-source-type :switch? nil)
+    ("--with-source" :fun guix-read-file-name)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-environment-argument
+  '(("--exec" :fun read-shell-command)
+    ("--load" :fun guix-read-file-name)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-gc-argument
+  '(("--list-dead" :char ?D)
+    ("--list-live" :char ?L)
+    ("--referrers" :char ?f)
+    ("--verify"    :fun guix-read-verify-options-string)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-graph-argument
+  '(("--type" :fun guix-read-graph-type)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-import-argument
+  '(("cran" :char ?r)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-import-elpa-argument
+  '(("--archive" :fun guix-read-elpa-archive)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-lint-argument
+  '(("--checkers" :fun guix-read-lint-checker-names-string)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-package-argument
+  ;; Unlike all other options, --install/--remove do not have a form
+  ;; '--install=foo,bar' but '--install foo bar' instead, so we need
+  ;; some tweaks.
+  '(("--install"
+     :name "--install " :fun guix-read-package-names-string
+     :switch? nil :option? t)
+    ("--remove"
+     :name "--remove "  :fun guix-read-package-names-string
+     :switch? nil :option? t)
+    ("--install-from-file" :fun guix-read-file-name)
+    ("--manifest"       :fun guix-read-file-name)
+    ("--do-not-upgrade" :char ?U)
+    ("--roll-back"      :char ?R)
+    ("--show"           :char ?w :fun guix-read-package-name)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-refresh-argument
+  '(("--select"     :fun guix-read-refresh-subset)
+    ("--key-server" :char ?S)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-size-argument
+  '(("--map-file" :fun guix-read-file-name)))
+
+(guix-command-define-argument-improver
+    guix-command-improve-system-argument
+  '(("vm-image"    :char ?V)
+    ("--on-error"  :char ?E)
+    ("--no-grub"   :char ?g)
+    ("--full-boot" :char ?b)))
+
+(defvar guix-command-argument-improvers
+  '((()
+     guix-command-improve-action-argument)
+    (("archive")
+     guix-command-improve-common-build-argument
+     guix-command-improve-target-argument
+     guix-command-improve-system-type-argument
+     guix-command-improve-archive-argument)
+    (("build")
+     guix-command-improve-common-build-argument
+     guix-command-improve-target-argument
+     guix-command-improve-system-type-argument
+     guix-command-improve-build-argument)
+    (("download")
+     guix-command-improve-hash-argument)
+    (("hash")
+     guix-command-improve-hash-argument)
+    (("environment")
+     guix-command-improve-common-build-argument
+     guix-command-improve-search-paths-argument
+     guix-command-improve-system-type-argument
+     guix-command-improve-environment-argument)
+    (("gc")
+     guix-command-improve-gc-argument)
+    (("graph")
+     guix-command-improve-graph-argument)
+    (("import")
+     guix-command-improve-import-argument)
+    (("import" "gnu")
+     guix-command-improve-key-policy-argument)
+    (("import" "elpa")
+     guix-command-improve-import-elpa-argument)
+    (("lint")
+     guix-command-improve-lint-argument)
+    (("package")
+     guix-command-improve-common-build-argument
+     guix-command-improve-search-paths-argument
+     guix-command-improve-package-argument)
+    (("refresh")
+     guix-command-improve-key-policy-argument
+     guix-command-improve-refresh-argument)
+    (("size")
+     guix-command-improve-system-type-argument
+     guix-command-improve-substitute-urls-argument
+     guix-command-improve-size-argument)
+    (("system")
+     guix-command-improve-common-build-argument
+     guix-command-improve-system-argument))
+  "Alist of guix commands and argument improvers for them.")
+
+(defun guix-command-improve-argument (argument improvers)
+  "Return ARGUMENT modified with IMPROVERS."
+  (or (guix-any (lambda (improver)
+                  (funcall improver argument))
+                improvers)
+      argument))
+
+(defun guix-command-improve-arguments (arguments commands)
+  "Return ARGUMENTS for 'guix COMMANDS ...' modified for popup interface."
+  (let ((improvers (cons 'guix-command-improve-common-argument
+                         (guix-assoc-value guix-command-argument-improvers
+                                           commands))))
+    (mapcar (lambda (argument)
+              (guix-command-improve-argument argument improvers))
+            arguments)))
+
+(defun guix-command-parse-arguments (&optional commands)
+  "Return a list of parsed 'guix COMMANDS ...' arguments."
+  (with-temp-buffer
+    (insert (guix-help-string commands))
+    (let (args)
+      (guix-while-search guix-help-parse-option-regexp
+        (let* ((short (match-string-no-properties 1))
+               (name  (match-string-no-properties 2))
+               (arg   (match-string-no-properties 3))
+               (doc   (match-string-no-properties 4))
+               (char  (if short
+                          (elt short 1) ; short option letter
+                        (elt name 2))) ; first letter of the long option
+               ;; If "--foo=bar" or "--foo[=bar]" then it is 'option'.
+               (option? (not (string= "" arg)))
+               ;; If "--foo" or "--foo[=bar]" then it is 'switch'.
+               (switch? (or (string= "" arg)
+                            (eq ?\[ (elt arg 0)))))
+          (push (guix-command-make-argument
+                 :name    name
+                 :char    char
+                 :doc     doc
+                 :switch? switch?
+                 :option? option?)
+                args)))
+      (guix-while-search guix-help-parse-command-regexp
+        (let* ((name (match-string-no-properties 1))
+               (char (elt name 0)))
+          (push (guix-command-make-argument
+                 :name    name
+                 :char    char
+                 :fun     (guix-command-action-name commands name)
+                 :action? t)
+                args)))
+      args)))
+
+(defun guix-command-rest-argument (&optional commands)
+  "Return '--' argument for COMMANDS."
+  (cl-flet ((argument (&rest args)
+              (apply #'guix-command-make-argument
+                     :name "-- " :char ?= :option? t args)))
+    (let ((command (car commands)))
+      (cond
+       ((member command '("archive" "build" "graph" "edit"
+                          "environment" "lint" "refresh"))
+        (argument :doc "Packages" :fun 'guix-read-package-names-string))
+       ((string= command "download")
+        (argument :doc "URL"))
+       ((string= command "gc")
+        (argument :doc "Paths" :fun 'guix-read-file-name))
+       ((member command '("hash" "system"))
+        (argument :doc "File" :fun 'guix-read-file-name))
+       ((string= command "size")
+        (argument :doc "Package" :fun 'guix-read-package-name))
+       ((equal commands '("import" "nix"))
+        (argument :doc "Nixpkgs Attribute"))
+       ;; Other 'guix import' subcommands, but not 'import' itself.
+       ((and (cdr commands)
+             (string= command "import"))
+        (argument :doc "Package name"))))))
+
+(defun guix-command-additional-arguments (&optional commands)
+  "Return additional arguments for COMMANDS."
+  (let ((rest-arg (guix-command-rest-argument commands)))
+    (and rest-arg (list rest-arg))))
+
+;; Ideally only `guix-command-arguments' function should exist with the
+;; contents of `guix-command-all-arguments', but we need to make a
+;; special case for `guix-command-complex-with-shared-arguments' commands.
+
+(defun guix-command-all-arguments (&optional commands)
+  "Return list of all arguments for 'guix COMMANDS ...'."
+  (let ((parsed (guix-command-parse-arguments commands)))
+    (append (guix-command-improve-arguments parsed commands)
+            (guix-command-additional-arguments commands))))
+
+(guix-memoized-defalias guix-command-all-arguments-memoize
+  guix-command-all-arguments)
+
+(defun guix-command-arguments (&optional commands)
+  "Return list of arguments for 'guix COMMANDS ...'."
+  (let ((command (car commands)))
+    (if (member command
+                guix-command-complex-with-shared-arguments)
+        ;; Take actions only for 'guix system', and switches+options for
+        ;; 'guix system foo'.
+        (funcall (if (null (cdr commands))
+                     #'cl-remove-if-not
+                   #'cl-remove-if)
+                 #'guix-command-argument-action?
+                 (guix-command-all-arguments-memoize (list command)))
+      (guix-command-all-arguments commands))))
+
+(defun guix-command-switch->popup-switch (switch)
+  "Return popup switch from command SWITCH argument."
+  (list (guix-command-argument-char switch)
+        (or (guix-command-argument-doc switch)
+            "Unknown")
+        (guix-command-argument-name switch)))
+
+(defun guix-command-option->popup-option (option)
+  "Return popup option from command OPTION argument."
+  (list (guix-command-argument-char option)
+        (or (guix-command-argument-doc option)
+            "Unknown")
+        (let ((name (guix-command-argument-name option)))
+          (if (string-match-p " \\'" name) ; ends with space
+              name
+            (concat name "=")))
+        (or (guix-command-argument-fun option)
+            'read-from-minibuffer)))
+
+(defun guix-command-action->popup-action (action)
+  "Return popup action from command ACTION argument."
+  (list (guix-command-argument-char action)
+        (or (guix-command-argument-doc action)
+            (guix-command-argument-name action)
+            "Unknown")
+        (guix-command-argument-fun action)))
+
+(defun guix-command-sort-arguments (arguments)
+  "Sort ARGUMENTS by name in alphabetical order."
+  (sort arguments
+        (lambda (a1 a2)
+          (let ((name1 (guix-command-argument-name a1))
+                (name2 (guix-command-argument-name a2)))
+            (cond ((null name1) nil)
+                  ((null name2) t)
+                  (t (string< name1 name2)))))))
+
+(defun guix-command-switches (arguments)
+  "Return switches from ARGUMENTS."
+  (cl-remove-if-not #'guix-command-argument-switch? arguments))
+
+(defun guix-command-options (arguments)
+  "Return options from ARGUMENTS."
+  (cl-remove-if-not #'guix-command-argument-option? arguments))
+
+(defun guix-command-actions (arguments)
+  "Return actions from ARGUMENTS."
+  (cl-remove-if-not #'guix-command-argument-action? arguments))
+
+(defun guix-command-post-process-args (args)
+  "Adjust appropriately command line ARGS returned from popup command."
+  ;; XXX We need to split "--install foo bar" and similar strings into
+  ;; lists of strings.  But some commands (e.g., 'guix hash') accept a
+  ;; file name as the 'rest' argument, and as file names may contain
+  ;; spaces, splitting by spaces will break such names.  For example, the
+  ;; following argument: "-- /tmp/file with spaces" will be transformed
+  ;; into the following list: ("--" "/tmp/file" "with" "spaces") instead
+  ;; of the wished ("--" "/tmp/file with spaces").
+  (let* (rest
+         (rx (rx string-start
+                 (or "-- " "--install " "--remove ")))
+         (args (mapcar (lambda (arg)
+                         (if (string-match-p rx arg)
+                             (progn (push (split-string arg) rest)
+                                    nil)
+                           arg))
+                       args)))
+    (if rest
+        (apply #'append (delq nil args) rest)
+      args)))
+
+
+;;; 'Execute' actions
+
+(defvar guix-command-default-execute-arguments
+  (list
+   (guix-command-make-argument
+    :name "repl"  :char ?r :doc "Run in Guix REPL")
+   (guix-command-make-argument
+    :name "shell" :char ?s :doc "Run in shell")
+   (guix-command-make-argument
+    :name "copy"  :char ?c :doc "Copy command line"))
+  "List of default 'execute' action arguments.")
+
+(defvar guix-command-additional-execute-arguments
+  `((("graph")
+     ,(guix-command-make-argument
+       :name "view" :char ?v :doc "View graph")))
+  "Alist of guix commands and additional 'execute' action arguments.")
+
+(defun guix-command-execute-arguments (commands)
+  "Return a list of 'execute' action arguments for COMMANDS."
+  (mapcar (lambda (arg)
+            (guix-command-modify-argument arg
+              :action? t
+              :fun (guix-command-action-name
+                    commands (guix-command-argument-name arg))))
+          (append guix-command-default-execute-arguments
+                  (guix-assoc-value
+                   guix-command-additional-execute-arguments commands))))
+
+(defvar guix-command-special-executors
+  '((("environment")
+     ("repl" . guix-run-environment-command-in-repl))
+    (("pull")
+     ("repl" . guix-run-pull-command-in-repl))
+    (("graph")
+     ("view" . guix-run-view-graph)))
+  "Alist of guix commands and alists of special executers for them.
+See also `guix-command-default-executors'.")
+
+(defvar guix-command-default-executors
+  '(("repl"  . guix-run-command-in-repl)
+    ("shell" . guix-run-command-in-shell)
+    ("copy"  . guix-copy-command-as-kill))
+  "Alist of default executers for action names.")
+
+(defun guix-command-executor (commands name)
+  "Return function to run command line arguments for guix COMMANDS."
+  (or (guix-assoc-value guix-command-special-executors commands name)
+      (guix-assoc-value guix-command-default-executors name)))
+
+(defun guix-run-environment-command-in-repl (args)
+  "Run 'guix ARGS ...' environment command in Guix REPL."
+  ;; As 'guix environment' usually tries to run another process, it may
+  ;; be fun but not wise to run this command in Geiser REPL.
+  (when (or (member "--dry-run" args)
+            (member "--search-paths" args)
+            (when (y-or-n-p
+                   (format "'%s' command will spawn an external process.
+Do you really want to execute this command in Geiser REPL? "
+                           (guix-command-string args)))
+              (message "May \"M-x shell-mode\" be with you!")
+              t))
+    (guix-run-command-in-repl args)))
+
+(defun guix-run-pull-command-in-repl (args)
+  "Run 'guix ARGS ...' pull command in Guix REPL.
+Perform pull-specific actions after operation, see
+`guix-after-pull-hook' and `guix-update-after-pull'."
+  (guix-eval-in-repl
+   (apply #'guix-make-guile-expression 'guix-command args)
+   nil 'pull))
+
+(defun guix-run-view-graph (args)
+  "Run 'guix ARGS ...' graph command, make the image and open it."
+  (let* ((graph-file (guix-dot-file-name))
+         (dot-args   (guix-dot-arguments graph-file)))
+    (if (guix-eval-read (guix-make-guile-expression
+                         'pipe-guix-output args dot-args))
+        (guix-find-file graph-file)
+      (error "Couldn't create a graph"))))
+
+
+;;; Generating popups, actions, etc.
+
+(defmacro guix-command-define-popup-action (name &optional commands)
+  "Define NAME function to generate (if needed) and run popup for COMMANDS."
+  (declare (indent 1) (debug t))
+  (let* ((popup-fun (guix-command-symbol `(,@commands "popup")))
+         (doc (format "Call `%s' (generate it if needed)."
+                      popup-fun)))
+    `(defun ,name (&optional arg)
+       ,doc
+       (interactive "P")
+       (unless (fboundp ',popup-fun)
+         (guix-command-generate-popup ',popup-fun ',commands))
+       (,popup-fun arg))))
+
+(defmacro guix-command-define-execute-action (name executor
+                                                   &optional commands)
+  "Define NAME function to execute the current action for guix COMMANDS.
+EXECUTOR function is called with the current command line arguments."
+  (declare (indent 1) (debug t))
+  (let* ((arguments-fun (guix-command-symbol `(,@commands "arguments")))
+         (doc (format "Call `%s' with the current popup arguments."
+                      executor)))
+    `(defun ,name (&rest args)
+       ,doc
+       (interactive (,arguments-fun))
+       (,executor (append ',commands
+                          (guix-command-post-process-args args))))))
+
+(defun guix-command-generate-popup-actions (actions &optional commands)
+  "Generate 'popup' commands from ACTIONS arguments for guix COMMANDS."
+  (dolist (action actions)
+    (let ((fun (guix-command-argument-fun action)))
+      (unless (fboundp fun)
+        (eval `(guix-command-define-popup-action ,fun
+                 ,(append commands
+                          (list (guix-command-argument-name action)))))))))
+
+(defun guix-command-generate-execute-actions (actions &optional commands)
+  "Generate 'execute' commands from ACTIONS arguments for guix COMMANDS."
+  (dolist (action actions)
+    (let ((fun (guix-command-argument-fun action)))
+      (unless (fboundp fun)
+        (eval `(guix-command-define-execute-action ,fun
+                 ,(guix-command-executor
+                   commands (guix-command-argument-name action))
+                 ,commands))))))
+
+(defun guix-command-generate-popup (name &optional commands)
+  "Define NAME popup with 'guix COMMANDS ...' interface."
+  (let* ((command  (car commands))
+         (man-page (concat "guix" (and command (concat "-" command))))
+         (doc      (format "Popup window for '%s' command."
+                           (guix-concat-strings (cons "guix" commands)
+                                                " ")))
+         (args     (guix-command-arguments commands))
+         (switches (guix-command-sort-arguments
+                    (guix-command-switches args)))
+         (options  (guix-command-sort-arguments
+                    (guix-command-options args)))
+         (popup-actions (guix-command-sort-arguments
+                         (guix-command-actions args)))
+         (execute-actions (unless popup-actions
+                            (guix-command-execute-arguments commands)))
+         (actions (or popup-actions execute-actions)))
+    (if popup-actions
+        (guix-command-generate-popup-actions popup-actions commands)
+      (guix-command-generate-execute-actions execute-actions commands))
+    (eval
+     `(guix-define-popup ,name
+        ,doc
+        'guix-commands
+        :man-page ,man-page
+        :switches ',(mapcar #'guix-command-switch->popup-switch switches)
+        :options  ',(mapcar #'guix-command-option->popup-option options)
+        :actions  ',(mapcar #'guix-command-action->popup-action actions)
+        :max-action-columns 4))))
+
+;;;###autoload (autoload 'guix "guix-command" "Popup window for 'guix'." t)
+(guix-command-define-popup-action guix)
+
+(defalias 'guix-edit-action #'guix-edit)
+
+
+(defvar guix-command-font-lock-keywords
+  (eval-when-compile
+    `((,(rx "("
+            (group "guix-command-define-"
+                   (or "popup-action"
+                       "execute-action"
+                       "argument-improver"))
+            symbol-end
+            (zero-or-more blank)
+            (zero-or-one
+             (group (one-or-more (or (syntax word) (syntax symbol))))))
+       (1 font-lock-keyword-face)
+       (2 font-lock-function-name-face nil t)))))
+
+(font-lock-add-keywords 'emacs-lisp-mode guix-command-font-lock-keywords)
+
+(provide 'guix-command)
+
+;;; guix-command.el ends here
diff --git a/emacs/guix-config.el.in b/emacs/guix-config.el.in
new file mode 100644
index 0000000000..16434cecea
--- /dev/null
+++ b/emacs/guix-config.el.in
@@ -0,0 +1,40 @@
+;;; guix-config.el --- Compile-time configuration of Guix.
+
+;; Copyright © 2015 Mathieu Lirzin <mthl@openmailbox.org>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(defconst guix-emacs-interface-directory
+  (replace-regexp-in-string "${prefix}" "@prefix@" "@emacsuidir@"))
+
+(defconst guix-state-directory
+  ;; This must match `NIX_STATE_DIR' as defined in `daemon.am'.
+  (or (getenv "NIX_STATE_DIR") "@guix_localstatedir@/guix"))
+
+(defvar guix-guile-program "@GUILE@"
+  "Name of the guile executable used for Guix REPL.
+May be either a string (the name of the executable) or a list of
+strings of the form:
+
+  (NAME . ARGS)
+
+Where ARGS is a list of arguments to the guile program.")
+
+(provide 'guix-config)
+
+;;; guix-config.el ends here
diff --git a/emacs/guix-external.el b/emacs/guix-external.el
new file mode 100644
index 0000000000..d233473abe
--- /dev/null
+++ b/emacs/guix-external.el
@@ -0,0 +1,72 @@
+;;; guix-external.el --- External programs  -*- lexical-binding: t -*-
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides auxiliary code for running external programs.
+
+;;; Code:
+
+(defgroup guix-external nil
+  "Settings for external programs."
+  :group 'guix)
+
+(defcustom guix-dot-program (executable-find "dot")
+  "Name of the 'dot' executable."
+  :type 'string
+  :group 'guix-external)
+
+(defcustom guix-dot-default-arguments
+  '("-Tpng")
+  "Default arguments for 'dot' program."
+  :type '(repeat string)
+  :group 'guix-external)
+
+(defcustom guix-dot-file-name-function #'guix-png-file-name
+  "Function used to define a file name of a temporary 'dot' file.
+The function is called without arguments."
+  :type '(choice (function-item guix-png-file-name)
+                 (function :tag "Other function"))
+  :group 'guix-external)
+
+(defun guix-dot-arguments (output-file &rest args)
+  "Return a list of dot arguments for writing a graph into OUTPUT-FILE.
+If ARGS is nil, use `guix-dot-default-arguments'."
+  (or guix-dot-program
+      (error (concat "Couldn't find 'dot'.\n"
+                     "Set guix-dot-program to a proper value")))
+  (apply #'list
+         guix-dot-program
+         (concat "-o" output-file)
+         (or args guix-dot-default-arguments)))
+
+(defun guix-dot-file-name ()
+  "Call `guix-dot-file-name-function'."
+  (funcall guix-dot-file-name-function))
+
+(defun guix-png-file-name ()
+  "Return '.png' file name in the `temporary-file-directory'."
+  (concat (make-temp-name
+           (concat (file-name-as-directory temporary-file-directory)
+                   "graph-"))
+          ".png"))
+
+(provide 'guix-external)
+
+;;; guix-external.el ends here
diff --git a/emacs/guix-help-vars.el b/emacs/guix-help-vars.el
new file mode 100644
index 0000000000..8117d28f3e
--- /dev/null
+++ b/emacs/guix-help-vars.el
@@ -0,0 +1,108 @@
+;;; guix-help-vars.el --- Variables related to --help output
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides regular expressions to parse various "guix
+;; ... --help" outputs and lists of non-receivable items (system types,
+;; hash formats, etc.).
+
+;;; Code:
+
+
+;;; Regexps for parsing "guix ..." outputs
+
+(defvar guix-help-parse-option-regexp
+  (rx bol "  "
+      (zero-or-one (group "-" (not (any "- ")))
+                   ",")
+      (one-or-more " ")
+      (group "--" (one-or-more (or wordchar "-")))
+      (group (zero-or-one "[")
+             (zero-or-one "="))
+      (zero-or-more (not space))
+      (one-or-more space)
+      (group (one-or-more any)))
+  "Common regexp used to find command options.")
+
+(defvar guix-help-parse-command-regexp
+  (rx bol "   "
+      (group wordchar (one-or-more (or wordchar "-"))))
+  "Regexp used to find guix commands.
+'Command' means any option not prefixed with '-'.  For example,
+guix subcommand, system action, importer, etc.")
+
+(defvar guix-help-parse-long-option-regexp
+  (rx (or "  " ", ")
+      (group "--" (one-or-more (or wordchar "-"))
+             (zero-or-one "=")))
+  "Regexp used to find long options.")
+
+(defvar guix-help-parse-short-option-regexp
+  (rx bol (one-or-more blank)
+      "-" (group (not (any "- "))))
+  "Regexp used to find short options.")
+
+(defvar guix-help-parse-package-regexp
+  (rx bol (group (one-or-more (not blank))))
+  "Regexp used to find names of the packages.")
+
+(defvar guix-help-parse-list-regexp
+  (rx bol (zero-or-more blank) "- "
+      (group (one-or-more (or wordchar "-"))))
+  "Regexp used to find various lists (lint checkers, graph types).")
+
+(defvar guix-help-parse-regexp-group 1
+  "Parenthesized expression of regexps used to find commands and
+options.")
+
+
+;;; Non-receivable lists of system types, hash formats, etc.
+
+(defvar guix-help-system-types
+  '("x86_64-linux" "i686-linux" "armhf-linux" "mips64el-linux")
+  "List of supported systems.")
+
+(defvar guix-help-source-types
+  '("package" "all" "transitive")
+  "List of supported sources types.")
+
+(defvar guix-help-hash-formats
+  '("nix-base32" "base32" "base16" "hex" "hexadecimal")
+  "List of supported hash formats.")
+
+(defvar guix-help-refresh-subsets
+  '("core" "non-core")
+  "List of supported 'refresh' subsets.")
+
+(defvar guix-help-key-policies
+  '("interactive" "always" "never")
+  "List of supported key download policies.")
+
+(defvar guix-help-verify-options
+  '("repair" "contents")
+  "List of supported 'verify' options")
+
+(defvar guix-help-elpa-archives
+  '("gnu" "melpa" "melpa-stable")
+  "List of supported ELPA archives.")
+
+(provide 'guix-help-vars)
+
+;;; guix-help-vars.el ends here
diff --git a/emacs/guix-info.el b/emacs/guix-info.el
index f17ce01ab6..4bdd62a6a5 100644
--- a/emacs/guix-info.el
+++ b/emacs/guix-info.el
@@ -178,13 +178,13 @@ The order of displayed parameters is the same as in this list.")
 (defun guix-info-get-insert-methods (entry-type param)
   "Return list of insert methods for parameter PARAM of ENTRY-TYPE.
 See `guix-info-insert-methods' for details."
-  (guix-get-key-val guix-info-insert-methods
-                    entry-type param))
+  (guix-assq-value guix-info-insert-methods
+                   entry-type param))
 
 (defun guix-info-get-displayed-params (entry-type)
   "Return parameters of ENTRY-TYPE that should be displayed."
-  (guix-get-key-val guix-info-displayed-params
-                    entry-type))
+  (guix-assq-value guix-info-displayed-params
+                   entry-type))
 
 (defun guix-info-get-indent (&optional level)
   "Return `guix-info-indent' \"multiplied\" by LEVEL spaces.
@@ -232,7 +232,7 @@ Use `guix-info-insert-ENTRY-TYPE-function' or
   "Insert title and value of a PARAM at point.
 ENTRY is alist with parameters and their values.
 ENTRY-TYPE is a type of ENTRY."
-  (let ((val (guix-get-key-val entry param)))
+  (let ((val (guix-assq-value entry param)))
     (unless (and guix-info-ignore-empty-vals (null val))
       (let* ((title          (guix-get-param-title entry-type param))
              (insert-methods (guix-info-get-insert-methods entry-type param))
@@ -492,12 +492,12 @@ filling them to fit the window."
 (defun guix-package-info-insert-heading (entry)
   "Insert the heading for package ENTRY.
 Show package name, version, and `guix-package-info-heading-params'."
-  (guix-format-insert (concat (guix-get-key-val entry 'name) " "
-                              (guix-get-key-val entry 'version))
+  (guix-format-insert (concat (guix-assq-value entry 'name) " "
+                              (guix-assq-value entry 'version))
                       'guix-package-info-heading)
   (insert "\n\n")
   (mapc (lambda (param)
-          (let ((val  (guix-get-key-val entry param))
+          (let ((val  (guix-assq-value entry param))
                 (face (guix-get-symbol (symbol-name param)
                                        'info 'package)))
             (when val
@@ -587,10 +587,10 @@ If nil, insert installed info in a default way.")
 
 (defun guix-package-info-insert-outputs (outputs entry)
   "Insert OUTPUTS from package ENTRY at point."
-  (and (guix-get-key-val entry 'obsolete)
+  (and (guix-assq-value entry 'obsolete)
        (guix-package-info-insert-obsolete-text))
-  (and (guix-get-key-val entry 'non-unique)
-       (guix-get-key-val entry 'installed)
+  (and (guix-assq-value entry 'non-unique)
+       (guix-assq-value entry 'installed)
        (guix-package-info-insert-non-unique-text
         (guix-get-full-name entry)))
   (insert "\n")
@@ -617,11 +617,11 @@ If nil, insert installed info in a default way.")
 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-get-key-val entry 'installed))
-         (obsolete  (guix-get-key-val entry 'obsolete))
+  (let* ((installed (guix-assq-value entry 'installed))
+         (obsolete  (guix-assq-value entry 'obsolete))
          (installed-entry (cl-find-if
                            (lambda (entry)
-                             (string= (guix-get-key-val entry 'output)
+                             (string= (guix-assq-value entry 'output)
                                       output))
                            installed))
          (action-type (if installed-entry 'delete 'install)))
@@ -655,8 +655,8 @@ ENTRY is an alist with package info."
         (current-buffer)))
      (concat type-str " '" full-name "'")
      'action-type type
-     'id (or (guix-get-key-val entry 'package-id)
-             (guix-get-key-val entry 'id))
+     'id (or (guix-assq-value entry 'package-id)
+             (guix-assq-value entry 'id))
      'output output)))
 
 (defun guix-package-info-insert-output-path (path &optional _)
@@ -720,7 +720,7 @@ PACKAGE-ID is an ID of the package which source to show."
            (entries (cl-substitute-if
                      new-entry
                      (lambda (entry)
-                       (equal (guix-get-key-val entry 'id)
+                       (equal (guix-assq-value entry 'id)
                               entry-id))
                      guix-entries
                      :count 1)))
@@ -746,9 +746,9 @@ SOURCE is a list of URLs."
   (guix-info-insert-indent)
   (if (null source)
       (guix-format-insert nil)
-    (let* ((source-file (guix-get-key-val entry 'source-file))
-           (entry-id    (guix-get-key-val entry 'id))
-           (package-id  (or (guix-get-key-val entry 'package-id)
+    (let* ((source-file (guix-assq-value entry 'source-file))
+           (entry-id    (guix-assq-value entry 'id))
+           (package-id  (or (guix-assq-value entry 'package-id)
                             entry-id)))
       (if (null source-file)
           (guix-info-insert-action-button
@@ -798,13 +798,13 @@ If nil, insert output in a default way.")
   "Insert output VERSION and obsolete text if needed at point."
   (guix-info-insert-val-default version
                                 'guix-package-info-version)
-  (and (guix-get-key-val entry 'obsolete)
+  (and (guix-assq-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-get-key-val entry 'installed))
-         (obsolete  (guix-get-key-val entry 'obsolete))
+  (let* ((installed (guix-assq-value entry 'installed))
+         (obsolete  (guix-assq-value entry 'obsolete))
          (action-type (if installed 'delete 'install)))
     (guix-info-insert-val-default
      output
@@ -874,7 +874,7 @@ If nil, insert generation in a default way.")
        (guix-switch-to-generation guix-profile (button-get btn 'number)
                                   (current-buffer)))
      "Switch to this generation (make it the current one)"
-     'number (guix-get-key-val entry 'number))))
+     'number (guix-assq-value entry 'number))))
 
 (provide 'guix-info)
 
diff --git a/emacs/guix-init.el.in b/emacs/guix-init.el
index 728bc375c2..3a727c7eb6 100644
--- a/emacs/guix-init.el.in
+++ b/emacs/guix-init.el
@@ -1,9 +1,5 @@
 (require 'guix-autoloads)
 
-(defvar guix-load-path
-  (replace-regexp-in-string "${prefix}" "@prefix@" "@emacsuidir@")
-  "Directory with scheme files for \"guix.el\" package.")
-
 (defcustom guix-package-enable-at-startup t
   "If non-nil, activate Emacs packages installed in a user profile.
 Set this variable to nil before requiring `guix-init' file to
diff --git a/emacs/guix-list.el b/emacs/guix-list.el
index e84d60a0aa..9796464dbf 100644
--- a/emacs/guix-list.el
+++ b/emacs/guix-list.el
@@ -1,6 +1,6 @@
 ;;; guix-list.el --- List buffers for displaying entries   -*- lexical-binding: t -*-
 
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
 
 ;; This file is part of GNU Guix.
 
@@ -110,13 +110,13 @@ parameters and their values).")
 
 (defun guix-list-get-param-title (entry-type param)
   "Return title of an ENTRY-TYPE entry parameter PARAM."
-  (or (guix-get-key-val guix-list-column-titles
-                        entry-type param)
+  (or (guix-assq-value guix-list-column-titles
+                       entry-type param)
       (guix-get-param-title entry-type param)))
 
 (defun guix-list-get-column-format (entry-type)
   "Return column format for ENTRY-TYPE."
-  (guix-get-key-val guix-list-column-format entry-type))
+  (guix-assq-value guix-list-column-format entry-type))
 
 (defun guix-list-get-displayed-params (entry-type)
   "Return list of parameters of ENTRY-TYPE that should be displayed."
@@ -170,7 +170,7 @@ ENTRIES should have a form of `guix-entries'."
 Values are taken from ENTRIES which should have the form of
 `guix-entries'."
   (mapcar (lambda (entry)
-            (list (guix-get-key-val entry 'id)
+            (list (guix-assq-value entry 'id)
                   (guix-list-get-tabulated-entry entry entry-type)))
           entries))
 
@@ -180,9 +180,9 @@ Parameters are taken from ENTRY of ENTRY-TYPE."
   (guix-list-make-tabulated-vector
    entry-type
    (lambda (param _)
-     (let ((val (guix-get-key-val entry param))
-           (fun (guix-get-key-val guix-list-column-value-methods
-                                  entry-type param)))
+     (let ((val (guix-assq-value entry param))
+           (fun (guix-assq-value guix-list-column-value-methods
+                                 entry-type param)))
        (if fun
            (funcall fun val entry)
          (guix-get-string val))))))
@@ -221,7 +221,7 @@ VAL may be nil."
     (guix-package-list-mode
      (guix-list-current-id))
     (guix-output-list-mode
-     (guix-get-key-val (guix-list-current-entry) 'package-id))))
+     (guix-assq-value (guix-list-current-entry) 'package-id))))
 
 (defun guix-list-for-each-line (fun &rest args)
   "Call FUN with ARGS for each entry line."
@@ -262,7 +262,7 @@ ARGS is a list of additional values.")
 
 (defsubst guix-list-get-mark (name)
   "Return mark character by its NAME."
-  (or (guix-get-key-val guix-list-mark-alist name)
+  (or (guix-assq-value guix-list-mark-alist name)
       (error "Mark '%S' not found" name)))
 
 (defsubst guix-list-get-mark-string (name)
@@ -355,8 +355,8 @@ With ARG, unmark all lines."
   "Put marks according to `guix-list-mark-alist'."
   (guix-list-for-each-line
    (lambda ()
-     (let ((mark-name (car (guix-get-key-val guix-list-marked
-                                             (guix-list-current-id)))))
+     (let ((mark-name (car (guix-assq-value guix-list-marked
+                                            (guix-list-current-id)))))
        (tabulated-list-put-tag
         (guix-list-get-mark-string (or mark-name 'empty)))))))
 
@@ -472,7 +472,7 @@ With prefix (if ARG is non-nil), describe entries marked with any mark."
 (defun guix-list-edit-package ()
   "Go to the location of the current package."
   (interactive)
-  (guix-edit-package (guix-list-current-package-id)))
+  (guix-edit (guix-list-current-package-id)))
 
 
 ;;; Displaying packages
@@ -524,16 +524,16 @@ likely)."
 Colorize it with `guix-package-list-installed' or
 `guix-package-list-obsolete' if needed."
   (guix-get-string name
-                   (cond ((guix-get-key-val entry 'obsolete)
+                   (cond ((guix-assq-value entry 'obsolete)
                           'guix-package-list-obsolete)
-                         ((guix-get-key-val entry 'installed)
+                         ((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-get-key-val entry 'output))
+             (guix-assq-value entry 'output))
            installed)))
 
 (defun guix-package-list-marking-check ()
@@ -562,7 +562,7 @@ be separated with \",\")."
   (interactive "P")
   (guix-package-list-marking-check)
   (let* ((entry     (guix-list-current-entry))
-         (all       (guix-get-key-val entry 'outputs))
+         (all       (guix-assq-value entry 'outputs))
          (installed (guix-get-installed-outputs entry))
          (available (cl-set-difference all installed :test #'string=)))
     (or available
@@ -597,7 +597,7 @@ be separated with \",\")."
          (installed (guix-get-installed-outputs entry)))
     (or installed
         (user-error "This package is not installed"))
-    (when (or (guix-get-key-val entry 'obsolete)
+    (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
@@ -611,14 +611,14 @@ accept an entry as argument."
   (guix-package-list-marking-check)
   (let ((obsolete (cl-remove-if-not
                    (lambda (entry)
-                     (guix-get-key-val entry 'obsolete))
+                     (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-get-key-val entry 'id)))
+                        (equal id (guix-assq-value entry 'id)))
                       obsolete)))
          (when entry
            (funcall fun entry)))))))
@@ -682,7 +682,7 @@ The specification is suitable for `guix-process-package-actions'."
   (interactive)
   (guix-package-list-marking-check)
   (let* ((entry     (guix-list-current-entry))
-         (installed (guix-get-key-val entry 'installed)))
+         (installed (guix-assq-value entry 'installed)))
     (if installed
         (user-error "This output is already installed")
       (guix-list--mark 'install t))))
@@ -692,7 +692,7 @@ The specification is suitable for `guix-process-package-actions'."
   (interactive)
   (guix-package-list-marking-check)
   (let* ((entry     (guix-list-current-entry))
-         (installed (guix-get-key-val entry 'installed)))
+         (installed (guix-assq-value entry 'installed)))
     (if installed
         (guix-list--mark 'delete t)
       (user-error "This output is not installed"))))
@@ -702,10 +702,10 @@ The specification is suitable for `guix-process-package-actions'."
   (interactive)
   (guix-package-list-marking-check)
   (let* ((entry     (guix-list-current-entry))
-         (installed (guix-get-key-val entry 'installed)))
+         (installed (guix-assq-value entry 'installed)))
     (or installed
         (user-error "This output is not installed"))
-    (when (or (guix-get-key-val entry 'obsolete)
+    (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))))
 
@@ -777,8 +777,8 @@ VAL is a boolean value."
   "Switch current profile to the generation at point."
   (interactive)
   (let* ((entry   (guix-list-current-entry))
-         (current (guix-get-key-val entry 'current))
-         (number  (guix-get-key-val entry 'number)))
+         (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)))))
diff --git a/emacs/guix-main.scm b/emacs/guix-main.scm
index e0dc683d88..c9b84d36d9 100644
--- a/emacs/guix-main.scm
+++ b/emacs/guix-main.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -45,6 +45,7 @@
 (use-modules
  (ice-9 vlist)
  (ice-9 match)
+ (ice-9 popen)
  (srfi srfi-1)
  (srfi srfi-2)
  (srfi srfi-11)
@@ -57,6 +58,8 @@
  (guix licenses)
  (guix utils)
  (guix ui)
+ (guix scripts graph)
+ (guix scripts lint)
  (guix scripts package)
  (guix scripts pull)
  (gnu packages))
@@ -68,7 +71,14 @@
 (define (list-maybe obj)
   (if (list? obj) obj (list obj)))
 
-(define full-name->name+version package-name->name+version)
+(define (full-name->name+version spec)
+  "Given package specification SPEC with or without output,
+return two values: name and version.  For example, for SPEC
+\"foo-0.9.1b:lib\", return \"foo\" and \"0.9.1b\"."
+  (let-values (((name version output)
+                (package-specification->name+version+output spec)))
+    (values name version)))
+
 (define (name+version->full-name name version)
   (string-append name "-" version))
 
@@ -244,6 +254,10 @@ Example:
   (filter-map (match-lambda
                ((_ (? package? package))
                 (package-full-name package))
+               ((_ (? package? package) output)
+                (make-package-specification (package-name package)
+                                            (package-version package)
+                                            output))
                (_ #f))
               inputs))
 
@@ -279,7 +293,7 @@ Example:
     (license           . ,package-license-names)
     (source            . ,package-source-names)
     (synopsis          . ,package-synopsis)
-    (description       . ,package-description)
+    (description       . ,package-description-string)
     (home-url          . ,package-home-page)
     (outputs           . ,package-outputs)
     (non-unique        . ,(negate package-unique?))
@@ -887,9 +901,10 @@ GENERATIONS is a list of generation numbers."
   (with-store store
     (delete-generations store profile generations)))
 
-(define (package-location-string package-id)
-  "Return a location string of a package PACKAGE-ID."
-  (and-let* ((package  (package-by-id package-id))
+(define (package-location-string id-or-name)
+  "Return a location string of a package with ID-OR-NAME."
+  (and-let* ((package  (or (package-by-id id-or-name)
+                           (first (packages-by-name id-or-name))))
              (location (package-location package)))
     (location->string location)))
 
@@ -927,3 +942,54 @@ GENERATIONS is a list of generation numbers."
           (build-derivations store derivations))
         (format #t "The source store path: ~a~%"
                 (package-source-derivation->store-path derivation))))))
+
+
+;;; Executing guix commands
+
+(define (guix-command . args)
+  "Run 'guix ARGS ...' command."
+  (catch 'quit
+    (lambda () (apply run-guix args))
+    (const #t)))
+
+(define (guix-command-output . args)
+  "Return string with 'guix ARGS ...' output."
+  (with-output-to-string
+    (lambda () (apply guix-command args))))
+
+(define (help-string . commands)
+  "Return string with 'guix COMMANDS ... --help' output."
+  (apply guix-command-output `(,@commands "--help")))
+
+(define (pipe-guix-output guix-args command-args)
+  "Run 'guix GUIX-ARGS ...' command and pipe its output to a shell command
+defined by COMMAND-ARGS.
+Return #t if the shell command was executed successfully."
+  (let ((pipe (apply open-pipe* OPEN_WRITE command-args)))
+    (with-output-to-port pipe
+      (lambda () (apply guix-command guix-args)))
+    (zero? (status:exit-val (close-pipe pipe)))))
+
+
+;;; Lists of packages, lint checkers, etc.
+
+(define (graph-type-names)
+  "Return a list of names of available graph node types."
+  (map node-type-name %node-types))
+
+(define (lint-checker-names)
+  "Return a list of names of available lint checkers."
+  (map (lambda (checker)
+         (symbol->string (lint-checker-name checker)))
+       %checkers))
+
+(define (package-names)
+  "Return a list of names of available packages."
+  (delete-duplicates
+   (fold-packages (lambda (pkg res)
+                    (cons (package-name pkg) res))
+                  '())))
+
+;; See the comment to 'guix-package-names' function in "guix-popup.el".
+(define (package-names-lists)
+  (map list (package-names)))
diff --git a/emacs/guix-messages.el b/emacs/guix-messages.el
index bd985a0670..2bf99de6fa 100644
--- a/emacs/guix-messages.el
+++ b/emacs/guix-messages.el
@@ -1,6 +1,6 @@
 ;;; guix-messages.el --- Minibuffer messages
 
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
 
 ;; This file is part of GNU Guix.
 
@@ -186,14 +186,14 @@
 (defun guix-result-message (profile entries entry-type
                             search-type search-vals)
   "Display an appropriate message after displaying ENTRIES."
-  (let* ((type-spec (guix-get-key-val guix-messages
-                                      entry-type search-type))
+  (let* ((type-spec (guix-assq-value guix-messages
+                                     entry-type search-type))
          (fun-or-count-spec (car type-spec)))
     (if (functionp fun-or-count-spec)
         (funcall fun-or-count-spec profile entries search-vals)
       (let* ((count     (length entries))
              (count-key (if (> count 1) 'many count))
-             (msg-spec  (guix-get-key-val type-spec count-key))
+             (msg-spec  (guix-assq-value type-spec count-key))
              (msg       (car msg-spec))
              (args      (cdr msg-spec)))
         (mapc (lambda (subst)
diff --git a/emacs/guix-pcomplete.el b/emacs/guix-pcomplete.el
index 9ec563cf52..4743be59bd 100644
--- a/emacs/guix-pcomplete.el
+++ b/emacs/guix-pcomplete.el
@@ -28,59 +28,7 @@
 (require 'pcmpl-unix)
 (require 'cl-lib)
 (require 'guix-utils)
-
-
-;;; Regexps for parsing various "guix ..." outputs
-
-(defvar guix-pcomplete-parse-package-regexp
-  (rx bol (group (one-or-more (not blank))))
-  "Regexp used to find names of the packages.")
-
-(defvar guix-pcomplete-parse-command-regexp
-  (rx bol "   "
-      (group wordchar (one-or-more (or wordchar "-"))))
-  "Regexp used to find guix commands.
-'Command' means any option not prefixed with '-'.  For example,
-guix subcommand, system action, importer, etc.")
-
-(defvar guix-pcomplete-parse-long-option-regexp
-  (rx (or "  " ", ")
-      (group "--" (one-or-more (or wordchar "-"))
-             (zero-or-one "=")))
-  "Regexp used to find long options.")
-
-(defvar guix-pcomplete-parse-short-option-regexp
-  (rx bol (one-or-more blank)
-      "-" (group (not (any "- "))))
-  "Regexp used to find short options.")
-
-(defvar guix-pcomplete-parse-list-regexp
-  (rx bol (zero-or-more blank) "- "
-      (group (one-or-more (or wordchar "-"))))
-  "Regexp used to find various lists (lint checkers, graph types).")
-
-(defvar guix-pcomplete-parse-regexp-group 1
-  "Parenthesized expression of regexps used to find commands and
-options.")
-
-
-;;; Non-receivable completions
-
-(defvar guix-pcomplete-systems
-  '("x86_64-linux" "i686-linux" "armhf-linux" "mips64el-linux")
-  "List of supported systems.")
-
-(defvar guix-pcomplete-hash-formats
-  '("nix-base32" "base32" "base16" "hex" "hexadecimal")
-  "List of supported hash formats.")
-
-(defvar guix-pcomplete-refresh-subsets
-  '("core" "non-core")
-  "List of supported 'refresh' subsets.")
-
-(defvar guix-pcomplete-key-policies
-  '("interactive" "always" "never")
-  "List of supported key download policies.")
+(require 'guix-help-vars)
 
 
 ;;; Interacting with guix
@@ -105,9 +53,8 @@ Return a list of strings matching REGEXP.
 GROUP specifies a parenthesized expression used in REGEXP."
   (with-temp-buffer
     (apply #'guix-pcomplete-run-guix args)
-    (goto-char (point-min))
     (let (result)
-      (while (re-search-forward regexp nil t)
+      (guix-while-search regexp
         (push (match-string-no-properties group) result))
       (nreverse result))))
 
@@ -129,7 +76,7 @@ function call is returned."
      (let* ((args '("--help"))
             (args (if command (cons command args) args))
             (res (apply #'guix-pcomplete-run-guix-and-search
-                        ,regexp guix-pcomplete-parse-regexp-group args)))
+                        ,regexp guix-help-parse-regexp-group args)))
        ,(if filter
             `(funcall ,filter res)
           'res))))
@@ -138,23 +85,23 @@ function call is returned."
   "If COMMAND is nil, return a list of available guix commands.
 If COMMAND is non-nil (it should be a string), return available
 subcommands, actions, etc. for this guix COMMAND."
-  guix-pcomplete-parse-command-regexp)
+  guix-help-parse-command-regexp)
 
 (guix-pcomplete-define-options-finder guix-pcomplete-long-options
   "Return a list of available long options for guix COMMAND."
-  guix-pcomplete-parse-long-option-regexp)
+  guix-help-parse-long-option-regexp)
 
 (guix-pcomplete-define-options-finder guix-pcomplete-short-options
   "Return a string with available short options for guix COMMAND."
-  guix-pcomplete-parse-short-option-regexp
+  guix-help-parse-short-option-regexp
   (lambda (list)
-    (mapconcat #'identity list "")))
+    (guix-concat-strings list "")))
 
 (guix-memoized-defun guix-pcomplete-all-packages ()
   "Return a list of all available Guix packages."
   (guix-pcomplete-run-guix-and-search
-   guix-pcomplete-parse-package-regexp
-   guix-pcomplete-parse-regexp-group
+   guix-help-parse-package-regexp
+   guix-help-parse-regexp-group
    "package" "--list-available"))
 
 (guix-memoized-defun guix-pcomplete-installed-packages (&optional profile)
@@ -163,22 +110,22 @@ subcommands, actions, etc. for this guix COMMAND."
                     (list (concat "--profile=" profile))))
          (args (append '("package" "--list-installed") args)))
     (apply #'guix-pcomplete-run-guix-and-search
-           guix-pcomplete-parse-package-regexp
-           guix-pcomplete-parse-regexp-group
+           guix-help-parse-package-regexp
+           guix-help-parse-regexp-group
            args)))
 
 (guix-memoized-defun guix-pcomplete-lint-checkers ()
   "Return a list of all available lint checkers."
   (guix-pcomplete-run-guix-and-search
-   guix-pcomplete-parse-list-regexp
-   guix-pcomplete-parse-regexp-group
+   guix-help-parse-list-regexp
+   guix-help-parse-regexp-group
    "lint" "--list-checkers"))
 
 (guix-memoized-defun guix-pcomplete-graph-types ()
   "Return a list of all available graph types."
   (guix-pcomplete-run-guix-and-search
-   guix-pcomplete-parse-list-regexp
-   guix-pcomplete-parse-regexp-group
+   guix-help-parse-list-regexp
+   guix-help-parse-regexp-group
    "graph" "--list-types"))
 
 
@@ -284,7 +231,7 @@ INPUT is the current partially completed string."
      ((option? "-L" "--load-path")
       (complete* (pcomplete-dirs)))
      ((string= "--key-download" option)
-      (complete* guix-pcomplete-key-policies))
+      (complete* guix-help-key-policies))
 
      ((command? "package")
       (cond
@@ -313,7 +260,7 @@ INPUT is the current partially completed string."
 
      ((and (command? "archive" "build" "size")
            (option? "-s" "--system"))
-      (complete* guix-pcomplete-systems))
+      (complete* guix-help-system-types))
 
      ((and (command? "build")
            (option? "-r" "--root"))
@@ -329,7 +276,7 @@ INPUT is the current partially completed string."
 
      ((and (command? "hash" "download")
            (option? "-f" "--format"))
-      (complete* guix-pcomplete-hash-formats))
+      (complete* guix-help-hash-formats))
 
      ((and (command? "lint")
            (option? "-c" "--checkers"))
@@ -342,7 +289,7 @@ INPUT is the current partially completed string."
 
      ((and (command? "refresh")
            (option? "-s" "--select"))
-      (complete* guix-pcomplete-refresh-subsets))
+      (complete* guix-help-refresh-subsets))
 
      ((and (command? "size")
            (option? "-m" "--map-file"))
diff --git a/emacs/guix-popup.el b/emacs/guix-popup.el
new file mode 100644
index 0000000000..59e98a352e
--- /dev/null
+++ b/emacs/guix-popup.el
@@ -0,0 +1,48 @@
+;;; guix-popup.el --- Popup windows library
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides `guix-define-popup' macro which is just an alias
+;; to `magit-define-popup'.  According to the manual (info
+;; "(magit-popup) Defining prefix and suffix commands") `magit-popup'
+;; library will eventually be superseded by a more general library.
+
+;;; Code:
+
+(require 'magit-popup)
+
+(defalias 'guix-define-popup 'magit-define-popup)
+
+(defvar guix-popup-font-lock-keywords
+  (eval-when-compile
+    `((,(rx "("
+            (group "guix-define-popup")
+            symbol-end
+            (zero-or-more blank)
+            (zero-or-one
+             (group (one-or-more (or (syntax word) (syntax symbol))))))
+       (1 font-lock-keyword-face)
+       (2 font-lock-function-name-face nil t)))))
+
+(font-lock-add-keywords 'emacs-lisp-mode guix-popup-font-lock-keywords)
+
+(provide 'guix-popup)
+
+;;; guix-popup.el ends here
diff --git a/emacs/guix-prettify.el b/emacs/guix-prettify.el
index b01495c86b..24dfbb33e2 100644
--- a/emacs/guix-prettify.el
+++ b/emacs/guix-prettify.el
@@ -1,6 +1,6 @@
 ;;; guix-prettify.el --- Prettify Guix store file names
 
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
 
 ;; This file is part of GNU Guix.
 
@@ -47,9 +47,12 @@
 
 ;;; Code:
 
+(require 'guix-utils)
+
 (defgroup guix-prettify nil
   "Prettify Guix store file names."
   :prefix "guix-prettify-"
+  :group 'guix
   :group 'font-lock
   :group 'convenience)
 
@@ -136,13 +139,11 @@ enabling/disabling `guix-prettify-mode'.  If nil, do nothing.")
           (remove-text-properties (point-min)
                                   (point-max)
                                   '(composition nil))
-        (save-excursion
-          (goto-char (point-min))
-          (while (re-search-forward guix-prettify-regexp nil t)
-            (remove-text-properties
-             (match-beginning guix-prettify-regexp-group)
-             (match-end       guix-prettify-regexp-group)
-             '(composition nil))))))))
+        (guix-while-search guix-prettify-regexp
+          (remove-text-properties
+           (match-beginning guix-prettify-regexp-group)
+           (match-end       guix-prettify-regexp-group)
+           '(composition nil)))))))
 
 ;;;###autoload
 (define-minor-mode guix-prettify-mode
diff --git a/emacs/guix-profiles.el.in b/emacs/guix-profiles.el
index 1e43707b68..1a41745512 100644
--- a/emacs/guix-profiles.el.in
+++ b/emacs/guix-profiles.el
@@ -19,12 +19,14 @@
 
 ;;; Code:
 
+(require 'guix-config)
+
 (defvar guix-user-profile
   (expand-file-name "~/.guix-profile")
   "User profile.")
 
 (defvar guix-default-profile
-  (concat (or (getenv "NIX_STATE_DIR") "@guix_localstatedir@/guix")
+  (concat guix-state-directory
           "/profiles/per-user/"
           (getenv "USER")
           "/guix-profile")
diff --git a/emacs/guix-read.el b/emacs/guix-read.el
new file mode 100644
index 0000000000..5a7201c3aa
--- /dev/null
+++ b/emacs/guix-read.el
@@ -0,0 +1,176 @@
+;;; guix-read.el --- Minibuffer readers
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides functions to prompt a user for packages, system
+;; types, hash formats and other guix related stuff.
+
+;;; Code:
+
+(require 'guix-help-vars)
+(require 'guix-utils)
+(require 'guix-base)
+
+(defun guix-read-file-name (prompt &optional dir default-filename
+                                   mustmatch initial predicate)
+  "Read file name.
+This function is similar to `read-file-name' except it also
+expands the file name."
+  (expand-file-name (read-file-name prompt dir default-filename
+                                    mustmatch initial predicate)))
+
+(defmacro guix-define-reader (name read-fun completions prompt)
+  "Define NAME function to read from minibuffer.
+READ-FUN may be `completing-read', `completing-read-multiple' or
+another function with the same arguments."
+  `(defun ,name (&optional prompt initial-contents)
+     (,read-fun ,(if prompt
+                     `(or prompt ,prompt)
+                   'prompt)
+                ,completions nil nil initial-contents)))
+
+(defmacro guix-define-readers (&rest args)
+  "Define reader functions.
+
+ARGS should have a form [KEYWORD VALUE] ...  The following
+keywords are available:
+
+  - `completions-var' - variable used to get completions.
+
+  - `completions-getter' - function used to get completions.
+
+  - `single-reader', `single-prompt' - name of a function to read
+    a single value, and a prompt for it.
+
+  - `multiple-reader', `multiple-prompt' - name of a function to
+    read multiple values, and a prompt for it.
+
+  - `multiple-separator' - if specified, another
+    `<multiple-reader-name>-string' function returning a string
+    of multiple values separated the specified separator will be
+    defined."
+  (let (completions-var
+        completions-getter
+        single-reader
+        single-prompt
+        multiple-reader
+        multiple-prompt
+        multiple-separator)
+
+    ;; Process the keyword args.
+    (while (keywordp (car args))
+      (pcase (pop args)
+        (`:completions-var    (setq completions-var    (pop args)))
+        (`:completions-getter (setq completions-getter (pop args)))
+        (`:single-reader      (setq single-reader      (pop args)))
+        (`:single-prompt      (setq single-prompt      (pop args)))
+        (`:multiple-reader    (setq multiple-reader    (pop args)))
+        (`:multiple-prompt    (setq multiple-prompt    (pop args)))
+        (`:multiple-separator (setq multiple-separator (pop args)))
+	(_ (pop args))))
+
+    (let ((completions
+           (cond ((and completions-var completions-getter)
+                  `(or ,completions-var
+                       (setq ,completions-var
+                             (funcall ',completions-getter))))
+                 (completions-var
+                  completions-var)
+                 (completions-getter
+                  `(funcall ',completions-getter)))))
+      `(progn
+         ,(when (and completions-var
+                     (not (boundp completions-var)))
+            `(defvar ,completions-var nil))
+
+         ,(when single-reader
+            `(guix-define-reader ,single-reader completing-read
+                                 ,completions ,single-prompt))
+
+         ,(when multiple-reader
+            `(guix-define-reader ,multiple-reader completing-read-multiple
+                                 ,completions ,multiple-prompt))
+
+         ,(when (and multiple-reader multiple-separator)
+            (let ((name (intern (concat (symbol-name multiple-reader)
+                                        "-string"))))
+              `(defun ,name (&optional prompt initial-contents)
+                 (guix-concat-strings
+                  (,multiple-reader prompt initial-contents)
+                  ,multiple-separator))))))))
+
+(guix-define-readers
+ :completions-var guix-help-system-types
+ :single-reader guix-read-system-type
+ :single-prompt "System type: ")
+
+(guix-define-readers
+ :completions-var guix-help-source-types
+ :single-reader guix-read-source-type
+ :single-prompt "Source type: ")
+
+(guix-define-readers
+ :completions-var guix-help-hash-formats
+ :single-reader guix-read-hash-format
+ :single-prompt "Hash format: ")
+
+(guix-define-readers
+ :completions-var guix-help-refresh-subsets
+ :single-reader guix-read-refresh-subset
+ :single-prompt "Refresh subset: ")
+
+(guix-define-readers
+ :completions-var guix-help-key-policies
+ :single-reader guix-read-key-policy
+ :single-prompt "Key policy: ")
+
+(guix-define-readers
+ :completions-var guix-help-elpa-archives
+ :single-reader guix-read-elpa-archive
+ :single-prompt "ELPA archive: ")
+
+(guix-define-readers
+ :completions-var guix-help-verify-options
+ :multiple-reader guix-read-verify-options
+ :multiple-prompt "Verify option,s: "
+ :multiple-separator ",")
+
+(guix-define-readers
+ :completions-getter guix-graph-type-names
+ :single-reader guix-read-graph-type
+ :single-prompt "Graph node type: ")
+
+(guix-define-readers
+ :completions-getter guix-lint-checker-names
+ :multiple-reader guix-read-lint-checker-names
+ :multiple-prompt "Linter,s: "
+ :multiple-separator ",")
+
+(guix-define-readers
+ :completions-getter guix-package-names
+ :single-reader guix-read-package-name
+ :single-prompt "Package: "
+ :multiple-reader guix-read-package-names
+ :multiple-prompt "Package,s: "
+ :multiple-separator " ")
+
+(provide 'guix-read)
+
+;;; guix-read.el ends here
diff --git a/emacs/guix-utils.el b/emacs/guix-utils.el
index dc0c58a114..c1ce954f8f 100644
--- a/emacs/guix-utils.el
+++ b/emacs/guix-utils.el
@@ -128,6 +128,53 @@ split it into several short lines."
       (fill-region (point-min) (point-max)))
     (buffer-string)))
 
+(defun guix-concat-strings (strings separator &optional location)
+  "Return new string by concatenating STRINGS with SEPARATOR.
+If LOCATION is a symbol `head', add another SEPARATOR to the
+beginning of the returned string; if `tail' - add SEPARATOR to
+the end of the string; if nil, do not add SEPARATOR; otherwise
+add both to the end and to the beginning."
+  (let ((str (mapconcat #'identity strings separator)))
+    (cond ((null location)
+           str)
+          ((eq location 'head)
+           (concat separator str))
+          ((eq location 'tail)
+           (concat str separator))
+          (t
+           (concat separator str separator)))))
+
+(defun guix-shell-quote-argument (argument)
+  "Quote shell command ARGUMENT.
+This function is similar to `shell-quote-argument', but less strict."
+  (if (equal argument "")
+      "''"
+    (replace-regexp-in-string
+     "\n" "'\n'"
+     (replace-regexp-in-string
+      (rx (not (any alnum "-=,./\n"))) "\\\\\\&" argument))))
+
+(defun guix-command-symbol (&optional args)
+  "Return symbol by concatenating 'guix' and ARGS (strings)."
+  (intern (guix-concat-strings (cons "guix" args) "-")))
+
+(defun guix-command-string (&optional args)
+  "Return 'guix ARGS ...' string with quoted shell arguments."
+  (let ((args (mapcar #'guix-shell-quote-argument args)))
+    (guix-concat-strings (cons "guix" args) " ")))
+
+(defun guix-copy-as-kill (string &optional no-message?)
+  "Put STRING into `kill-ring'.
+If NO-MESSAGE? is non-nil, do not display a message about it."
+  (kill-new string)
+  (unless no-message?
+    (message "'%s' has been added to kill ring." string)))
+
+(defun guix-copy-command-as-kill (args &optional no-message?)
+  "Put 'guix ARGS ...' string into `kill-ring'.
+See also `guix-copy-as-kill'."
+  (guix-copy-as-kill (guix-command-string args) no-message?))
+
 (defun guix-completing-read-multiple (prompt table &optional predicate
                                       require-match initial-input
                                       hist def inherit-input-method)
@@ -146,20 +193,56 @@ Return time value."
   (require 'org)
   (org-read-date nil t nil prompt))
 
-(defun guix-get-key-val (alist &rest keys)
-  "Return value from ALIST by KEYS.
-ALIST is alist of alists of alists ... which can be consecutively
-accessed with KEYS."
-  (let ((val alist))
-    (dolist (key keys val)
-      (setq val (cdr (assq key val))))))
+(defcustom guix-find-file-function #'find-file
+  "Function used to find a file.
+The function is called by `guix-find-file' with a file name as a
+single argument."
+  :type '(choice (function-item find-file)
+                 (function-item org-open-file)
+                 (function :tag "Other function"))
+  :group 'guix)
 
 (defun guix-find-file (file)
   "Find FILE if it exists."
   (if (file-exists-p file)
-      (find-file file)
+      (funcall guix-find-file-function file)
     (message "File '%s' does not exist." file)))
 
+(defmacro guix-while-search (regexp &rest body)
+  "Evaluate BODY after each search for REGEXP in the current buffer."
+  (declare (indent 1) (debug t))
+  `(save-excursion
+     (goto-char (point-min))
+     (while (re-search-forward ,regexp nil t)
+       ,@body)))
+
+(defun guix-any (pred lst)
+  "Test whether any element from LST satisfies PRED.
+If so, return the return value from the successful PRED call.
+Return nil otherwise."
+  (when lst
+    (or (funcall pred (car lst))
+        (guix-any pred (cdr lst)))))
+
+
+;;; Alist accessors
+
+(defmacro guix-define-alist-accessor (name assoc-fun)
+  "Define NAME function to access alist values using ASSOC-FUN."
+  `(defun ,name (alist &rest keys)
+     ,(format "Return value from ALIST by KEYS using `%s'.
+ALIST is alist of alists of alists ... which can be consecutively
+accessed with KEYS."
+              assoc-fun)
+     (if (or (null alist) (null keys))
+         alist
+       (apply #',name
+              (cdr (,assoc-fun (car keys) alist))
+              (cdr keys)))))
+
+(guix-define-alist-accessor guix-assq-value assq)
+(guix-define-alist-accessor guix-assoc-value assoc)
+
 
 ;;; Diff
 
@@ -199,6 +282,27 @@ See `defun' for the meaning of arguments."
               (mapconcat #'symbol-name arglist " ")
               docstring)))
 
+(defmacro guix-memoized-defalias (symbol definition &optional docstring)
+  "Set SYMBOL's function definition to memoized version of DEFINITION."
+  (declare (doc-string 3) (indent 1))
+  `(defalias ',symbol
+     (guix-memoize #',definition)
+     ,(or docstring
+          (format "Memoized version of `%S'." definition))))
+
+(defvar guix-memoized-font-lock-keywords
+  (eval-when-compile
+    `((,(rx "("
+            (group "guix-memoized-" (or "defun" "defalias"))
+            symbol-end
+            (zero-or-more blank)
+            (zero-or-one
+             (group (one-or-more (or (syntax word) (syntax symbol))))))
+       (1 font-lock-keyword-face)
+       (2 font-lock-function-name-face nil t)))))
+
+(font-lock-add-keywords 'emacs-lisp-mode guix-memoized-font-lock-keywords)
+
 (provide 'guix-utils)
 
 ;;; guix-utils.el ends here
diff --git a/emacs/guix.el b/emacs/guix.el
index afe7285696..244696a184 100644
--- a/emacs/guix.el
+++ b/emacs/guix.el
@@ -1,6 +1,6 @@
 ;;; guix.el --- Interface for GNU Guix package manager
 
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
 
 ;; Package-Requires: ((geiser "0.3"))
 ;; Keywords: tools
@@ -32,6 +32,7 @@
 (require 'guix-list)
 (require 'guix-info)
 (require 'guix-utils)
+(require 'guix-read)
 
 (defgroup guix nil
   "Interface for Guix package manager."
@@ -193,6 +194,15 @@ Interactively with prefix, prompt for PROFILE."
                              (float-time from)
                              (float-time to)))
 
+;;;###autoload
+(defun guix-edit (id-or-name)
+  "Edit (go to location of) package with ID-OR-NAME."
+  (interactive (list (guix-read-package-name)))
+  (let ((loc (guix-package-location id-or-name)))
+    (if loc
+        (guix-find-location loc)
+      (message "Couldn't find package location."))))
+
 (provide 'guix)
 
 ;;; guix.el ends here