guile: pretty printing evaluation results (#64)
[geiser.git] / elisp / geiser-guile.el
blobe8eb11aebb19812b0cbb4b2e29004bdcfd50dc7b
1 ;; geiser-guile.el -- guile's implementation of the geiser protocols
3 ;; Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jose Antonio Ortega Ruiz
5 ;; This program is free software; you can redistribute it and/or
6 ;; modify it under the terms of the Modified BSD License. You should
7 ;; have received a copy of the license along with this program. If
8 ;; not, see <http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5>.
10 ;; Start date: Sun Mar 08, 2009 23:03
14 (require 'geiser-connection)
15 (require 'geiser-syntax)
16 (require 'geiser-custom)
17 (require 'geiser-base)
18 (require 'geiser-eval)
19 (require 'geiser-edit)
20 (require 'geiser-log)
21 (require 'geiser)
23 (require 'compile)
24 (require 'info-look)
26 (eval-when-compile (require 'cl))
29 ;;; Customization:
31 (defgroup geiser-guile nil
32 "Customization for Geiser's Guile flavour."
33 :group 'geiser)
35 (geiser-custom--defcustom geiser-guile-binary
36 (cond ((eq system-type 'windows-nt) "guile.exe")
37 ((eq system-type 'darwin) "guile")
38 (t "guile"))
39 "Name to use to call the Guile executable when starting a REPL."
40 :type '(choice string (repeat string))
41 :group 'geiser-guile)
43 (geiser-custom--defcustom geiser-guile-load-path nil
44 "A list of paths to be added to Guile's load path when it's
45 started."
46 :type '(repeat file)
47 :group 'geiser-guile)
49 (geiser-custom--defcustom geiser-guile-init-file "~/.guile-geiser"
50 "Initialization file with user code for the Guile REPL.
51 If all you want is to load ~/.guile, set
52 `geiser-guile-load-init-file-p' instead."
53 :type 'string
54 :group 'geiser-guile)
56 (geiser-custom--defcustom geiser-guile-load-init-file-p nil
57 "Whether to load ~/.guile when starting Guile.
58 Note that, due to peculiarities in the way Guile loads its init
59 file, using `geiser-guile-init-file' is not equivalent to setting
60 this variable to t."
61 :type 'boolean
62 :group 'geiser-guile)
64 (geiser-custom--defcustom geiser-guile-debug-show-bt-p nil
65 "Whether to autmatically show a full backtrace when entering the debugger.
66 If `nil', only the last frame is shown."
67 :type 'boolean
68 :group 'geiser-guile)
70 (geiser-custom--defcustom geiser-guile-jump-on-debug-p nil
71 "Whether to autmatically jump to error when entering the debugger.
72 If `t', Geiser will use `next-error' to jump to the error's location."
73 :type 'boolean
74 :group 'geiser-guile)
76 (geiser-custom--defcustom geiser-guile-show-debug-help-p t
77 "Whether to show brief help in the echo area when entering the debugger."
78 :type 'boolean
79 :group 'geiser-guile)
81 (geiser-custom--defcustom geiser-guile-warning-level 'medium
82 "Verbosity of the warnings reported by Guile.
84 You can either choose one of the predefined warning sets, or
85 provide a list of symbols identifying the ones you want. Possible
86 choices are arity-mismatch, unbound-variable, unused-variable and
87 unused-toplevel. Unrecognised symbols are ignored.
89 The predefined levels are:
91 - Medium: arity-mismatch, unbound-variable, format
92 - High: arity-mismatch, unbound-variable, unused-variable, format
93 - None: no warnings
95 Changes to the value of this variable will automatically take
96 effect on new REPLs. For existing ones, use the command
97 \\[geiser-guile-update-warning-level]."
98 :type '(choice (const :tag "Medium (arity and unbound vars)" medium)
99 (const :tag "High (also unused vars)" high)
100 (const :tag "No warnings" none)
101 (repeat :tag "Custom" symbol))
102 :group 'geiser-guile)
104 (geiser-custom--defcustom geiser-guile-extra-keywords nil
105 "Extra keywords highlighted in Guile scheme buffers."
106 :type '(repeat string)
107 :group 'geiser-guile)
109 (geiser-custom--defcustom geiser-guile-case-sensitive-p t
110 "Non-nil means keyword highlighting is case-sensitive."
111 :type 'boolean
112 :group 'geiser-guile)
114 (geiser-custom--defcustom geiser-guile-manual-lookup-other-window-p nil
115 "Non-nil means pop up the Info buffer in another window."
116 :type 'boolean
117 :group 'geiser-guile)
119 (geiser-custom--defcustom geiser-guile-manual-lookup-nodes
120 '("Guile" "guile-2.0")
121 "List of info nodes that, when present, are used for manual lookups"
122 :type '(repeat string)
123 :group 'geiser-guile)
126 ;;; REPL support:
128 (defun geiser-guile--binary ()
129 (if (listp geiser-guile-binary)
130 (car geiser-guile-binary)
131 geiser-guile-binary))
133 (defun geiser-guile--parameters ()
134 "Return a list with all parameters needed to start Guile.
135 This function uses `geiser-guile-init-file' if it exists."
136 (let ((init-file (and (stringp geiser-guile-init-file)
137 (expand-file-name geiser-guile-init-file)))
138 (q-flags (and (not geiser-guile-load-init-file-p) '("-q"))))
139 `(,@(and (listp geiser-guile-binary) (cdr geiser-guile-binary))
140 ,@q-flags "-L" ,(expand-file-name "guile/" geiser-scheme-dir)
141 ,@(apply 'append (mapcar (lambda (p) (list "-L" p))
142 geiser-guile-load-path))
143 ,@(and init-file (file-readable-p init-file) (list "-l" init-file)))))
145 ;;(defconst geiser-guile--prompt-regexp "^[^() \n]+@([^)]*?)> ")
146 (defconst geiser-guile--prompt-regexp "[^@()]+@([^)]*?)> ")
147 (defconst geiser-guile--debugger-prompt-regexp
148 "[^@()]+@([^)]*?) \\[[0-9]+\\]> ")
151 ;;; Evaluation support:
152 (defsubst geiser-guile--linearize-args (args)
153 (mapconcat 'identity args " "))
155 (defun geiser-guile--geiser-procedure (proc &rest args)
156 (case proc
157 ((eval compile) (format ",geiser-eval %s %s%s"
158 (or (car args) "#f")
159 (geiser-guile--linearize-args (cdr args))
160 (if (cddr args) "" " ()")))
161 ((load-file compile-file) (format ",geiser-load-file %s" (car args)))
162 ((no-values) ",geiser-no-values")
163 (t (format "ge:%s (%s)" proc (geiser-guile--linearize-args args)))))
165 (defconst geiser-guile--module-re
166 "(define-module +\\(([^)]+)\\)")
168 (defconst geiser-guile--library-re
169 "(library +\\(([^)]+)\\)")
171 (defun geiser-guile--get-module (&optional module)
172 (cond ((null module)
173 (save-excursion
174 (geiser-syntax--pop-to-top)
175 (if (or (re-search-backward geiser-guile--module-re nil t)
176 (looking-at geiser-guile--library-re)
177 (re-search-forward geiser-guile--module-re nil t))
178 (geiser-guile--get-module (match-string-no-properties 1))
179 :f)))
180 ((listp module) module)
181 ((stringp module)
182 (condition-case nil
183 (car (geiser-syntax--read-from-string module))
184 (error :f)))
185 (t :f)))
187 (defun geiser-guile--module-cmd (module fmt &optional def)
188 (when module
189 (let* ((module (geiser-guile--get-module module))
190 (module (cond ((or (null module) (eq module :f)) def)
191 (t (format "%s" module)))))
192 (and module (format fmt module)))))
194 (defun geiser-guile--import-command (module)
195 (geiser-guile--module-cmd module ",use %s"))
197 (defun geiser-guile--enter-command (module)
198 (geiser-guile--module-cmd module ",m %s" "(guile-user)"))
201 (defun geiser-guile--exit-command () ",q")
203 (defun geiser-guile--symbol-begin (module)
204 (if module
205 (max (save-excursion (beginning-of-line) (point))
206 (save-excursion (skip-syntax-backward "^(>") (1- (point))))
207 (save-excursion (skip-syntax-backward "^-()>") (point))))
210 ;;; Error display
212 (defun geiser-guile--enter-debugger ()
213 (let ((bt-cmd (format ",geiser-newline\n,error-message\n,%s\n"
214 (if geiser-guile-debug-show-bt-p "bt" "fr"))))
215 (compilation-forget-errors)
216 (goto-char (point-max))
217 (geiser-repl--prepare-send)
218 (comint-send-string nil bt-cmd)
219 (when geiser-guile-show-debug-help-p
220 (message "Debug REPL. Enter ,q to quit, ,h for help."))
221 (when geiser-guile-jump-on-debug-p
222 (accept-process-output (get-buffer-process (current-buffer))
223 0.2 nil t)
224 (ignore-errors (next-error)))))
226 (defun geiser-guile--display-error (module key msg)
227 (newline)
228 (when (stringp msg)
229 (save-excursion (insert msg))
230 (geiser-edit--buttonize-files))
231 (and (not key) msg (not (zerop (length msg)))))
234 ;;; Trying to ascertain whether a buffer is Guile Scheme:
236 (defconst geiser-guile--guess-re
237 (format "\\(%s\\|#! *.+\\(/\\| \\)guile\\( *\\\\\\)?\\)"
238 geiser-guile--module-re))
240 (defun geiser-guile--guess ()
241 (save-excursion
242 (goto-char (point-min))
243 (re-search-forward geiser-guile--guess-re nil t)))
246 ;;; Keywords and syntax
248 (defun geiser-guile--keywords ()
249 (append
250 (when geiser-guile-extra-keywords
251 `((,(format "[[(]%s\\>" (regexp-opt geiser-guile-extra-keywords 1))
252 . 1)))
253 `((,(rx "(" (group "define-once") eow (* space) (? (group (+ word))))
254 (1 font-lock-keyword-face)
255 (2 font-lock-variable-name-face nil t))
256 ("(\\(define-module\\) +(\\([^)]+\\))"
257 (1 font-lock-keyword-face)
258 (2 font-lock-type-face nil t)))))
260 (geiser-syntax--scheme-indent
261 (c-declare 0)
262 (c-lambda 2)
263 (lambda* 1)
264 (pmatch defun)
265 (sigaction 1)
266 (with-fluid* 1)
267 (with-fluids 1)
268 (with-fluids* 1)
269 (with-method 1)
270 (with-mutex 1))
274 ;;; Compilation shell regexps
276 (defconst geiser-guile--path-rx "^In \\([^:\n ]+\\):\n")
278 (defconst geiser-guile--rel-path-rx "^In +\\([^/\n :]+\\):\n")
280 (defvar geiser-guile--file-cache (make-hash-table :test 'equal))
282 (defun geiser-guile--resolve-file (file)
283 (when (and (stringp file)
284 (not (member file '("socket" "stdin" "unknown file"))))
285 (if (file-name-absolute-p file) file
286 (or (gethash file geiser-guile--file-cache)
287 (puthash file
288 (geiser-eval--send/result `(:eval (:ge find-file ,file)))
289 geiser-guile--file-cache)))))
291 (defun geiser-guile--resolve-file-x ()
292 (let ((f (geiser-guile--resolve-file (match-string-no-properties 1))))
293 (and (stringp f) (list f))))
296 ;;; REPL startup
298 (defconst geiser-guile-minimum-version "2.0")
300 (defun geiser-guile--version (binary)
301 (shell-command-to-string (format "%s -c '(display (version))'" binary)))
303 (defun geiser-guile-update-warning-level ()
304 "Update the warning level used by the REPL.
305 The new level is set using the value of `geiser-guile-warning-level'."
306 (interactive)
307 (let ((code `(:eval (:ge set-warnings ',geiser-guile-warning-level)
308 (geiser evaluation))))
309 (geiser-eval--send/result code)))
311 (defun connect-to-guile ()
312 "Start a Guile REPL connected to a remote process.
314 Start the external Guile process with the flag --listen to make
315 it spawn a server thread."
316 (interactive)
317 (geiser-connect 'guile))
319 (defun geiser-guile--set-geiser-load-path ()
320 (let* ((path (expand-file-name "guile/" geiser-scheme-dir))
321 (witness "geiser/emacs.scm")
322 (code `(begin (if (not (%search-load-path ,witness))
323 (set! %load-path (cons ,path %load-path)))
324 'done)))
325 (geiser-eval--send/wait code)))
327 (defun geiser-guile--startup (remote)
328 (set (make-local-variable 'compilation-error-regexp-alist)
329 `((,geiser-guile--path-rx geiser-guile--resolve-file-x)
330 ("^ +\\([0-9]+\\):\\([0-9]+\\)" nil 1 2)))
331 (compilation-setup t)
332 (font-lock-add-keywords nil `((,geiser-guile--path-rx
333 1 compilation-error-face)))
334 (let ((geiser-log-verbose-p t))
335 (when remote (geiser-guile--set-geiser-load-path))
336 (geiser-eval--send/wait ",use (geiser emacs)\n'done")
337 (dolist (dir geiser-guile-load-path)
338 (let ((dir (expand-file-name dir)))
339 (geiser-eval--send/wait `(:eval (:ge add-to-load-path ,dir)))))
340 (geiser-guile-update-warning-level)))
343 ;;; Manual lookup
345 (defun geiser-guile--info-spec (&optional nodes)
346 (let* ((nrx "^[ ]+-+ [^:]+:[ ]*")
347 (drx "\\b")
348 (res (when (Info-find-file "r5rs" t)
349 `(("(r5rs)Index" nil ,nrx ,drx)))))
350 (dolist (node (or nodes geiser-guile-manual-lookup-nodes) res)
351 (when (Info-find-file node t)
352 (mapc (lambda (idx)
353 (add-to-list 'res
354 (list (format "(%s)%s" node idx) nil nrx drx)))
355 '("Variable Index" "Procedure Index" "R5RS Index"))))))
358 (info-lookup-add-help :topic 'symbol :mode 'geiser-guile-mode
359 :ignore-case nil
360 :regexp "[^()`',\" \n]+"
361 :doc-spec (geiser-guile--info-spec))
363 (defun guile--manual-look-up (id mod)
364 (let ((info-lookup-other-window-flag
365 geiser-guile-manual-lookup-other-window-p))
366 (info-lookup-symbol id 'geiser-guile-mode))
367 (when geiser-guile-manual-lookup-other-window-p
368 (switch-to-buffer-other-window "*info*"))
369 (search-forward (format "%s" id) nil t))
373 ;;; Implementation definition:
375 (define-geiser-implementation guile
376 (binary geiser-guile--binary)
377 (arglist geiser-guile--parameters)
378 (version-command geiser-guile--version)
379 (minimum-version geiser-guile-minimum-version)
380 (repl-startup geiser-guile--startup)
381 (prompt-regexp geiser-guile--prompt-regexp)
382 (debugger-prompt-regexp geiser-guile--debugger-prompt-regexp)
383 (enter-debugger geiser-guile--enter-debugger)
384 (marshall-procedure geiser-guile--geiser-procedure)
385 (find-module geiser-guile--get-module)
386 (enter-command geiser-guile--enter-command)
387 (exit-command geiser-guile--exit-command)
388 (import-command geiser-guile--import-command)
389 (find-symbol-begin geiser-guile--symbol-begin)
390 (display-error geiser-guile--display-error)
391 (external-help guile--manual-look-up)
392 (check-buffer geiser-guile--guess)
393 (keywords geiser-guile--keywords)
394 (case-sensitive geiser-guile-case-sensitive-p))
396 (geiser-impl--add-to-alist 'regexp "\\.scm$" 'guile t)
399 (provide 'geiser-guile)