1 ;;; guix-backend.el --- Communication with Geiser
3 ;; Copyright © 2014 Alex Kost <alezost@gmail.com>
5 ;; This file is part of GNU Guix.
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.
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.
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/>.
22 ;; This file provides the code for interacting with Guile using Geiser.
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.
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>).
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.
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.
54 (require 'geiser-mode
)
57 (defvar guix-load-path
58 (file-name-directory (or load-file-name
59 (locate-library "guix")))
60 "Directory with scheme files for \"guix.el\" package.")
62 (defvar guix-helper-file
63 (expand-file-name "guix-helper.scm" guix-load-path
)
64 "Auxiliary scheme file for loading.")
66 (defvar guix-guile-program
(or geiser-guile-binary
"guile")
67 "Name of the guile executable used for Guix REPL.
68 May be either a string (the name of the executable) or a list of
73 Where ARGS is a list of arguments to the guile program.")
78 (defgroup guix-repl nil
79 "Settings for Guix REPLs."
83 (defcustom guix-repl-startup-time
30000
84 "Time, in milliseconds, to wait for Guix REPL to startup.
85 Same as `geiser-repl-startup-time' but is used for Guix REPL.
86 If you have a slow system, try to increase this time."
90 (defcustom guix-repl-buffer-name
"*Guix REPL*"
91 "Default name of a Geiser REPL buffer used for Guix."
95 (defcustom guix-after-start-repl-hook
()
96 "Hook called after Guix REPL is started."
100 (defcustom guix-use-guile-server t
101 "If non-nil, start guile with '--listen' argument.
102 This allows to receive information about packages using an additional
103 REPL while some packages are being installed/removed in the main REPL."
107 (defcustom guix-default-port
37246
108 "Default port used if `guix-use-guile-server' is non-nil."
112 (defvar guix-repl-buffer nil
113 "Main Geiser REPL buffer used for communicating with Guix.
114 This REPL is used for processing package actions and for
115 receiving information if `guix-use-guile-server' is nil.")
117 (defvar guix-internal-repl-buffer nil
118 "Additional Geiser REPL buffer used for communicating with Guix.
119 This REPL is used for receiving information only if
120 `guix-use-guile-server' is non-nil.")
122 (defvar guix-internal-repl-buffer-name
"*Guix Internal REPL*"
123 "Default name of an internal Guix REPL buffer.")
125 (defvar guix-before-repl-operation-hook nil
126 "Hook run before executing an operation in Guix REPL.")
128 (defvar guix-after-repl-operation-hook
129 '(guix-emacs-load-autoloads-maybe
130 guix-repl-operation-success-message
)
131 "Hook run after executing successful operation in Guix REPL.")
133 (defvar guix-repl-operation-p nil
134 "Non-nil, if current operation is performed by `guix-eval-in-repl'.
135 This internal variable is used to distinguish Guix operations
136 from operations performed in Guix REPL by a user.")
138 (defvar guix-repl-operation-type nil
139 "Type of the current operation performed by `guix-eval-in-repl'.
140 This internal variable is used to define what actions should be
141 executed after the current operation succeeds.
142 See `guix-eval-in-repl' for details.")
144 (defun guix-repl-operation-success-message ()
145 "Message telling about successful Guix operation."
146 (message "Guix operation has been performed."))
148 (defun guix-get-guile-program (&optional internal
)
149 "Return a value suitable for `geiser-guile-binary'."
151 (not guix-use-guile-server
))
153 (append (if (listp guix-guile-program
)
155 (list guix-guile-program
))
156 ;; Guile understands "--listen=..." but not "--listen ..."
157 (list (concat "--listen="
158 (number-to-string guix-default-port
))))))
160 (defun guix-start-process-maybe (&optional start-msg end-msg
)
161 "Start Geiser REPL configured for Guix if needed.
162 START-MSG and END-MSG are strings displayed in the minibuffer in
163 the beginning and in the end of the starting process. If nil,
164 display default messages."
165 (guix-start-repl-maybe nil
166 (or start-msg
"Starting Guix REPL ...")
167 (or end-msg
"Guix REPL has been started."))
168 (if guix-use-guile-server
169 (guix-start-repl-maybe 'internal
)
170 (setq guix-internal-repl-buffer guix-repl-buffer
)))
172 (defun guix-start-repl-maybe (&optional internal start-msg end-msg
)
173 "Start Guix REPL if needed.
174 If INTERNAL is non-nil, start an internal REPL.
176 START-MSG and END-MSG are strings displayed in the minibuffer in
177 the beginning and in the end of the process. If nil, do not
179 (let* ((repl-var (guix-get-repl-buffer-variable internal
))
180 (repl (symbol-value repl-var
)))
181 (unless (and (buffer-live-p repl
)
182 (get-buffer-process repl
))
183 (and start-msg
(message start-msg
))
184 (setq guix-repl-operation-p nil
)
185 (let ((geiser-guile-binary (guix-get-guile-program internal
))
186 (geiser-guile-init-file (or internal guix-helper-file
))
187 (repl (get-buffer-create
188 (guix-get-repl-buffer-name internal
))))
190 (guix-start-repl repl
192 (geiser-repl--read-address
193 "localhost" guix-default-port
)))
195 (error (concat "Couldn't start Guix REPL. Perhaps the port %s is busy.\n"
196 "See buffer '%s' for details")
197 guix-default-port
(buffer-name repl
))))
199 (and end-msg
(message end-msg
))
201 (run-hooks 'guix-after-start-repl-hook
))))))
203 (defun guix-start-repl (buffer &optional address
)
204 "Start Guix REPL in BUFFER.
205 If ADDRESS is non-nil, connect to a remote guile process using
206 this address (it should be defined by
207 `geiser-repl--read-address')."
208 ;; A mix of the code from `geiser-repl--start-repl' and
209 ;; `geiser-repl--to-repl-buffer'.
211 (geiser-guile-load-path (cons guix-load-path
212 geiser-guile-load-path
))
213 (geiser-repl-startup-time guix-repl-startup-time
))
214 (with-current-buffer buffer
216 (geiser-impl--set-buffer-implementation impl
)
217 (geiser-repl--autodoc-mode -
1)
218 (goto-char (point-max))
219 (let ((prompt (geiser-con--combined-prompt
220 geiser-guile--prompt-regexp
221 geiser-guile--debugger-prompt-regexp
)))
222 (geiser-repl--save-remote-data address
)
223 (geiser-repl--start-scheme impl address prompt
)
224 (geiser-repl--quit-setup)
225 (geiser-repl--history-setup)
226 (setq-local geiser-repl--repls
(list buffer
))
227 (geiser-repl--set-this-buffer-repl buffer
)
228 (setq geiser-repl--connection
229 (geiser-con--make-connection
230 (get-buffer-process (current-buffer))
231 geiser-guile--prompt-regexp
232 geiser-guile--debugger-prompt-regexp
))
233 (geiser-repl--startup impl address
)
234 (geiser-repl--autodoc-mode 1)
235 (geiser-company--setup geiser-repl-company-p
)
236 (add-hook 'comint-output-filter-functions
237 'guix-repl-output-filter
239 (set-process-query-on-exit-flag
240 (get-buffer-process (current-buffer))
241 geiser-repl-query-on-kill-p
)))))
243 (defun guix-repl-output-filter (str)
244 "Filter function suitable for `comint-output-filter-functions'.
245 This is a replacement for `geiser-repl--output-filter'."
247 ((string-match-p geiser-guile--prompt-regexp str
)
248 (geiser-autodoc--disinhibit-autodoc)
249 (when guix-repl-operation-p
250 (setq guix-repl-operation-p nil
)
251 (run-hooks 'guix-after-repl-operation-hook
)
252 ;; Run hooks specific to the current operation type.
253 (when guix-repl-operation-type
254 (let ((type-hook (intern
255 (concat "guix-after-"
256 (symbol-name guix-repl-operation-type
)
258 (setq guix-repl-operation-type nil
)
259 (and (boundp type-hook
)
260 (run-hooks type-hook
))))))
261 ((string-match geiser-guile--debugger-prompt-regexp str
)
262 (setq guix-repl-operation-p nil
)
263 (geiser-con--connection-set-debugging geiser-repl--connection
265 (geiser-autodoc--disinhibit-autodoc))))
267 (defun guix-repl-exit (&optional internal no-wait
)
268 "Exit the current Guix REPL.
269 If INTERNAL is non-nil, exit the internal REPL.
270 If NO-WAIT is non-nil, do not wait for the REPL process to exit:
271 send a kill signal to it and return immediately."
272 (let ((repl (symbol-value (guix-get-repl-buffer-variable internal
))))
273 (when (get-buffer-process repl
)
274 (with-current-buffer repl
275 (geiser-con--connection-deactivate geiser-repl--connection t
)
278 (while (get-buffer-process repl
)
279 (sleep-for 0.1)))))))
281 (defun guix-get-repl-buffer (&optional internal
)
282 "Return Guix REPL buffer; start REPL if needed.
283 If INTERNAL is non-nil, return an additional internal REPL."
284 (guix-start-process-maybe)
285 (let ((repl (symbol-value (guix-get-repl-buffer-variable internal
))))
286 ;; If a new Geiser REPL is started, `geiser-repl--repl' variable may
287 ;; be set to the new value in a Guix REPL, so set it back to a
288 ;; proper value here.
289 (with-current-buffer repl
290 (geiser-repl--set-this-buffer-repl repl
))
293 (defun guix-get-repl-buffer-variable (&optional internal
)
294 "Return the name of a variable with a REPL buffer."
296 'guix-internal-repl-buffer
299 (defun guix-get-repl-buffer-name (&optional internal
)
300 "Return the name of a REPL buffer."
302 guix-internal-repl-buffer-name
303 guix-repl-buffer-name
))
305 (defun guix-switch-to-repl (&optional internal
)
306 "Switch to Guix REPL.
307 If INTERNAL is non-nil (interactively with prefix), switch to the
308 additional internal REPL if it exists."
310 (geiser-repl--switch-to-buffer (guix-get-repl-buffer internal
)))
313 ;;; Evaluating expressions
315 (defvar guix-operation-buffer nil
316 "Buffer from which the latest Guix operation was performed.")
318 (defun guix-make-guile-expression (fun &rest args
)
319 "Return string containing a guile expression for calling FUN with ARGS."
320 (format "(%S %s)" fun
326 ;; An ugly hack to separate 'false' from nil
329 (concat "#" (prin1-to-string arg t
)))
330 ((or (symbolp arg
) (listp arg
))
331 (concat "'" (prin1-to-string arg
)))
332 (t (prin1-to-string arg
))))
336 (defun guix-eval (str &optional wrap
)
337 "Evaluate guile expression STR.
338 If WRAP is non-nil, wrap STR into (begin ...) form.
339 Return a list of strings with result values of evaluation."
340 (with-current-buffer (guix-get-repl-buffer 'internal
)
341 (let* ((wrapped (if wrap
(geiser-debug--wrap-region str
) str
))
342 (code `(:eval
(:scm
,wrapped
)))
343 (ret (geiser-eval--send/wait code
)))
344 (if (geiser-eval--retort-error ret
)
345 (error "Error in evaluating guile expression: %s"
346 (geiser-eval--retort-output ret
))
347 (cdr (assq 'result ret
))))))
349 (defun guix-eval-read (str &optional wrap
)
350 "Evaluate guile expression STR.
351 For the meaning of WRAP, see `guix-eval'.
352 Return elisp expression of the first result value of evaluation."
353 ;; Parsing scheme code with elisp `read' is probably not the best idea.
354 (read (replace-regexp-in-string
355 "#f\\|#<unspecified>" "nil"
356 (replace-regexp-in-string
357 "#t" "t" (car (guix-eval str wrap
))))))
359 (defun guix-eval-in-repl (str &optional operation-buffer operation-type
)
360 "Switch to Guix REPL and evaluate STR with guile expression there.
361 If OPERATION-BUFFER is non-nil, it should be a buffer from which
362 the current operation was performed.
364 If OPERATION-TYPE is non-nil, it should be a symbol. After
365 successful executing of the current operation,
366 `guix-after-OPERATION-TYPE-hook' is called."
367 (run-hooks 'guix-before-repl-operation-hook
)
368 (setq guix-repl-operation-p t
369 guix-repl-operation-type operation-type
370 guix-operation-buffer operation-buffer
)
371 (let ((repl (guix-get-repl-buffer)))
372 (with-current-buffer repl
373 (geiser-repl--send str
))
374 (geiser-repl--switch-to-buffer repl
)))
376 (provide 'guix-backend
)
378 ;;; guix-backend.el ends here