emacs: Add support for displaying outputs.
[jackhill/guix/guix.git] / emacs / guix-backend.el
CommitLineData
457f60fa
AK
1;;; guix-backend.el --- Communication with Geiser
2
3;; Copyright © 2014 Alex Kost <alezost@gmail.com>
4
5;; This file is part of GNU Guix.
6
7;; GNU Guix is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Guix is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; This file provides the code for interacting with Guile using Geiser.
23
24;; By default (if `guix-use-guile-server' is non-nil) 2 Geiser REPLs are
25;; started. The main one (with "guile --listen" process) is used for
26;; "interacting" with a user - for showing a progress of
27;; installing/deleting Guix packages. The second (internal) REPL is
28;; used for synchronous evaluating, e.g. when information about
29;; packages/generations should be received for a list/info buffer.
30;;
31;; This "2 REPLs concept" makes it possible to have a running process of
32;; installing/deleting packages and to continue to search/list/get info
33;; about other packages at the same time. If you prefer to use a single
34;; Guix REPL, do not try to receive any information while there is a
35;; running code in the REPL (see
36;; <https://github.com/jaor/geiser/issues/28>).
37;;
38;; If you need to use "guix.el" in another Emacs (i.e. when there is
39;; a runnig "guile --listen..." REPL somewhere), you can either change
40;; `guix-default-port' in that Emacs instance or set
41;; `guix-use-guile-server' to t.
42;;
43;; Guix REPLs (unlike the usual Geiser REPLs) are not added to
44;; `geiser-repl--repls' variable, and thus cannot be used for evaluating
45;; while editing scm-files. The only purpose of Guix REPLs is to be an
46;; intermediate between "Guix/Guile level" and "Emacs interface level".
47;; That being said you can still want to use a Guix REPL while hacking
48;; auxiliary scheme-files for "guix.el". You can just use "M-x
49;; connect-to-guile" (connect to "localhost" and `guix-default-port') to
50;; have a usual Geiser REPL with all stuff defined by "guix.el" package.
51
52;;; Code:
53
54(require 'geiser-mode)
55
56(defvar guix-load-path
57 (file-name-directory (or load-file-name
58 (locate-library "guix")))
59 "Directory with scheme files for \"guix.el\" package.")
60
61(defvar guix-helper-file
62 (expand-file-name "guix-helper.scm" guix-load-path)
63 "Auxiliary scheme file for loading.")
64
65(defvar guix-guile-program (or geiser-guile-binary "guile")
66 "Name of the guile executable used for Guix REPL.
67May be either a string (the name of the executable) or a list of
68strings of the form:
69
70 (NAME . ARGS)
71
72Where ARGS is a list of arguments to the guile program.")
73
74\f
75;;; REPL
76
77(defgroup guix-repl nil
78 "Settings for Guix REPLs."
79 :prefix "guix-repl-"
80 :group 'guix)
81
82(defcustom guix-repl-startup-time 30000
83 "Time, in milliseconds, to wait for Guix REPL to startup.
84Same as `geiser-repl-startup-time' but is used for Guix REPL.
85If you have a slow system, try to increase this time."
86 :type 'integer
87 :group 'guix-repl)
88
89(defcustom guix-repl-buffer-name "*Guix REPL*"
90 "Default name of a Geiser REPL buffer used for Guix."
91 :type 'string
92 :group 'guix-repl)
93
94(defcustom guix-after-start-repl-hook ()
95 "Hook called after Guix REPL is started."
96 :type 'hook
97 :group 'guix-repl)
98
99(defcustom guix-use-guile-server t
100 "If non-nil, start guile with '--listen' argument.
101This allows to receive information about packages using an additional
102REPL while some packages are being installed/removed in the main REPL."
103 :type 'boolean
104 :group 'guix-repl)
105
106(defcustom guix-default-port 37246
107 "Default port used if `guix-use-guile-server' is non-nil."
108 :type 'integer
109 :group 'guix-repl)
110
111(defvar guix-repl-buffer nil
112 "Main Geiser REPL buffer used for communicating with Guix.
113This REPL is used for processing package actions and for
114receiving information if `guix-use-guile-server' is nil.")
115
116(defvar guix-internal-repl-buffer nil
117 "Additional Geiser REPL buffer used for communicating with Guix.
118This REPL is used for receiving information only if
119`guix-use-guile-server' is non-nil.")
120
121(defvar guix-internal-repl-buffer-name "*Guix Internal REPL*"
122 "Default name of an internal Guix REPL buffer.")
123
124(defun guix-get-guile-program (&optional internal)
125 "Return a value suitable for `geiser-guile-binary'."
126 (if (or internal
127 (not guix-use-guile-server))
128 guix-guile-program
129 (append (if (listp guix-guile-program)
130 guix-guile-program
131 (list guix-guile-program))
132 ;; Guile understands "--listen=..." but not "--listen ..."
133 (list (concat "--listen="
134 (number-to-string guix-default-port))))))
135
136(defun guix-start-process-maybe ()
137 "Start Geiser REPL configured for Guix if needed."
138 (guix-start-repl-maybe)
139 (if guix-use-guile-server
140 (guix-start-repl-maybe 'internal)
141 (setq guix-internal-repl-buffer guix-repl-buffer)))
142
143(defun guix-start-repl-maybe (&optional internal)
144 "Start Guix REPL if needed.
145If INTERNAL is non-nil, start an internal REPL."
146 (let* ((repl-var (guix-get-repl-buffer-variable internal))
147 (repl (symbol-value repl-var)))
148 (unless (and (buffer-live-p repl)
149 (get-buffer-process repl))
150 ;; Kill REPL buffer with a dead process
151 (and (buffer-live-p repl) (kill-buffer repl))
152 (or internal
153 (message "Starting Geiser REPL for Guix ..."))
154 (let ((geiser-guile-binary (guix-get-guile-program internal))
155 (geiser-guile-init-file (or internal guix-helper-file))
156 (repl (get-buffer-create
157 (guix-get-repl-buffer-name internal))))
158 (condition-case err
159 (guix-start-repl repl
160 (and internal
161 (geiser-repl--read-address
162 "localhost" guix-default-port)))
163 (text-read-only
164 (error (concat "Couldn't start Guix REPL. Perhaps the port %s is busy.\n"
165 "See buffer '%s' for details")
166 guix-default-port (buffer-name repl))))
167 (set repl-var repl)
168 (unless internal
169 (message "Guix REPL has been started.")
170 (run-hooks 'guix-after-start-repl-hook))))))
171
172(defun guix-start-repl (buffer &optional address)
173 "Start Guix REPL in BUFFER.
174If ADDRESS is non-nil, connect to a remote guile process using
175this address (it should be defined by
176`geiser-repl--read-address')."
177 ;; A mix of the code from `geiser-repl--start-repl' and
178 ;; `geiser-repl--to-repl-buffer'.
179 (let ((impl 'guile)
180 (geiser-guile-load-path (list guix-load-path))
181 (geiser-repl-startup-time guix-repl-startup-time))
182 (with-current-buffer buffer
183 (geiser-repl-mode)
184 (geiser-impl--set-buffer-implementation impl)
185 (geiser-repl--autodoc-mode -1)
186 (goto-char (point-max))
187 (let* ((prompt-re (geiser-repl--prompt-regexp impl))
188 (deb-prompt-re (geiser-repl--debugger-prompt-regexp impl))
189 (prompt (geiser-con--combined-prompt prompt-re deb-prompt-re)))
190 (or prompt-re
191 (error "Oh no! Guix REPL in the buffer '%s' has not been started"
192 (buffer-name buffer)))
193 (geiser-repl--save-remote-data address)
194 (geiser-repl--start-scheme impl address prompt)
195 (geiser-repl--quit-setup)
196 (geiser-repl--history-setup)
197 (setq-local geiser-repl--repls (list buffer))
198 (geiser-repl--set-this-buffer-repl buffer)
199 (setq geiser-repl--connection
200 (geiser-con--make-connection
201 (get-buffer-process (current-buffer))
202 prompt-re
203 deb-prompt-re))
204 (geiser-repl--startup impl address)
205 (geiser-repl--autodoc-mode 1)
206 (geiser-company--setup geiser-repl-company-p)
207 (add-hook 'comint-output-filter-functions
208 'geiser-repl--output-filter
209 nil t)
210 (set-process-query-on-exit-flag
211 (get-buffer-process (current-buffer))
212 geiser-repl-query-on-kill-p)))))
213
214(defun guix-get-repl-buffer (&optional internal)
215 "Return Guix REPL buffer; start REPL if needed.
216If INTERNAL is non-nil, return an additional internal REPL."
217 (guix-start-process-maybe)
218 (let ((repl (symbol-value (guix-get-repl-buffer-variable internal))))
219 ;; If a new Geiser REPL is started, `geiser-repl--repl' variable may
220 ;; be set to the new value in a Guix REPL, so set it back to a
221 ;; proper value here.
222 (with-current-buffer repl
223 (geiser-repl--set-this-buffer-repl repl))
224 repl))
225
226(defun guix-get-repl-buffer-variable (&optional internal)
227 "Return the name of a variable with a REPL buffer."
228 (if internal
229 'guix-internal-repl-buffer
230 'guix-repl-buffer))
231
232(defun guix-get-repl-buffer-name (&optional internal)
233 "Return the name of a REPL buffer."
234 (if internal
235 guix-internal-repl-buffer-name
236 guix-repl-buffer-name))
237
238(defun guix-switch-to-repl (&optional internal)
239 "Switch to Guix REPL.
240If INTERNAL is non-nil (interactively with prefix), switch to the
241additional internal REPL if it exists."
242 (interactive "P")
243 (geiser-repl--switch-to-buffer (guix-get-repl-buffer internal)))
244
245\f
246;;; Evaluating expressions
247
248(defun guix-make-guile-expression (fun &rest args)
249 "Return string containing a guile expression for calling FUN with ARGS."
250 (format "(%S %s)" fun
251 (mapconcat
252 (lambda (arg)
253 (cond
254 ((null arg) "'()")
255 ((or (eq arg t)
256 ;; An ugly hack to separate 'false' from nil
257 (equal arg 'f)
258 (keywordp arg))
259 (concat "#" (prin1-to-string arg t)))
260 ((or (symbolp arg) (listp arg))
261 (concat "'" (prin1-to-string arg)))
262 (t (prin1-to-string arg))))
263 args
264 " ")))
265
266(defun guix-eval (str &optional wrap)
267 "Evaluate guile expression STR.
268If WRAP is non-nil, wrap STR into (begin ...) form.
269Return a list of strings with result values of evaluation."
270 (with-current-buffer (guix-get-repl-buffer 'internal)
271 (let* ((wrapped (if wrap (geiser-debug--wrap-region str) str))
272 (code `(:eval (:scm ,wrapped)))
273 (ret (geiser-eval--send/wait code)))
274 (if (geiser-eval--retort-error ret)
275 (error "Error in evaluating guile expression: %s"
276 (geiser-eval--retort-output ret))
277 (cdr (assq 'result ret))))))
278
279(defun guix-eval-read (str &optional wrap)
280 "Evaluate guile expression STR.
281For the meaning of WRAP, see `guix-eval'.
282Return elisp expression of the first result value of evaluation."
283 ;; Parsing scheme code with elisp `read' is probably not the best idea.
284 (read (replace-regexp-in-string
285 "#f\\|#<unspecified>" "nil"
286 (replace-regexp-in-string
287 "#t" "t" (car (guix-eval str wrap))))))
288
289(defun guix-eval-in-repl (str)
290 "Switch to Guix REPL and evaluate STR with guile expression there."
291 (let ((repl (guix-get-repl-buffer)))
292 (with-current-buffer repl
293 (delete-region (geiser-repl--last-prompt-end) (point-max))
294 (goto-char (point-max))
295 (insert str)
296 (geiser-repl--send-input))
297 (geiser-repl--switch-to-buffer repl)))
298
299(provide 'guix-backend)
300
301;;; guix-backend.el ends here