summary refs log tree commit diff
path: root/emacs
diff options
context:
space:
mode:
Diffstat (limited to 'emacs')
-rw-r--r--emacs/guix-backend.el24
-rw-r--r--emacs/guix-base.el62
-rw-r--r--emacs/guix-config.el.in2
-rw-r--r--emacs/guix-license.el103
-rw-r--r--emacs/guix-location.el79
-rw-r--r--emacs/guix-main.scm42
-rw-r--r--emacs/guix-messages.el15
-rw-r--r--emacs/guix-read.el11
-rw-r--r--emacs/guix-ui-license.el150
-rw-r--r--emacs/guix-ui-location.el83
-rw-r--r--emacs/guix-ui-package.el41
-rw-r--r--emacs/guix-ui.el33
-rw-r--r--emacs/guix-utils.el28
-rw-r--r--emacs/local.mk76
14 files changed, 574 insertions, 175 deletions
diff --git a/emacs/guix-backend.el b/emacs/guix-backend.el
index 8afbc9ed48..6341aacae1 100644
--- a/emacs/guix-backend.el
+++ b/emacs/guix-backend.el
@@ -82,7 +82,7 @@ If you have a slow system, try to increase this time."
   :type 'string
   :group 'guix-repl)
 
-(defcustom guix-after-start-repl-hook ()
+(defcustom guix-after-start-repl-hook '(guix-set-directory)
   "Hook called after Guix REPL is started."
   :type 'hook
   :group 'guix-repl)
@@ -337,6 +337,28 @@ additional internal REPL if it exists."
   (geiser-repl--switch-to-buffer (guix-get-repl-buffer internal)))
 
 
+;;; Guix directory
+
+(defvar guix-directory nil
+  "Default directory with Guix source.
+If it is not set by a user, it is set after starting Guile REPL.
+This directory is used to define package locations.")
+
+(defun guix-read-directory ()
+  "Return `guix-directory' or prompt for it.
+This function is intended for using in `interactive' forms."
+  (if current-prefix-arg
+      (read-directory-name "Directory with Guix modules: "
+                           guix-directory)
+    guix-directory))
+
+(defun guix-set-directory ()
+  "Set `guix-directory' if needed."
+  (or guix-directory
+      (setq guix-directory
+            (guix-eval-read "%guix-dir"))))
+
+
 ;;; Evaluating expressions
 
 (defvar guix-operation-buffer nil
diff --git a/emacs/guix-base.el b/emacs/guix-base.el
index 75d19cbfe0..888836428f 100644
--- a/emacs/guix-base.el
+++ b/emacs/guix-base.el
@@ -48,53 +48,7 @@
           (when output (concat ":" output))))
 
 
-;;; Location of packages, profiles and manifests
-
-(defvar guix-directory nil
-  "Default Guix directory.
-If it is not set by a user, it is set after starting Guile REPL.
-This directory is used to define location of the packages.")
-
-(defun guix-read-directory ()
-  "Return `guix-directory' or prompt for it.
-This function is intended for using in `interactive' forms."
-  (if current-prefix-arg
-      (read-directory-name "Directory with Guix modules: "
-                           guix-directory)
-    guix-directory))
-
-(defun guix-set-directory ()
-  "Set `guix-directory' if needed."
-  (or guix-directory
-      (setq guix-directory
-            (guix-eval-read "%guix-dir"))))
-
-(add-hook 'guix-after-start-repl-hook 'guix-set-directory)
-
-(defun guix-find-location (location &optional directory)
-  "Go to LOCATION of a package.
-LOCATION is a string of the form:
-
-  \"PATH:LINE:COLUMN\"
-
-If PATH is relative, it is considered to be relative to
-DIRECTORY (`guix-directory' by default)."
-  (cl-multiple-value-bind (path line col)
-      (split-string location ":")
-    (let ((file (expand-file-name path (or directory guix-directory)))
-          (line (string-to-number line))
-          (col  (string-to-number col)))
-      (find-file file)
-      (goto-char (point-min))
-      (forward-line (- line 1))
-      (move-to-column col)
-      (recenter 1))))
-
-(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)))
+;;; Location of profiles and manifests
 
 (defun guix-generation-file (profile generation)
   "Return the file name of a PROFILE's GENERATION."
@@ -120,20 +74,6 @@ See `guix-packages-profile'."
   (expand-file-name "manifest"
                     (guix-packages-profile profile generation system?)))
 
-;;;###autoload
-(defun guix-edit (id-or-name &optional directory)
-  "Edit (go to location of) package with ID-OR-NAME.
-See `guix-find-location' for the meaning of package location and
-DIRECTORY.
-Interactively, with prefix argument, prompt for DIRECTORY."
-  (interactive
-   (list (guix-read-package-name)
-         (guix-read-directory)))
-  (let ((loc (guix-package-location id-or-name)))
-    (if loc
-        (guix-find-location loc directory)
-      (message "Couldn't find package location."))))
-
 
 ;;; Actions on packages and generations
 
diff --git a/emacs/guix-config.el.in b/emacs/guix-config.el.in
index bd821596c4..d03df9ce63 100644
--- a/emacs/guix-config.el.in
+++ b/emacs/guix-config.el.in
@@ -24,7 +24,7 @@
   (replace-regexp-in-string "${prefix}" "@prefix@" "@emacsuidir@"))
 
 (defconst guix-config-state-directory
-  ;; This must match `NIX_STATE_DIR' as defined in `daemon.am'.
+  ;; This must match `NIX_STATE_DIR' as defined in `nix/local.mk'.
   (or (getenv "NIX_STATE_DIR") "@guix_localstatedir@/guix"))
 
 (defconst guix-config-guile-program "@GUILE@"
diff --git a/emacs/guix-license.el b/emacs/guix-license.el
index a99d7af98d..6003a21aac 100644
--- a/emacs/guix-license.el
+++ b/emacs/guix-license.el
@@ -23,14 +23,15 @@
 
 ;;; Code:
 
-(require 'guix-buffer)
-(require 'guix-list)
-(require 'guix-info)
 (require 'guix-read)
 (require 'guix-backend)
 (require 'guix-guile)
 
-(guix-define-entry-type license)
+(defun guix-license-file (&optional directory)
+  "Return name of the file with license definitions.
+DIRECTORY is a directory with Guix source (`guix-directory' by default)."
+  (expand-file-name "guix/licenses.scm"
+                    (or directory guix-directory)))
 
 (defun guix-lookup-license-url (license)
   "Return URL of a LICENSE."
@@ -38,80 +39,20 @@
                        'lookup-license-uri license))
       (error "Hm, I don't know URL of '%s' license" license)))
 
-(defun guix-license-get-entries (search-type &rest args)
-  "Receive 'license' entries.
-SEARCH-TYPE may be one of the following symbols: `all', `id', `name'."
-  (guix-eval-read
-   (apply #'guix-make-guile-expression
-          'license-entries search-type args)))
-
-(defun guix-license-get-display (search-type &rest args)
-  "Search for licenses and show results."
-  (apply #'guix-list-get-display-entries
-         'license search-type args))
-
-
-;;; License 'info'
-
-(guix-info-define-interface license
-  :buffer-name "*Guix License Info*"
-  :get-entries-function 'guix-license-get-entries
-  :format '((name ignore (simple guix-info-heading))
-            ignore
-            guix-license-insert-packages-button
-            (url ignore (simple guix-url))
-            guix-license-insert-comment)
-  :titles '((url . "URL")))
-
-(declare-function guix-packages-by-license "guix-ui-package")
-
-(defun guix-license-insert-packages-button (entry)
-  "Insert button to display packages by license ENTRY."
-  (guix-info-insert-action-button
-   "Packages"
-   (lambda (btn)
-     (guix-packages-by-license (button-get btn 'license)))
-   "Show packages with this license"
-   'license (guix-entry-value entry 'name)))
-
-(defun guix-license-insert-comment (entry)
-  "Insert 'comment' of a license ENTRY."
-  (let ((comment (guix-entry-value entry 'comment)))
-    (if (and comment
-             (string-match-p "^http" comment))
-        (guix-info-insert-value-simple comment 'guix-url)
-      (guix-info-insert-title-simple
-       (guix-info-param-title 'license 'comment))
-      (guix-info-insert-value-indent comment))))
-
-
-;;; License 'list'
-
-(guix-list-define-interface license
-  :buffer-name "*Guix Licenses*"
-  :get-entries-function 'guix-license-get-entries
-  :describe-function 'guix-license-list-describe
-  :format '((name nil 40 t)
-            (url guix-list-get-url 50 t))
-  :titles '((name . "License"))
-  :sort-key '(name))
-
-(let ((map guix-license-list-mode-map))
-  (define-key map (kbd "RET") 'guix-license-list-show-packages))
-
-(defun guix-license-list-describe (ids)
-  "Describe licenses with IDS (list of identifiers)."
-  (guix-buffer-display-entries
-   (guix-entries-by-ids ids (guix-buffer-current-entries))
-   'info 'license (cl-list* 'id ids) 'add))
-
-(defun guix-license-list-show-packages ()
-  "Display packages with the license at point."
-  (interactive)
-  (guix-packages-by-license (guix-list-current-id)))
-
-
-;;; Interactive commands
+;;;###autoload
+(defun guix-find-license-definition (license &optional directory)
+  "Open licenses file from DIRECTORY and move to the LICENSE definition.
+See `guix-license-file' for the meaning of DIRECTORY.
+Interactively, with prefix argument, prompt for DIRECTORY."
+  (interactive
+   (list (guix-read-license-name)
+         (guix-read-directory)))
+  (find-file (guix-license-file directory))
+  (goto-char (point-min))
+  (when (re-search-forward (concat "\"" (regexp-quote license) "\"")
+                           nil t)
+    (beginning-of-defun)
+    (recenter 1)))
 
 ;;;###autoload
 (defun guix-browse-license-url (license)
@@ -119,12 +60,6 @@ SEARCH-TYPE may be one of the following symbols: `all', `id', `name'."
   (interactive (list (guix-read-license-name)))
   (browse-url (guix-lookup-license-url license)))
 
-;;;###autoload
-(defun guix-licenses ()
-  "Display licenses of the Guix packages."
-  (interactive)
-  (guix-license-get-display 'all))
-
 (provide 'guix-license)
 
 ;;; guix-license.el ends here
diff --git a/emacs/guix-location.el b/emacs/guix-location.el
new file mode 100644
index 0000000000..81396b4017
--- /dev/null
+++ b/emacs/guix-location.el
@@ -0,0 +1,79 @@
+;;; guix-location.el --- Package locations
+
+;; Copyright © 2016 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 Location as published by
+;; the Free Software Foundation, either version 3 of the Location, 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 Location for more details.
+
+;; You should have received a copy of the GNU General Public Location
+;; along with this program.  If not, see <http://www.gnu.org/locations/>.
+
+;;; Commentary:
+
+;; This file provides the code to work with locations of Guix packages.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'guix-backend)
+(require 'guix-read)
+(require 'guix-guile)
+
+(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)))
+
+;;;###autoload
+(defun guix-find-location (location &optional directory)
+  "Go to LOCATION of a package.
+LOCATION is a string of the form:
+
+  \"FILE:LINE:COLUMN\"
+
+If FILE is relative, it is considered to be relative to
+DIRECTORY (`guix-directory' by default).
+
+Interactively, prompt for LOCATION.  With prefix argument, prompt
+for DIRECTORY as well."
+  (interactive
+   (list (guix-read-package-location)
+         (guix-read-directory)))
+  (cl-multiple-value-bind (file line column)
+      (split-string location ":")
+    (find-file (expand-file-name file (or directory guix-directory)))
+    (when (and line column)
+      (let ((line   (string-to-number line))
+            (column (string-to-number column)))
+        (goto-char (point-min))
+        (forward-line (- line 1))
+        (move-to-column column)
+        (recenter 1)))))
+
+;;;###autoload
+(defun guix-edit (id-or-name &optional directory)
+  "Edit (go to location of) package with ID-OR-NAME.
+See `guix-find-location' for the meaning of package location and
+DIRECTORY.
+Interactively, with prefix argument, prompt for DIRECTORY."
+  (interactive
+   (list (guix-read-package-name)
+         (guix-read-directory)))
+  (let ((loc (guix-package-location id-or-name)))
+    (if loc
+        (guix-find-location loc directory)
+      (message "Couldn't find package location."))))
+
+(provide 'guix-location)
+
+;;; guix-location.el ends here
diff --git a/emacs/guix-main.scm b/emacs/guix-main.scm
index c62044056f..5358f3bfa4 100644
--- a/emacs/guix-main.scm
+++ b/emacs/guix-main.scm
@@ -684,6 +684,8 @@ ENTRIES is a list of installed manifest entries."
          (license-proc          (lambda (_ license-name)
                                   (packages-by-license
                                    (lookup-license license-name))))
+         (location-proc         (lambda (_ location)
+                                  (packages-by-location-file location)))
          (all-proc              (lambda _ (all-available-packages)))
          (newest-proc           (lambda _ (newest-available-packages))))
     `((package
@@ -693,6 +695,7 @@ ENTRIES is a list of installed manifest entries."
        (obsolete         . ,(apply-to-first obsolete-package-patterns))
        (regexp           . ,regexp-proc)
        (license          . ,license-proc)
+       (location         . ,location-proc)
        (all-available    . ,all-proc)
        (newest-available . ,newest-proc))
       (output
@@ -702,6 +705,7 @@ ENTRIES is a list of installed manifest entries."
        (obsolete         . ,(apply-to-first obsolete-output-patterns))
        (regexp           . ,regexp-proc)
        (license          . ,license-proc)
+       (location         . ,location-proc)
        (all-available    . ,all-proc)
        (newest-available . ,newest-proc)))))
 
@@ -1097,3 +1101,41 @@ Return #t if the shell command was executed successfully."
 (define (license-entries search-type . search-values)
   (map license->sexp
        (apply find-licenses search-type search-values)))
+
+
+;;; Package locations
+
+(define-values (packages-by-location-file
+                package-location-files)
+  (let* ((table (delay (fold-packages
+                        (lambda (package table)
+                          (let ((file (location-file
+                                       (package-location package))))
+                            (vhash-cons file package table)))
+                        vlist-null)))
+         (files (delay (vhash-fold
+                        (lambda (file _ result)
+                          (if (member file result)
+                              result
+                              (cons file result)))
+                        '()
+                        (force table)))))
+    (values
+     (lambda (file)
+       "Return the (possibly empty) list of packages defined in location FILE."
+       (vhash-fold* cons '() file (force table)))
+     (lambda ()
+       "Return the list of file names of all package locations."
+       (force files)))))
+
+(define %package-location-param-alist
+  `((id       . ,identity)
+    (location . ,identity)
+    (number-of-packages . ,(lambda (location)
+                             (length (packages-by-location-file location))))))
+
+(define package-location->sexp
+  (object-transformer %package-location-param-alist))
+
+(define (package-location-entries)
+  (map package-location->sexp (package-location-files)))
diff --git a/emacs/guix-messages.el b/emacs/guix-messages.el
index de0331fff8..7ebe7e8b5c 100644
--- a/emacs/guix-messages.el
+++ b/emacs/guix-messages.el
@@ -40,6 +40,10 @@
       ,(lambda (_ entries licenses)
          (apply #'guix-message-packages-by-license
                 entries 'package licenses)))
+     (location
+      ,(lambda (_ entries locations)
+         (apply #'guix-message-packages-by-location
+                entries 'package locations)))
      (regexp
       (0 "No packages matching '%s'." val)
       (1 "A single package matching '%s'." val)
@@ -72,6 +76,10 @@
       ,(lambda (_ entries licenses)
          (apply #'guix-message-packages-by-license
                 entries 'output licenses)))
+     (location
+      ,(lambda (_ entries locations)
+         (apply #'guix-message-packages-by-location
+                entries 'output locations)))
      (regexp
       (0 "No package outputs matching '%s'." val)
       (1 "A single package output matching '%s'." val)
@@ -174,6 +182,13 @@ Try \"M-x guix-search-by-name\"."
          (str-end (format "with license '%s'" license)))
     (message "%s %s." str-beg str-end)))
 
+(defun guix-message-packages-by-location (entries entry-type location)
+  "Display a message for packages or outputs searched by LOCATION."
+  (let* ((count   (length entries))
+         (str-beg (guix-message-string-entries count entry-type))
+         (str-end (format "placed in '%s'" location)))
+    (message "%s %s." str-beg str-end)))
+
 (defun guix-message-generations-by-time (profile entries times)
   "Display a message for generations searched by TIMES."
   (let* ((count (length entries))
diff --git a/emacs/guix-read.el b/emacs/guix-read.el
index a1a6b86364..5423c9bcfa 100644
--- a/emacs/guix-read.el
+++ b/emacs/guix-read.el
@@ -62,6 +62,12 @@
   "Return a list of names of available licenses."
   (guix-eval-read (guix-make-guile-expression 'license-names)))
 
+(guix-memoized-defun guix-package-locations ()
+  "Return a list of available package locations."
+  (sort (guix-eval-read (guix-make-guile-expression
+                         'package-location-files))
+        #'string<))
+
 
 ;;; Readers
 
@@ -131,6 +137,11 @@
  :single-reader guix-read-license-name
  :single-prompt "License: ")
 
+(guix-define-readers
+ :completions-getter guix-package-locations
+ :single-reader guix-read-package-location
+ :single-prompt "Location: ")
+
 (provide 'guix-read)
 
 ;;; guix-read.el ends here
diff --git a/emacs/guix-ui-license.el b/emacs/guix-ui-license.el
new file mode 100644
index 0000000000..cf1b5cd357
--- /dev/null
+++ b/emacs/guix-ui-license.el
@@ -0,0 +1,150 @@
+;;; guix-ui-license.el --- Interface for displaying licenses
+
+;; Copyright © 2016 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 'list'/'info' interface for displaying licenses of
+;; Guix packages.
+
+;;; Code:
+
+(require 'guix-buffer)
+(require 'guix-list)
+(require 'guix-info)
+(require 'guix-backend)
+(require 'guix-guile)
+(require 'guix-license)
+
+(guix-define-entry-type license)
+
+(defun guix-license-get-entries (search-type &rest args)
+  "Receive 'license' entries.
+SEARCH-TYPE may be one of the following symbols: `all', `id', `name'."
+  (guix-eval-read
+   (apply #'guix-make-guile-expression
+          'license-entries search-type args)))
+
+(defun guix-license-get-display (search-type &rest args)
+  "Search for licenses and show results."
+  (apply #'guix-list-get-display-entries
+         'license search-type args))
+
+(defun guix-license-message (entries search-type &rest args)
+  "Display a message after showing license ENTRIES."
+  ;; Some objects in (guix licenses) module are procedures (e.g.,
+  ;; 'non-copyleft' or 'x11-style').  Such licenses cannot be "described".
+  (when (null entries)
+    (if (cdr args)
+        (message "Unknown licenses.")
+      (message "Unknown license."))))
+
+
+;;; License 'info'
+
+(guix-info-define-interface license
+  :buffer-name "*Guix License Info*"
+  :get-entries-function 'guix-license-get-entries
+  :message-function 'guix-license-message
+  :format '((name ignore (simple guix-info-heading))
+            ignore
+            guix-license-insert-packages-button
+            (url ignore (simple guix-url))
+            guix-license-insert-comment
+            ignore
+            guix-license-insert-file)
+  :titles '((url . "URL")))
+
+(declare-function guix-packages-by-license "guix-ui-package")
+
+(defun guix-license-insert-packages-button (entry)
+  "Insert button to display packages by license ENTRY."
+  (let ((license (guix-entry-value entry 'name)))
+    (guix-info-insert-action-button
+     "Packages"
+     (lambda (btn)
+       (guix-packages-by-license (button-get btn 'license)))
+     (format "Display packages with license '%s'" license)
+     'license license)))
+
+(defun guix-license-insert-comment (entry)
+  "Insert 'comment' of a license ENTRY."
+  (let ((comment (guix-entry-value entry 'comment)))
+    (if (and comment
+             (string-match-p "^http" comment))
+        (guix-info-insert-value-simple comment 'guix-url)
+      (guix-info-insert-title-simple
+       (guix-info-param-title 'license 'comment))
+      (guix-info-insert-value-indent comment))))
+
+(defun guix-license-insert-file (entry)
+  "Insert button to open license definition."
+  (let ((license (guix-entry-value entry 'name)))
+    (guix-insert-button
+     (guix-license-file) 'guix-file
+     'help-echo (format "Open definition of license '%s'" license)
+     'action (lambda (btn)
+               (guix-find-license-definition (button-get btn 'license)))
+     'license license)))
+
+
+;;; License 'list'
+
+(guix-list-define-interface license
+  :buffer-name "*Guix Licenses*"
+  :get-entries-function 'guix-license-get-entries
+  :describe-function 'guix-license-list-describe
+  :message-function 'guix-license-message
+  :format '((name nil 40 t)
+            (url guix-list-get-url 50 t))
+  :titles '((name . "License"))
+  :sort-key '(name))
+
+(let ((map guix-license-list-mode-map))
+  (define-key map (kbd "e")   'guix-license-list-edit)
+  (define-key map (kbd "RET") 'guix-license-list-show-packages))
+
+(defun guix-license-list-describe (ids)
+  "Describe licenses with IDS (list of identifiers)."
+  (guix-buffer-display-entries
+   (guix-entries-by-ids ids (guix-buffer-current-entries))
+   'info 'license (cl-list* 'id ids) 'add))
+
+(defun guix-license-list-show-packages ()
+  "Display packages with the license at point."
+  (interactive)
+  (guix-packages-by-license (guix-list-current-id)))
+
+(defun guix-license-list-edit (&optional directory)
+  "Go to the location of the current license definition.
+See `guix-license-file' for the meaning of DIRECTORY."
+  (interactive (list (guix-read-directory)))
+  (guix-find-license-definition (guix-list-current-id) directory))
+
+
+;;; Interactive commands
+
+;;;###autoload
+(defun guix-licenses ()
+  "Display licenses of the Guix packages."
+  (interactive)
+  (guix-license-get-display 'all))
+
+(provide 'guix-ui-license)
+
+;;; guix-ui-license.el ends here
diff --git a/emacs/guix-ui-location.el b/emacs/guix-ui-location.el
new file mode 100644
index 0000000000..0027c1fba8
--- /dev/null
+++ b/emacs/guix-ui-location.el
@@ -0,0 +1,83 @@
+;;; guix-ui-location.el --- Interface for displaying package locations
+
+;; Copyright © 2016 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 Location as published by
+;; the Free Software Foundation, either version 3 of the Location, 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 Location for more details.
+
+;; You should have received a copy of the GNU General Public Location
+;; along with this program.  If not, see <http://www.gnu.org/locations/>.
+
+;;; Commentary:
+
+;; This file provides a 'list' interface for displaying locations of Guix
+;; packages.
+
+;;; Code:
+
+(require 'guix-buffer)
+(require 'guix-list)
+(require 'guix-location)
+(require 'guix-backend)
+
+(guix-define-entry-type location)
+
+(defun guix-location-get-entries ()
+  "Receive 'package location' entries."
+  (guix-eval-read "(package-location-entries)"))
+
+
+;;; Location 'list'
+
+(guix-list-define-interface location
+  :buffer-name "*Guix Package Locations*"
+  :get-entries-function 'guix-location-get-entries
+  :format '((location guix-location-list-file-name-specification 50 t)
+            (number-of-packages nil 10 guix-list-sort-numerically-1
+                                :right-align t))
+  :sort-key '(location))
+
+(let ((map guix-location-list-mode-map))
+  (define-key map (kbd "RET") 'guix-location-list-show-packages)
+  ;; "Location Info" buffer is not defined (it would be useless), so
+  ;; unbind "i" key (by default, it is used to display Info buffer).
+  (define-key map (kbd "i") nil))
+
+(defun guix-location-list-file-name-specification (location &optional _)
+  "Return LOCATION button specification for `tabulated-list-entries'."
+  (list location
+        'face 'guix-list-file-name
+        'action (lambda (btn)
+                  (guix-find-location (button-get btn 'location)))
+        'follow-link t
+        'help-echo (concat "Find location: " location)
+        'location location))
+
+(declare-function guix-packages-by-location "guix-ui-package")
+
+(defun guix-location-list-show-packages ()
+  "Display packages placed in the location at point."
+  (interactive)
+  (guix-packages-by-location (guix-list-current-id)))
+
+
+;;; Interactive commands
+
+;;;###autoload
+(defun guix-locations ()
+  "Display locations of the Guix packages."
+  (interactive)
+  (guix-list-get-display-entries 'location))
+
+(provide 'guix-ui-location)
+
+;;; guix-ui-location.el ends here
diff --git a/emacs/guix-ui-package.el b/emacs/guix-ui-package.el
index df5f8d12d1..38f0c08fc7 100644
--- a/emacs/guix-ui-package.el
+++ b/emacs/guix-ui-package.el
@@ -1,6 +1,6 @@
 ;;; guix-ui-package.el --- Interface for displaying packages  -*- lexical-binding: t -*-
 
-;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015, 2016 Alex Kost <alezost@gmail.com>
 
 ;; This file is part of GNU Guix.
 
@@ -38,6 +38,7 @@
 (require 'guix-hydra-build)
 (require 'guix-read)
 (require 'guix-license)
+(require 'guix-location)
 (require 'guix-profiles)
 
 (guix-ui-define-entry-type package)
@@ -222,7 +223,7 @@ ENTRIES is a list of package entries to get info about packages."
             ignore
             (outputs simple guix-package-info-insert-outputs)
             (source simple guix-package-info-insert-source)
-            (location format (format guix-package-location))
+            (location simple guix-package-info-insert-location)
             (home-url format (format guix-url))
             (license format (format guix-package-license))
             (systems format guix-package-info-insert-systems)
@@ -345,9 +346,13 @@ formatted with this string, an action button is inserted.")
 (define-button-type 'guix-package-license
   :supertype 'guix
   'face 'guix-package-info-license
-  'help-echo "Browse license URL"
+  'help-echo "Display license info"
   'action (lambda (btn)
-            (guix-browse-license-url (button-label btn))))
+            (require 'guix-ui-license)
+            (guix-buffer-get-display-entries
+             'info 'license
+             (list 'name (button-label btn))
+             'add)))
 
 (define-button-type 'guix-package-name
   :supertype 'guix
@@ -382,6 +387,22 @@ formatted with this string, an action button is inserted.")
    'guix-package-heading
    'spec (guix-package-entry->name-specification entry)))
 
+(defun guix-package-info-insert-location (location &optional _)
+  "Insert package LOCATION at point."
+  (if (null location)
+      (guix-format-insert nil)
+    (let ((location-file (car (split-string location ":"))))
+      (guix-info-insert-value-indent location 'guix-package-location)
+      (guix-info-insert-indent)
+      (guix-info-insert-action-button
+       "Packages"
+       (lambda (btn)
+         (guix-package-get-display (guix-ui-current-profile)
+                                   'location
+                                   (button-get btn 'location)))
+       (format "Display packages from location '%s'" location-file)
+       'location location-file))))
+
 (defun guix-package-info-insert-systems (systems entry)
   "Insert supported package SYSTEMS at point."
   (guix-info-insert-value-format
@@ -797,7 +818,7 @@ for all ARGS."
             (source simple guix-package-info-insert-source)
             (path simple (indent guix-file))
             (dependencies simple (indent guix-file))
-            (location format (format guix-package-location))
+            (location simple guix-package-info-insert-location)
             (home-url format (format guix-url))
             (license format (format guix-package-license))
             (systems format guix-package-info-insert-systems)
@@ -970,6 +991,16 @@ Interactively with prefix, prompt for PROFILE."
   (guix-package-get-display profile 'license license))
 
 ;;;###autoload
+(defun guix-packages-by-location (location &optional profile)
+  "Display Guix packages placed in LOCATION file.
+If PROFILE is nil, use `guix-current-profile'.
+Interactively with prefix, prompt for PROFILE."
+  (interactive
+   (list (guix-read-package-location)
+         (guix-ui-read-profile)))
+  (guix-package-get-display profile 'location location))
+
+;;;###autoload
 (defun guix-search-by-regexp (regexp &optional params profile)
   "Search for Guix packages by REGEXP.
 PARAMS are package parameters that should be searched.
diff --git a/emacs/guix-ui.el b/emacs/guix-ui.el
index 9a88efc286..1b696314cd 100644
--- a/emacs/guix-ui.el
+++ b/emacs/guix-ui.el
@@ -1,6 +1,6 @@
 ;;; guix-ui.el --- Common code for Guix package management interface  -*- lexical-binding: t -*-
 
-;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015, 2016 Alex Kost <alezost@gmail.com>
 
 ;; This file is part of GNU Guix.
 
@@ -105,10 +105,11 @@ If `all', update all Guix buffers (not recommended)."
   :group 'guix-ui)
 
 (defcustom guix-ui-buffer-name-function
-  #'guix-ui-buffer-name-default
+  #'guix-ui-buffer-name-full
   "Function used to define a name of a Guix buffer.
 The function is called with 2 arguments: BASE-NAME and PROFILE."
-  :type '(choice (function-item guix-ui-buffer-name-default)
+  :type '(choice (function-item guix-ui-buffer-name-full)
+                 (function-item guix-ui-buffer-name-short)
                  (function-item guix-ui-buffer-name-simple)
                  (function :tag "Other function"))
   :group 'guix-ui)
@@ -117,26 +118,14 @@ The function is called with 2 arguments: BASE-NAME and PROFILE."
   "Return BASE-NAME."
   base-name)
 
-;; TODO separate '*...*' logic from the real profile appending.  Also add
-;; another function to return '*Guix ...: /full/path/to/profile*' name.
-(defun guix-ui-buffer-name-default (base-name profile)
+(defun guix-ui-buffer-name-short (base-name profile)
   "Return buffer name by appending BASE-NAME and PROFILE's base file name."
-  (let ((profile-name (file-name-base (directory-file-name profile)))
-        (re (rx string-start
-                (group (? "*"))
-                (group (*? any))
-                (group (? "*"))
-                string-end)))
-    (or (string-match re base-name)
-        (error "Unexpected error in defining guix buffer name"))
-    (let ((first*    (match-string 1 base-name))
-          (name-body (match-string 2 base-name))
-          (last*     (match-string 3 base-name)))
-      ;; Handle the case when buffer name is wrapped by '*'.
-      (if (and (string= "*" first*)
-               (string= "*" last*))
-          (concat "*" name-body ": " profile-name "*")
-        (concat base-name ": " profile-name)))))
+  (guix-compose-buffer-name base-name
+                            (file-name-base (directory-file-name profile))))
+
+(defun guix-ui-buffer-name-full (base-name profile)
+  "Return buffer name by appending BASE-NAME and PROFILE's full name."
+  (guix-compose-buffer-name base-name profile))
 
 (defun guix-ui-buffer-name (base-name profile)
   "Return Guix buffer name based on BASE-NAME and profile.
diff --git a/emacs/guix-utils.el b/emacs/guix-utils.el
index 8c1a5b42de..ea9933f5c3 100644
--- a/emacs/guix-utils.el
+++ b/emacs/guix-utils.el
@@ -1,6 +1,6 @@
 ;;; guix-utils.el --- General utility functions  -*- lexical-binding: t -*-
 
-;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015, 2016 Alex Kost <alezost@gmail.com>
 
 ;; This file is part of GNU Guix.
 
@@ -223,6 +223,32 @@ If NO-MESSAGE? is non-nil, do not display a message about it."
 See also `guix-copy-as-kill'."
   (guix-copy-as-kill (guix-command-string args) no-message?))
 
+(defun guix-compose-buffer-name (base-name postfix)
+  "Return buffer name by appending BASE-NAME and POSTFIX.
+
+In a simple case the result is:
+
+  BASE-NAME: POSTFIX
+
+If BASE-NAME is wrapped by '*', then the result is:
+
+  *BASE-NAME: POSTFIX*"
+  (let ((re (rx string-start
+                (group (? "*"))
+                (group (*? any))
+                (group (? "*"))
+                string-end)))
+    (or (string-match re base-name)
+        (error "Unexpected error in defining buffer name"))
+    (let ((first*    (match-string 1 base-name))
+          (name-body (match-string 2 base-name))
+          (last*     (match-string 3 base-name)))
+      ;; Handle the case when buffer name is wrapped by '*'.
+      (if (and (string= "*" first*)
+               (string= "*" last*))
+          (concat "*" name-body ": " postfix "*")
+        (concat base-name ": " postfix)))))
+
 (defun guix-completing-read (prompt table &optional predicate
                              require-match initial-input
                              hist def inherit-input-method)
diff --git a/emacs/local.mk b/emacs/local.mk
new file mode 100644
index 0000000000..62e33e4fd2
--- /dev/null
+++ b/emacs/local.mk
@@ -0,0 +1,76 @@
+# GNU Guix --- Functional package management for GNU
+# Copyright © 2014, 2015, 2016 Alex Kost <alezost@gmail.com>
+# Copyright © 2016 Mathieu Lirzin <mthl@gnu.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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+AUTOLOADS = emacs/guix-autoloads.el
+
+ELFILES =					\
+  emacs/guix-backend.el				\
+  emacs/guix-base.el				\
+  emacs/guix-build-log.el			\
+  emacs/guix-buffer.el				\
+  emacs/guix-command.el				\
+  emacs/guix-devel.el				\
+  emacs/guix-emacs.el				\
+  emacs/guix-entry.el				\
+  emacs/guix-external.el			\
+  emacs/guix-geiser.el				\
+  emacs/guix-guile.el				\
+  emacs/guix-help-vars.el			\
+  emacs/guix-history.el				\
+  emacs/guix-hydra.el				\
+  emacs/guix-hydra-build.el			\
+  emacs/guix-hydra-jobset.el			\
+  emacs/guix-info.el				\
+  emacs/guix-init.el				\
+  emacs/guix-license.el				\
+  emacs/guix-list.el				\
+  emacs/guix-location.el			\
+  emacs/guix-messages.el			\
+  emacs/guix-pcomplete.el			\
+  emacs/guix-popup.el				\
+  emacs/guix-prettify.el			\
+  emacs/guix-profiles.el			\
+  emacs/guix-read.el				\
+  emacs/guix-ui.el				\
+  emacs/guix-ui-license.el			\
+  emacs/guix-ui-location.el			\
+  emacs/guix-ui-package.el			\
+  emacs/guix-ui-generation.el			\
+  emacs/guix-ui-system-generation.el		\
+  emacs/guix-utils.el
+
+if HAVE_EMACS
+
+dist_lisp_DATA = $(ELFILES)
+
+nodist_lisp_DATA = 		\
+  emacs/guix-config.el		\
+  $(AUTOLOADS)
+
+$(AUTOLOADS): $(ELFILES)
+	$(AM_V_EMACS)$(EMACS) --batch --eval				\
+	  "(let ((backup-inhibited t)					\
+	         (generated-autoload-file				\
+	          (expand-file-name \"$(AUTOLOADS)\" \"$(builddir)\")))	\
+	     (update-directory-autoloads				\
+	      (expand-file-name \"emacs\" \"$(srcdir)\")))"
+
+CLEANFILES += $(AUTOLOADS)
+
+endif HAVE_EMACS