summary refs log tree commit diff
path: root/emacs/guix-backend.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs/guix-backend.el')
-rw-r--r--emacs/guix-backend.el301
1 files changed, 301 insertions, 0 deletions
diff --git a/emacs/guix-backend.el b/emacs/guix-backend.el
new file mode 100644
index 0000000000..46d0f06778
--- /dev/null
+++ b/emacs/guix-backend.el
@@ -0,0 +1,301 @@
+;;; guix-backend.el --- Communication with Geiser
+
+;; Copyright © 2014 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 Geiser.
+
+;; By default (if `guix-use-guile-server' is non-nil) 2 Geiser 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>).
+;;
+;; If you need to use "guix.el" in another Emacs (i.e. when there is
+;; a runnig "guile --listen..." REPL somewhere), you can either change
+;; `guix-default-port' in that Emacs instance or set
+;; `guix-use-guile-server' to t.
+;;
+;; 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 "M-x
+;; connect-to-guile" (connect to "localhost" and `guix-default-port') to
+;; have a usual Geiser REPL with all stuff defined by "guix.el" package.
+
+;;; Code:
+
+(require 'geiser-mode)
+
+(defvar guix-load-path
+  (file-name-directory (or load-file-name
+                           (locate-library "guix")))
+  "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
+
+(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 ()
+  "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-default-port 37246
+  "Default port used if `guix-use-guile-server' is non-nil."
+  :type 'integer
+  :group '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.")
+
+(defun guix-get-guile-program (&optional internal)
+  "Return a value suitable for `geiser-guile-binary'."
+  (if (or internal
+          (not guix-use-guile-server))
+      guix-guile-program
+    (append (if (listp guix-guile-program)
+                guix-guile-program
+              (list guix-guile-program))
+            ;; Guile understands "--listen=..." but not "--listen ..."
+            (list (concat "--listen="
+                          (number-to-string guix-default-port))))))
+
+(defun guix-start-process-maybe ()
+  "Start Geiser REPL configured for Guix if needed."
+  (guix-start-repl-maybe)
+  (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 Guix REPL if needed.
+If INTERNAL is non-nil, start an internal REPL."
+  (let* ((repl-var (guix-get-repl-buffer-variable internal))
+         (repl (symbol-value repl-var)))
+    (unless (and (buffer-live-p repl)
+                 (get-buffer-process repl))
+      ;; Kill REPL buffer with a dead process
+      (and (buffer-live-p repl) (kill-buffer repl))
+      (or internal
+          (message "Starting Geiser REPL for Guix ..."))
+      (let ((geiser-guile-binary (guix-get-guile-program internal))
+            (geiser-guile-init-file (or internal guix-helper-file))
+            (repl (get-buffer-create
+                   (guix-get-repl-buffer-name internal))))
+        (condition-case err
+            (guix-start-repl repl
+                             (and internal
+                                  (geiser-repl--read-address
+                                   "localhost" guix-default-port)))
+          (text-read-only
+           (error (concat "Couldn't start Guix REPL.  Perhaps the port %s is busy.\n"
+                          "See buffer '%s' for details")
+                  guix-default-port (buffer-name repl))))
+        (set repl-var repl)
+        (unless internal
+          (message "Guix REPL has been started.")
+          (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 (list guix-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-re (geiser-repl--prompt-regexp impl))
+             (deb-prompt-re (geiser-repl--debugger-prompt-regexp impl))
+             (prompt (geiser-con--combined-prompt prompt-re deb-prompt-re)))
+        (or prompt-re
+            (error "Oh no! Guix REPL in the buffer '%s' has not been started"
+                   (buffer-name buffer)))
+        (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))
+               prompt-re
+               deb-prompt-re))
+        (geiser-repl--startup impl address)
+        (geiser-repl--autodoc-mode 1)
+        (geiser-company--setup geiser-repl-company-p)
+        (add-hook 'comint-output-filter-functions
+                  'geiser-repl--output-filter
+                  nil t)
+        (set-process-query-on-exit-flag
+         (get-buffer-process (current-buffer))
+         geiser-repl-query-on-kill-p)))))
+
+(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)))
+
+
+;;; Evaluating expressions
+
+(defun guix-make-guile-expression (fun &rest args)
+  "Return string containing a guile expression for calling FUN with ARGS."
+  (format "(%S %s)" fun
+          (mapconcat
+           (lambda (arg)
+             (cond
+              ((null arg) "'()")
+              ((or (eq arg t)
+                   ;; An ugly hack to separate 'false' from nil
+                   (equal arg 'f)
+                   (keywordp arg))
+               (concat "#" (prin1-to-string arg t)))
+              ((or (symbolp arg) (listp arg))
+               (concat "'" (prin1-to-string arg)))
+              (t (prin1-to-string arg))))
+           args
+           " ")))
+
+(defun guix-eval (str &optional wrap)
+  "Evaluate guile expression STR.
+If WRAP is non-nil, wrap STR into (begin ...) form.
+Return a list of strings with result values of evaluation."
+  (with-current-buffer (guix-get-repl-buffer 'internal)
+    (let* ((wrapped (if wrap (geiser-debug--wrap-region str) str))
+           (code `(:eval (:scm ,wrapped)))
+           (ret (geiser-eval--send/wait code)))
+      (if (geiser-eval--retort-error ret)
+          (error "Error in evaluating guile expression: %s"
+                 (geiser-eval--retort-output ret))
+        (cdr (assq 'result ret))))))
+
+(defun guix-eval-read (str &optional wrap)
+  "Evaluate guile expression STR.
+For the meaning of WRAP, see `guix-eval'.
+Return elisp expression of the first result value of evaluation."
+  ;; Parsing scheme code with elisp `read' is probably not the best idea.
+  (read (replace-regexp-in-string
+         "#f\\|#<unspecified>" "nil"
+         (replace-regexp-in-string
+          "#t" "t" (car (guix-eval str wrap))))))
+
+(defun guix-eval-in-repl (str)
+  "Switch to Guix REPL and evaluate STR with guile expression there."
+  (let ((repl (guix-get-repl-buffer)))
+    (with-current-buffer repl
+      (delete-region (geiser-repl--last-prompt-end) (point-max))
+      (goto-char (point-max))
+      (insert str)
+      (geiser-repl--send-input))
+    (geiser-repl--switch-to-buffer repl)))
+
+(provide 'guix-backend)
+
+;;; guix-backend.el ends here