* emacs/guix-base.el (guix-directory, guix-read-directory) (guix-set-directory): Move to "guix-backend.el". (guix-find-location, guix-package-location, guix-edit): Move to... * emacs/guix-location.el: ... here. New file. * emacs/guix-ui-package.el: Use it. * emacs.am (ELFILES): Add it.
		
			
				
	
	
		
			393 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
;;; guix-backend.el --- Making and using Guix REPL
 | 
						||
 | 
						||
;; Copyright © 2014, 2015, 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 the code for interacting with Guile using Guix REPL
 | 
						||
;; (Geiser REPL with some guix-specific additions).
 | 
						||
 | 
						||
;; By default (if `guix-use-guile-server' is non-nil) 2 Guix REPLs are
 | 
						||
;; started.  The main one (with "guile --listen" process) is used for
 | 
						||
;; "interacting" with a user - for showing a progress of
 | 
						||
;; installing/deleting Guix packages.  The second (internal) REPL is
 | 
						||
;; used for synchronous evaluating, e.g. when information about
 | 
						||
;; packages/generations should be received for a list/info buffer.
 | 
						||
;;
 | 
						||
;; This "2 REPLs concept" makes it possible to have a running process of
 | 
						||
;; installing/deleting packages and to continue to search/list/get info
 | 
						||
;; about other packages at the same time.  If you prefer to use a single
 | 
						||
;; Guix REPL, do not try to receive any information while there is a
 | 
						||
;; running code in the REPL (see
 | 
						||
;; <https://github.com/jaor/geiser/issues/28>).
 | 
						||
;;
 | 
						||
;; Guix REPLs (unlike the usual Geiser REPLs) are not added to
 | 
						||
;; `geiser-repl--repls' variable, and thus cannot be used for evaluating
 | 
						||
;; while editing scm-files.  The only purpose of Guix REPLs is to be an
 | 
						||
;; intermediate between "Guix/Guile level" and "Emacs interface level".
 | 
						||
;; That being said you can still want to use a Guix REPL while hacking
 | 
						||
;; auxiliary scheme-files for "guix.el".  You can just use
 | 
						||
;; `geiser-connect-local' command with `guix-repl-current-socket' to
 | 
						||
;; have a usual Geiser REPL with all stuff defined by "guix.el" package.
 | 
						||
 | 
						||
;;; Code:
 | 
						||
 | 
						||
(require 'geiser-mode)
 | 
						||
(require 'geiser-guile)
 | 
						||
(require 'guix-geiser)
 | 
						||
(require 'guix-config)
 | 
						||
(require 'guix-external)
 | 
						||
(require 'guix-emacs)
 | 
						||
(require 'guix-profiles)
 | 
						||
 | 
						||
(defvar guix-load-path guix-config-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.")
 | 
						||
 | 
						||
 | 
						||
;;; REPL
 | 
						||
 | 
						||
(defgroup guix-repl nil
 | 
						||
  "Settings for Guix REPLs."
 | 
						||
  :prefix "guix-repl-"
 | 
						||
  :group 'guix)
 | 
						||
 | 
						||
(defcustom guix-repl-startup-time 30000
 | 
						||
  "Time, in milliseconds, to wait for Guix REPL to startup.
 | 
						||
Same as `geiser-repl-startup-time' but is used for Guix REPL.
 | 
						||
If you have a slow system, try to increase this time."
 | 
						||
  :type 'integer
 | 
						||
  :group 'guix-repl)
 | 
						||
 | 
						||
(defcustom guix-repl-buffer-name "*Guix REPL*"
 | 
						||
  "Default name of a Geiser REPL buffer used for Guix."
 | 
						||
  :type 'string
 | 
						||
  :group 'guix-repl)
 | 
						||
 | 
						||
(defcustom guix-after-start-repl-hook '(guix-set-directory)
 | 
						||
  "Hook called after Guix REPL is started."
 | 
						||
  :type 'hook
 | 
						||
  :group 'guix-repl)
 | 
						||
 | 
						||
(defcustom guix-use-guile-server t
 | 
						||
  "If non-nil, start guile with '--listen' argument.
 | 
						||
This allows to receive information about packages using an additional
 | 
						||
REPL while some packages are being installed/removed in the main REPL."
 | 
						||
  :type 'boolean
 | 
						||
  :group 'guix-repl)
 | 
						||
 | 
						||
(defcustom guix-repl-socket-file-name-function
 | 
						||
  #'guix-repl-socket-file-name
 | 
						||
  "Function used to define a socket file name used by Guix REPL.
 | 
						||
The function is called without arguments."
 | 
						||
  :type '(choice (function-item guix-repl-socket-file-name)
 | 
						||
                 (function :tag "Other function"))
 | 
						||
  :group 'guix-repl)
 | 
						||
 | 
						||
(defcustom guix-emacs-activate-after-operation t
 | 
						||
  "Activate Emacs packages after installing.
 | 
						||
If nil, do not load autoloads of the Emacs packages after
 | 
						||
they are successfully installed."
 | 
						||
  :type 'boolean
 | 
						||
  :group 'guix-repl)
 | 
						||
 | 
						||
(defvar guix-repl-current-socket nil
 | 
						||
  "Name of a socket file used by the current Guix REPL.")
 | 
						||
 | 
						||
(defvar guix-repl-buffer nil
 | 
						||
  "Main Geiser REPL buffer used for communicating with Guix.
 | 
						||
This REPL is used for processing package actions and for
 | 
						||
receiving information if `guix-use-guile-server' is nil.")
 | 
						||
 | 
						||
(defvar guix-internal-repl-buffer nil
 | 
						||
  "Additional Geiser REPL buffer used for communicating with Guix.
 | 
						||
This REPL is used for receiving information only if
 | 
						||
`guix-use-guile-server' is non-nil.")
 | 
						||
 | 
						||
(defvar guix-internal-repl-buffer-name "*Guix Internal REPL*"
 | 
						||
  "Default name of an internal Guix REPL buffer.")
 | 
						||
 | 
						||
(defvar guix-before-repl-operation-hook nil
 | 
						||
  "Hook run before executing an operation in Guix REPL.")
 | 
						||
 | 
						||
(defvar guix-after-repl-operation-hook
 | 
						||
  '(guix-repl-autoload-emacs-packages-maybe
 | 
						||
    guix-repl-operation-success-message)
 | 
						||
  "Hook run after executing successful operation in Guix REPL.")
 | 
						||
 | 
						||
(defvar guix-repl-operation-p nil
 | 
						||
  "Non-nil, if current operation is performed by `guix-eval-in-repl'.
 | 
						||
This internal variable is used to distinguish Guix operations
 | 
						||
from operations performed in Guix REPL by a user.")
 | 
						||
 | 
						||
(defvar guix-repl-operation-type nil
 | 
						||
  "Type of the current operation performed by `guix-eval-in-repl'.
 | 
						||
This internal variable is used to define what actions should be
 | 
						||
executed after the current operation succeeds.
 | 
						||
See `guix-eval-in-repl' for details.")
 | 
						||
 | 
						||
(defun guix-repl-autoload-emacs-packages-maybe ()
 | 
						||
  "Load autoloads for Emacs packages if needed.
 | 
						||
See `guix-emacs-activate-after-operation' for details."
 | 
						||
  (and guix-emacs-activate-after-operation
 | 
						||
       ;; FIXME Since a user can work with a non-current profile (using
 | 
						||
       ;; C-u before `guix-search-by-name' and other commands), emacs
 | 
						||
       ;; packages can be installed to another profile, and the
 | 
						||
       ;; following code will not work (i.e., the autoloads for this
 | 
						||
       ;; profile will not be loaded).
 | 
						||
       (guix-emacs-autoload-packages guix-current-profile)))
 | 
						||
 | 
						||
(defun guix-repl-operation-success-message ()
 | 
						||
  "Message telling about successful Guix operation."
 | 
						||
  (message "Guix operation has been performed."))
 | 
						||
 | 
						||
(defun guix-get-guile-program (&optional socket)
 | 
						||
  "Return a value suitable for `geiser-guile-binary'."
 | 
						||
  (if (null socket)
 | 
						||
      guix-guile-program
 | 
						||
    (append (if (listp guix-guile-program)
 | 
						||
                guix-guile-program
 | 
						||
              (list guix-guile-program))
 | 
						||
            (list (concat "--listen=" socket)))))
 | 
						||
 | 
						||
(defun guix-repl-socket-file-name ()
 | 
						||
  "Return a name of a socket file used by Guix REPL."
 | 
						||
  (make-temp-name
 | 
						||
   (concat (file-name-as-directory temporary-file-directory)
 | 
						||
           "guix-repl-")))
 | 
						||
 | 
						||
(defun guix-repl-delete-socket-maybe ()
 | 
						||
  "Delete `guix-repl-current-socket' file if it exists."
 | 
						||
  (and guix-repl-current-socket
 | 
						||
       (file-exists-p guix-repl-current-socket)
 | 
						||
       (delete-file guix-repl-current-socket)))
 | 
						||
 | 
						||
(add-hook 'kill-emacs-hook 'guix-repl-delete-socket-maybe)
 | 
						||
 | 
						||
(defun guix-start-process-maybe (&optional start-msg end-msg)
 | 
						||
  "Start Geiser REPL configured for Guix if needed.
 | 
						||
START-MSG and END-MSG are strings displayed in the minibuffer in
 | 
						||
the beginning and in the end of the starting process.  If nil,
 | 
						||
display default messages."
 | 
						||
  (guix-start-repl-maybe nil
 | 
						||
                         (or start-msg "Starting Guix REPL ...")
 | 
						||
                         (or end-msg "Guix REPL has been started."))
 | 
						||
  (if guix-use-guile-server
 | 
						||
      (guix-start-repl-maybe 'internal)
 | 
						||
    (setq guix-internal-repl-buffer guix-repl-buffer)))
 | 
						||
 | 
						||
(defun guix-start-repl-maybe (&optional internal start-msg end-msg)
 | 
						||
  "Start Guix REPL if needed.
 | 
						||
If INTERNAL is non-nil, start an internal REPL.
 | 
						||
 | 
						||
START-MSG and END-MSG are strings displayed in the minibuffer in
 | 
						||
the beginning and in the end of the process.  If nil, do not
 | 
						||
display messages."
 | 
						||
  (let* ((repl-var (guix-get-repl-buffer-variable internal))
 | 
						||
         (repl (symbol-value repl-var)))
 | 
						||
    (unless (and (buffer-live-p repl)
 | 
						||
                 (get-buffer-process repl))
 | 
						||
      (and start-msg (message start-msg))
 | 
						||
      (setq guix-repl-operation-p nil)
 | 
						||
      (unless internal
 | 
						||
        ;; Guile leaves socket file after exit, so remove it if it
 | 
						||
        ;; exists (after the REPL restart).
 | 
						||
        (guix-repl-delete-socket-maybe)
 | 
						||
        (setq guix-repl-current-socket
 | 
						||
              (and guix-use-guile-server
 | 
						||
                   (or guix-repl-current-socket
 | 
						||
                       (funcall guix-repl-socket-file-name-function)))))
 | 
						||
      (let ((geiser-guile-binary (guix-get-guile-program
 | 
						||
                                  (unless internal
 | 
						||
                                    guix-repl-current-socket)))
 | 
						||
            (geiser-guile-init-file (unless internal guix-helper-file))
 | 
						||
            (repl (get-buffer-create
 | 
						||
                   (guix-get-repl-buffer-name internal))))
 | 
						||
        (guix-start-repl repl (and internal guix-repl-current-socket))
 | 
						||
        (set repl-var repl)
 | 
						||
        (and end-msg (message end-msg))
 | 
						||
        (unless internal
 | 
						||
          (run-hooks 'guix-after-start-repl-hook))))))
 | 
						||
 | 
						||
(defun guix-start-repl (buffer &optional address)
 | 
						||
  "Start Guix REPL in BUFFER.
 | 
						||
If ADDRESS is non-nil, connect to a remote guile process using
 | 
						||
this address (it should be defined by
 | 
						||
`geiser-repl--read-address')."
 | 
						||
  ;; A mix of the code from `geiser-repl--start-repl' and
 | 
						||
  ;; `geiser-repl--to-repl-buffer'.
 | 
						||
  (let ((impl 'guile)
 | 
						||
        (geiser-guile-load-path (cons (expand-file-name guix-load-path)
 | 
						||
                                      geiser-guile-load-path))
 | 
						||
        (geiser-repl-startup-time guix-repl-startup-time))
 | 
						||
    (with-current-buffer buffer
 | 
						||
      (geiser-repl-mode)
 | 
						||
      (geiser-impl--set-buffer-implementation impl)
 | 
						||
      (geiser-repl--autodoc-mode -1)
 | 
						||
      (goto-char (point-max))
 | 
						||
      (let ((prompt (geiser-con--combined-prompt
 | 
						||
                     geiser-guile--prompt-regexp
 | 
						||
                     geiser-guile--debugger-prompt-regexp)))
 | 
						||
        (geiser-repl--save-remote-data address)
 | 
						||
        (geiser-repl--start-scheme impl address prompt)
 | 
						||
        (geiser-repl--quit-setup)
 | 
						||
        (geiser-repl--history-setup)
 | 
						||
        (setq-local geiser-repl--repls (list buffer))
 | 
						||
        (geiser-repl--set-this-buffer-repl buffer)
 | 
						||
        (setq geiser-repl--connection
 | 
						||
              (geiser-con--make-connection
 | 
						||
               (get-buffer-process (current-buffer))
 | 
						||
               geiser-guile--prompt-regexp
 | 
						||
               geiser-guile--debugger-prompt-regexp))
 | 
						||
        (geiser-repl--startup impl address)
 | 
						||
        (geiser-repl--autodoc-mode 1)
 | 
						||
        (geiser-company--setup geiser-repl-company-p)
 | 
						||
        (add-hook 'comint-output-filter-functions
 | 
						||
                  'guix-repl-output-filter
 | 
						||
                  nil t)
 | 
						||
        (set-process-query-on-exit-flag
 | 
						||
         (get-buffer-process (current-buffer))
 | 
						||
         geiser-repl-query-on-kill-p)))))
 | 
						||
 | 
						||
(defun guix-repl-output-filter (str)
 | 
						||
  "Filter function suitable for `comint-output-filter-functions'.
 | 
						||
This is a replacement for `geiser-repl--output-filter'."
 | 
						||
  (cond
 | 
						||
   ((string-match-p geiser-guile--prompt-regexp str)
 | 
						||
    (geiser-autodoc--disinhibit-autodoc)
 | 
						||
    (when guix-repl-operation-p
 | 
						||
      (setq guix-repl-operation-p nil)
 | 
						||
      (run-hooks 'guix-after-repl-operation-hook)
 | 
						||
      ;; Run hooks specific to the current operation type.
 | 
						||
      (when guix-repl-operation-type
 | 
						||
        (let ((type-hook (intern
 | 
						||
                          (concat "guix-after-"
 | 
						||
                                  (symbol-name guix-repl-operation-type)
 | 
						||
                                  "-hook"))))
 | 
						||
          (setq guix-repl-operation-type nil)
 | 
						||
          (and (boundp type-hook)
 | 
						||
               (run-hooks type-hook))))))
 | 
						||
   ((string-match geiser-guile--debugger-prompt-regexp str)
 | 
						||
    (setq guix-repl-operation-p nil)
 | 
						||
    (geiser-con--connection-set-debugging geiser-repl--connection
 | 
						||
                                          (match-beginning 0))
 | 
						||
    (geiser-autodoc--disinhibit-autodoc))))
 | 
						||
 | 
						||
(defun guix-repl-exit (&optional internal no-wait)
 | 
						||
  "Exit the current Guix REPL.
 | 
						||
If INTERNAL is non-nil, exit the internal REPL.
 | 
						||
If NO-WAIT is non-nil, do not wait for the REPL process to exit:
 | 
						||
send a kill signal to it and return immediately."
 | 
						||
  (let ((repl (symbol-value (guix-get-repl-buffer-variable internal))))
 | 
						||
    (when (get-buffer-process repl)
 | 
						||
      (with-current-buffer repl
 | 
						||
        (geiser-con--connection-deactivate geiser-repl--connection t)
 | 
						||
        (comint-kill-subjob)
 | 
						||
        (unless no-wait
 | 
						||
          (while (get-buffer-process repl)
 | 
						||
            (sleep-for 0.1)))))))
 | 
						||
 | 
						||
(defun guix-get-repl-buffer (&optional internal)
 | 
						||
  "Return Guix REPL buffer; start REPL if needed.
 | 
						||
If INTERNAL is non-nil, return an additional internal REPL."
 | 
						||
  (guix-start-process-maybe)
 | 
						||
  (let ((repl (symbol-value (guix-get-repl-buffer-variable internal))))
 | 
						||
    ;; If a new Geiser REPL is started, `geiser-repl--repl' variable may
 | 
						||
    ;; be set to the new value in a Guix REPL, so set it back to a
 | 
						||
    ;; proper value here.
 | 
						||
    (with-current-buffer repl
 | 
						||
      (geiser-repl--set-this-buffer-repl repl))
 | 
						||
    repl))
 | 
						||
 | 
						||
(defun guix-get-repl-buffer-variable (&optional internal)
 | 
						||
  "Return the name of a variable with a REPL buffer."
 | 
						||
  (if internal
 | 
						||
      'guix-internal-repl-buffer
 | 
						||
    'guix-repl-buffer))
 | 
						||
 | 
						||
(defun guix-get-repl-buffer-name (&optional internal)
 | 
						||
  "Return the name of a REPL buffer."
 | 
						||
  (if internal
 | 
						||
      guix-internal-repl-buffer-name
 | 
						||
    guix-repl-buffer-name))
 | 
						||
 | 
						||
(defun guix-switch-to-repl (&optional internal)
 | 
						||
  "Switch to Guix REPL.
 | 
						||
If INTERNAL is non-nil (interactively with prefix), switch to the
 | 
						||
additional internal REPL if it exists."
 | 
						||
  (interactive "P")
 | 
						||
  (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
 | 
						||
  "Buffer from which the latest Guix operation was performed.")
 | 
						||
 | 
						||
(defun guix-eval (str)
 | 
						||
  "Evaluate STR with guile expression using Guix REPL.
 | 
						||
See `guix-geiser-eval' for details."
 | 
						||
  (guix-geiser-eval str (guix-get-repl-buffer 'internal)))
 | 
						||
 | 
						||
(defun guix-eval-read (str)
 | 
						||
  "Evaluate STR with guile expression using Guix REPL.
 | 
						||
See `guix-geiser-eval-read' for details."
 | 
						||
  (guix-geiser-eval-read str (guix-get-repl-buffer 'internal)))
 | 
						||
 | 
						||
(defun guix-eval-in-repl (str &optional operation-buffer operation-type)
 | 
						||
  "Switch to Guix REPL and evaluate STR with guile expression there.
 | 
						||
If OPERATION-BUFFER is non-nil, it should be a buffer from which
 | 
						||
the current operation was performed.
 | 
						||
 | 
						||
If OPERATION-TYPE is non-nil, it should be a symbol.  After
 | 
						||
successful executing of the current operation,
 | 
						||
`guix-after-OPERATION-TYPE-hook' is called."
 | 
						||
  (run-hooks 'guix-before-repl-operation-hook)
 | 
						||
  (setq guix-repl-operation-p t
 | 
						||
        guix-repl-operation-type operation-type
 | 
						||
        guix-operation-buffer operation-buffer)
 | 
						||
  (guix-geiser-eval-in-repl str (guix-get-repl-buffer)))
 | 
						||
 | 
						||
(provide 'guix-backend)
 | 
						||
 | 
						||
;;; guix-backend.el ends here
 |