(gds-display-results): Add another binding for
[bpt/guile.git] / emacs / gds-scheme.el
CommitLineData
731bcf73
NJ
1;;; gds-scheme.el -- GDS function for Scheme mode buffers
2
3;;;; Copyright (C) 2005 Neil Jerram
4;;;;
5;;;; This library is free software; you can redistribute it and/or
6;;;; modify it under the terms of the GNU Lesser General Public
7;;;; License as published by the Free Software Foundation; either
8;;;; version 2.1 of the License, or (at your option) any later
9;;;; version.
10;;;;
11;;;; This library is distributed in the hope that it will be useful,
12;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14;;;; Lesser General Public License for more details.
15;;;;
16;;;; You should have received a copy of the GNU Lesser General Public
17;;;; License along with this library; if not, write to the Free
18;;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19;;;; 02111-1307 USA
20
21(require 'comint)
22(require 'scheme)
23(require 'derived)
24(require 'pp)
25
26;;;; Maintaining an association between a Guile client process and a
27;;;; set of Scheme mode buffers.
28
29(defcustom gds-auto-create-utility-client t
30 "Whether to automatically create a utility Guile client, and
31associate the current buffer with it, if there are no existing Guile
32clients available to GDS when the user does something that requires a
33running Guile client."
34 :type 'boolean
35 :group 'gds)
36
37(defcustom gds-auto-associate-single-client t
38 "Whether to automatically associate the current buffer with an
39existing Guile client, if there is only only client known to GDS when
40the user does something that requires a running Guile client, and the
41current buffer is not already associated with a Guile client."
42 :type 'boolean
43 :group 'gds)
44
45(defcustom gds-auto-associate-last-client t
46 "Whether to automatically associate the current buffer with the
47Guile client that most recently caused that buffer to be displayed,
48when the user does something that requires a running Guile client and
49the current buffer is not already associated with a Guile client."
50 :type 'boolean
51 :group 'gds)
52
53(defvar gds-last-touched-by nil
54 "For each Scheme mode buffer, this records the GDS client that most
55recently `touched' that buffer in the sense of using it to display
56source code, for example for the source code relevant to a debugger
57stack frame.")
58(make-variable-buffer-local 'gds-last-touched-by)
59
60(defun gds-auto-associate-buffer ()
61 "Automatically associate the current buffer with a Guile client, if
62possible."
63 (let* ((num-clients (length gds-client-info))
64 (client
65 (or
66 ;; If there are no clients yet, and
67 ;; `gds-auto-create-utility-client' allows us to create one
68 ;; automatically, do that.
69 (and (= num-clients 0)
70 gds-auto-create-utility-client
71 (gds-start-utility-guile))
72 ;; Otherwise, if there is a single existing client, and
73 ;; `gds-auto-associate-single-client' allows us to use it
74 ;; for automatic association, do that.
75 (and (= num-clients 1)
76 gds-auto-associate-single-client
77 (caar gds-client-info))
78 ;; Otherwise, if the current buffer was displayed because
79 ;; of a Guile client trapping somewhere in its code, and
80 ;; `gds-auto-associate-last-client' allows us to associate
81 ;; with that client, do so.
82 (and gds-auto-associate-last-client
83 gds-last-touched-by))))
84 (if client
85 (gds-associate-buffer client))))
86
87(defun gds-associate-buffer (client)
88 "Associate the current buffer with the Guile process CLIENT.
89This means that operations in this buffer that require a running Guile
90process - such as evaluation, help, completion and setting traps -
91will be sent to the Guile process whose name or connection number is
92CLIENT."
93 (interactive (list (gds-choose-client)))
94 ;; If this buffer is already associated, dissociate from its
95 ;; existing client first.
96 (if gds-client (gds-dissociate-buffer))
97 ;; Store the client number in the buffer-local variable gds-client.
98 (setq gds-client client)
99 ;; Add this buffer to the list of buffers associated with the
100 ;; client.
101 (gds-client-put client 'associated-buffers
102 (cons (current-buffer)
103 (gds-client-get client 'associated-buffers))))
104
105(defun gds-dissociate-buffer ()
106 "Dissociate the current buffer from any specific Guile process."
107 (interactive)
108 (if gds-client
109 (progn
110 ;; Remove this buffer from the list of buffers associated with
111 ;; the current client.
112 (gds-client-put gds-client 'associated-buffers
113 (delq (current-buffer)
114 (gds-client-get gds-client 'associated-buffers)))
115 ;; Reset the buffer-local variable gds-client.
116 (setq gds-client nil)
117 ;; Clear any process status indication from the modeline.
118 (setq mode-line-process nil)
119 (force-mode-line-update))))
120
121(defun gds-show-client-status (client status-string)
122 "Show a client's status in the modeline of all its associated
123buffers."
124 (let ((buffers (gds-client-get client 'associated-buffers)))
125 (while buffers
126 (if (buffer-live-p (car buffers))
127 (with-current-buffer (car buffers)
128 (setq mode-line-process status-string)
129 (force-mode-line-update)))
130 (setq buffers (cdr buffers)))))
131
132(defcustom gds-running-text ":running"
133 "*Mode line text used to show that a Guile process is \"running\".
134\"Running\" means that the process cannot currently accept any input
135from the GDS frontend in Emacs, because all of its threads are busy
136running code that GDS cannot easily interrupt."
137 :type 'string
138 :group 'gds)
139
140(defcustom gds-ready-text ":ready"
141 "*Mode line text used to show that a Guile process is \"ready\".
142\"Ready\" means that the process is ready to interact with the GDS
143frontend in Emacs, because at least one of its threads is waiting for
144GDS input."
145 :type 'string
146 :group 'gds)
147
148(defcustom gds-debug-text ":debug"
149 "*Mode line text used to show that a Guile process is \"debugging\".
150\"Debugging\" means that the process is using the GDS frontend in
151Emacs to display an error or trap so that the user can debug it."
152 :type 'string
153 :group 'gds)
154
155(defun gds-choose-client ()
156 "Ask the user to choose a GDS client process from a list."
157 (let ((table '())
158 (default nil))
159 ;; Prepare a table containing all current clients.
160 (mapcar (lambda (client-info)
19b16cd0 161 (setq table (cons (cons (cadr (memq 'name client-info))
731bcf73
NJ
162 (car client-info))
163 table)))
164 gds-client-info)
165 ;; Add an entry to allow the user to ask for a new process.
166 (setq table (cons (cons "Start a new Guile process" nil) table))
167 ;; Work out a good default. If the buffer has a good value in
168 ;; gds-last-touched-by, we use that; otherwise default to starting
169 ;; a new process.
170 (setq default (or (and gds-last-touched-by
171 (gds-client-get gds-last-touched-by 'name))
172 (caar table)))
173 ;; Read using this table.
174 (let* ((name (completing-read "Choose a Guile process: "
175 table
176 nil
177 t ; REQUIRE-MATCH
178 nil ; INITIAL-INPUT
179 nil ; HIST
180 default))
181 ;; Convert name to a client number.
182 (client (cdr (assoc name table))))
183 ;; If the user asked to start a new Guile process, do that now.
184 (or client (setq client (gds-start-utility-guile)))
185 ;; Return the chosen client ID.
186 client)))
187
188(defvar gds-last-utility-number 0
189 "Number of the last started Guile utility process.")
190
191(defun gds-start-utility-guile ()
192 "Start a new utility Guile process."
193 (setq gds-last-utility-number (+ gds-last-utility-number 1))
194 (let* ((procname (format "gds-util[%d]" gds-last-utility-number))
195 (code (format "(begin
196 %s
fce4b99e 197 (use-modules (ice-9 gds-client))
731bcf73
NJ
198 (run-utility))"
199 (if gds-scheme-directory
200 (concat "(set! %load-path (cons "
201 (format "%S" gds-scheme-directory)
202 " %load-path))")
203 "")))
204 (proc (start-process procname
205 (get-buffer-create procname)
206 gds-guile-program
207 "-q"
208 "--debug"
209 "-c"
210 code))
211 (client nil))
212 ;; Note that this process can be killed automatically on Emacs
213 ;; exit.
214 (process-kill-without-query proc)
215 ;; Set up a process filter to catch the new client's number.
216 (set-process-filter proc
217 (lambda (proc string)
218 (setq client (string-to-number string))
219 (if (process-buffer proc)
220 (with-current-buffer (process-buffer proc)
221 (insert string)))))
222 ;; Accept output from the new process until we have its number.
223 (while (not client)
224 (accept-process-output proc))
225 ;; Return the new process's client number.
226 client))
227
228;;;; Evaluating code.
229
230;; The following commands send code for evaluation through the GDS TCP
231;; connection, receive the result and any output generated through the
232;; same connection, and display the result and output to the user.
233;;
234;; For each buffer where evaluations can be requested, GDS uses the
235;; buffer-local variable `gds-client' to track which GDS client
236;; program should receive and handle that buffer's evaluations.
237
238(defun gds-module-name (start end)
239 "Determine and return the name of the module that governs the
240specified region. The module name is returned as a list of symbols."
241 (interactive "r") ; why not?
242 (save-excursion
243 (goto-char start)
244 (let (module-name)
245 (while (and (not module-name)
246 (beginning-of-defun-raw 1))
247 (if (looking-at "(define-module ")
248 (setq module-name
249 (progn
250 (goto-char (match-end 0))
251 (read (current-buffer))))))
252 module-name)))
253
254(defcustom gds-emacs-buffer-port-name-prefix "Emacs buffer: "
255 "Prefix used when telling Guile the name of the port from which a
256chunk of Scheme code (to be evaluated) comes. GDS uses this prefix,
257followed by the buffer name, in two cases: when the buffer concerned
258is not associated with a file, or if the buffer has been modified
259since last saving to its file. In the case where the buffer is
260identical to a saved file, GDS uses the file name as the port name."
261 :type '(string)
262 :group 'gds)
263
264(defun gds-port-name (start end)
265 "Return port name for the specified region of the current buffer.
266The name will be used by Guile as the port name when evaluating that
267region's code."
268 (or (and (not (buffer-modified-p))
269 buffer-file-name)
270 (concat gds-emacs-buffer-port-name-prefix (buffer-name))))
271
272(defun gds-line-and-column (pos)
273 "Return 0-based line and column number at POS."
274 (let (line column)
275 (save-excursion
276 (goto-char pos)
277 (setq column (current-column))
278 (beginning-of-line)
279 (setq line (count-lines (point-min) (point))))
280 (cons line column)))
281
282(defun gds-eval-region (start end)
283 "Evaluate the current region."
284 (interactive "r")
285 (or gds-client
286 (gds-auto-associate-buffer)
287 (call-interactively 'gds-associate-buffer))
288 (let ((module (gds-module-name start end))
289 (port-name (gds-port-name start end))
290 (lc (gds-line-and-column start)))
291 (let ((code (buffer-substring-no-properties start end)))
292 (gds-send (format "eval (region . %S) %s %S %d %d %S"
293 (gds-abbreviated code)
294 (if module (prin1-to-string module) "#f")
295 port-name (car lc) (cdr lc)
296 code)
297 gds-client))))
298
299(defun gds-eval-expression (expr &optional correlator)
300 "Evaluate the supplied EXPR (a string)."
301 (interactive "sEvaluate expression: \nP")
302 (or gds-client
303 (gds-auto-associate-buffer)
304 (call-interactively 'gds-associate-buffer))
305 (set-text-properties 0 (length expr) nil expr)
306 (gds-send (format "eval (%S . %S) #f \"Emacs expression\" 0 0 %S"
307 (or correlator 'expression)
308 (gds-abbreviated expr)
309 expr)
310 gds-client))
311
312(defconst gds-abbreviated-length 35)
313
314(defun gds-abbreviated (code)
315 (let ((nlpos (string-match (regexp-quote "\n") code)))
316 (while nlpos
317 (setq code
318 (if (= nlpos (- (length code) 1))
319 (substring code 0 nlpos)
320 (concat (substring code 0 nlpos)
321 "\\n"
322 (substring code (+ nlpos 1)))))
323 (setq nlpos (string-match (regexp-quote "\n") code))))
324 (if (> (length code) gds-abbreviated-length)
325 (concat (substring code 0 (- gds-abbreviated-length 3)) "...")
326 code))
327
328(defun gds-eval-defun ()
329 "Evaluate the defun (top-level form) at point."
330 (interactive)
331 (save-excursion
332 (end-of-defun)
333 (let ((end (point)))
334 (beginning-of-defun)
335 (gds-eval-region (point) end))))
336
337(defun gds-eval-last-sexp ()
338 "Evaluate the sexp before point."
339 (interactive)
340 (gds-eval-region (save-excursion (backward-sexp) (point)) (point)))
341
342;;;; Help.
343
344;; Help is implemented as a special case of evaluation, identified by
345;; the evaluation correlator 'help.
346
347(defun gds-help-symbol (sym)
348 "Get help for SYM (a Scheme symbol)."
349 (interactive
350 (let ((sym (thing-at-point 'symbol))
351 (enable-recursive-minibuffers t)
352 val)
353 (setq val (read-from-minibuffer
354 (if sym
355 (format "Describe Guile symbol (default %s): " sym)
356 "Describe Guile symbol: ")))
357 (list (if (zerop (length val)) sym val))))
358 (gds-eval-expression (format "(help %s)" sym) 'help))
359
360(defun gds-apropos (regex)
361 "List Guile symbols matching REGEX."
362 (interactive
363 (let ((sym (thing-at-point 'symbol))
364 (enable-recursive-minibuffers t)
365 val)
366 (setq val (read-from-minibuffer
367 (if sym
368 (format "Guile apropos (regexp, default \"%s\"): " sym)
369 "Guile apropos (regexp): ")))
370 (list (if (zerop (length val)) sym val))))
371 (set-text-properties 0 (length regex) nil regex)
372 (gds-eval-expression (format "(apropos %S)" regex) 'apropos))
373
374;;;; Displaying results of help and eval.
375
376(defun gds-display-results (client correlator stack-available results)
377 (let* ((helpp+bufname (cond ((eq (car correlator) 'help)
378 '(t . "*Guile Help*"))
379 ((eq (car correlator) 'apropos)
380 '(t . "*Guile Apropos*"))
381 (t
382 '(nil . "*Guile Evaluation*"))))
383 (helpp (car helpp+bufname)))
384 (let ((buf (get-buffer-create (cdr helpp+bufname))))
385 (save-excursion
386 (set-buffer buf)
387 (gds-dissociate-buffer)
388 (erase-buffer)
389 (scheme-mode)
390 (insert (cdr correlator) "\n\n")
391 (while results
392 (insert (car results))
393 (or (bolp) (insert "\\\n"))
394 (if helpp
395 nil
396 (if (cadr results)
397 (mapcar (function (lambda (value)
398 (insert " => " value "\n")))
399 (cadr results))
400 (insert " => no (or unspecified) value\n"))
401 (insert "\n"))
402 (setq results (cddr results)))
403 (if stack-available
404 (let ((beg (point))
405 (map (make-sparse-keymap)))
406 (define-key map [mouse-1] 'gds-show-last-stack)
6d6a3fe2 407 (define-key map "\C-m" 'gds-show-last-stack)
731bcf73
NJ
408 (insert "[click here to show error stack]")
409 (add-text-properties beg (point)
410 (list 'keymap map
411 'mouse-face 'highlight))
412 (insert "\n")))
413 (goto-char (point-min))
414 (gds-associate-buffer client))
415 (pop-to-buffer buf)
416 (run-hooks 'temp-buffer-show-hook)
417 (other-window 1))))
418
419(defun gds-show-last-stack ()
420 "Show stack of the most recent error."
421 (interactive)
422 (or gds-client
423 (gds-auto-associate-buffer)
424 (call-interactively 'gds-associate-buffer))
425 (gds-send "debug-lazy-trap-context" gds-client))
426
427;;;; Completion.
428
429(defvar gds-completion-results nil)
430
431(defun gds-complete-symbol ()
432 "Complete the Guile symbol before point. Returns `t' if anything
433interesting happened, `nil' if not."
434 (interactive)
435 (or gds-client
436 (gds-auto-associate-buffer)
437 (call-interactively 'gds-associate-buffer))
438 (let* ((chars (- (point) (save-excursion
439 (while (let ((syntax (char-syntax (char-before (point)))))
440 (or (eq syntax ?w) (eq syntax ?_)))
441 (forward-char -1))
442 (point)))))
443 (if (zerop chars)
444 nil
445 (setq gds-completion-results nil)
446 (gds-send (format "complete %s"
447 (prin1-to-string
448 (buffer-substring-no-properties (- (point) chars)
449 (point))))
450 gds-client)
451 (while (null gds-completion-results)
452 (accept-process-output gds-debug-server 0 200))
453 (cond ((eq gds-completion-results 'error)
454 (error "Internal error - please report the contents of the *Guile Evaluation* window"))
455 ((eq gds-completion-results t)
456 nil)
457 ((stringp gds-completion-results)
458 (if (<= (length gds-completion-results) chars)
459 nil
460 (insert (substring gds-completion-results chars))
461 (message "Sole completion")
462 t))
463 ((= (length gds-completion-results) 1)
464 (if (<= (length (car gds-completion-results)) chars)
465 nil
466 (insert (substring (car gds-completion-results) chars))
467 t))
468 (t
469 (with-output-to-temp-buffer "*Completions*"
470 (display-completion-list gds-completion-results))
471 t)))))
472
473;;;; Breakpoints.
474
475(defvar gds-bufferless-breakpoints nil
476 "The list of breakpoints that are not yet associated with a
477particular buffer. Each element looks like (BPDEF BPNUM) where BPDEF
478is the breakpoint definition and BPNUM the breakpoint's unique
479GDS-assigned number. A breakpoint definition BPDEF is a list of the
480form (BEHAVIOUR TYPE FILENAME TYPE-ARGS...), where BEHAVIOUR is 'debug
481or 'trace, TYPE is 'in or 'at, FILENAME is the full name of the file
482where the breakpoint is (or will be) set, and TYPE-ARGS is:
483
484- the name of the procedure to break in, if TYPE is 'in
485
486- the line number and column number to break at, if TYPE is 'at.
487
488If persistent breakpoints are enabled (by configuring
489gds-breakpoints-file-name), this list is initialized when GDS is
490loaded by reading gds-breakpoints-file-name.")
491
492(defsubst gds-bpdef:behaviour (bpdef)
493 (nth 0 bpdef))
494
495(defsubst gds-bpdef:type (bpdef)
496 (nth 1 bpdef))
497
498(defsubst gds-bpdef:file-name (bpdef)
499 (nth 2 bpdef))
500
501(defsubst gds-bpdef:proc-name (bpdef)
502 (nth 3 bpdef))
503
504(defsubst gds-bpdef:lc (bpdef)
505 (nth 3 bpdef))
506
507(defvar gds-breakpoint-number 0
508 "The last assigned breakpoint number. GDS increments this whenever
509it creates a new breakpoint.")
510
511(defvar gds-breakpoint-buffers nil
512 "The list of buffers that contain GDS breakpoints. When Emacs
513visits a Scheme file, GDS checks to see if any of the breakpoints in
514the bufferless list can be assigned to that file's buffer. If they
515can, they are removed from the bufferless list and become breakpoint
516overlays in that buffer. To retain the ability to enumerate all
517breakpoints, therefore, we keep a list of all such buffers.")
518
519(defvar gds-breakpoint-programming nil
520 "Information about how each breakpoint is actually programmed in the
521Guile clients that GDS is connected to. This is an alist of the form
522\((BPNUM (CLIENT . TRAPLIST) ...) ...), where BPNUM is the breakpoint
523number, CLIENT is the number of a GDS client, and TRAPLIST is the list
524of traps that that client has created for the breakpoint concerned (in
525an arbitrary but Emacs-readable format).")
526
527(defvar gds-breakpoint-cache nil
528 "Buffer-local cache of breakpoints in a particular buffer. When a
529breakpoint is represented as an overlay is a Scheme mode buffer, we
530need to be able to detect when the user has caused that overlay to
531evaporate by deleting a region of code that included it. We do this
532detection when the buffer is next saved, by comparing the current set
533of overlays with this cache. The cache is a list in which each
534element has the form (BPDEF BPNUM), with BPDEF and BPNUM as already
535described. The handling of such breakpoints (which we call \"lost\")
536is controlled by the setting of gds-delete-lost-breakpoints.")
537(make-variable-buffer-local 'gds-breakpoint-cache)
538
539(defface gds-breakpoint-face
540 '((((background dark)) (:background "red"))
541 (t (:background "pink")))
542 "*Face used to highlight the location of a breakpoint."
543 :group 'gds)
544
545(defcustom gds-breakpoints-file-name "~/.gds-breakpoints"
546 "Name of file used to store GDS breakpoints between sessions.
547You can disable breakpoint persistence by setting this to nil."
548 :group 'gds
549 :type '(choice (const :tag "nil" nil) file))
550
551(defcustom gds-delete-lost-breakpoints nil
552 "Whether to delete lost breakpoints.
553
554A non-nil value means that the Guile clients where lost breakpoints
555were programmed will be told immediately to delete their breakpoints.
556\"Immediately\" means when the lost breakpoints are detected, which
557means when the buffer that previously contained them is saved. Thus,
558even if the affected code (which the GDS user has deleted from his/her
559buffer in Emacs) is still in use in the Guile clients, the breakpoints
560that were previously set in that code will no longer take effect.
561
562Nil (which is the default) means that GDS leaves such breakpoints
563active in their Guile clients. This allows those breakpoints to
564continue taking effect until the affected code is no longer used by
565the Guile clients."
566 :group 'gds
567 :type 'boolean)
568
569(defvar gds-bpdefs-cache nil)
570
571(defun gds-read-breakpoints-file ()
572 "Read the persistent breakpoints file, and use its contents to
573initialize GDS's global breakpoint variables."
574 (let ((bpdefs (condition-case nil
575 (with-current-buffer
576 (find-file-noselect gds-breakpoints-file-name)
577 (goto-char (point-min))
578 (read (current-buffer)))
579 (error nil))))
580 ;; Cache the overall value so we don't unnecessarily modify the
581 ;; breakpoints buffer when `gds-write-breakpoints-file' is called.
582 (setq gds-bpdefs-cache bpdefs)
583 ;; Move definitions into the bufferless breakpoint list, assigning
584 ;; breakpoint numbers as we go.
585 (setq gds-bufferless-breakpoints
586 (mapcar (function (lambda (bpdef)
587 (setq gds-breakpoint-number
588 (1+ gds-breakpoint-number))
589 (list bpdef gds-breakpoint-number)))
590 bpdefs))
591 ;; Check each existing Scheme buffer to see if it wants to take
592 ;; ownership of any of these breakpoints.
593 (mapcar (function (lambda (buffer)
594 (with-current-buffer buffer
595 (if (eq (derived-mode-class major-mode) 'scheme-mode)
596 (gds-adopt-breakpoints)))))
597 (buffer-list))))
598
599(defun gds-adopt-breakpoints ()
600 "Take ownership of any of the breakpoints in the bufferless list
601that match the current buffer."
602 (mapcar (function gds-adopt-breakpoint)
603 (copy-sequence gds-bufferless-breakpoints)))
604
605(defun gds-adopt-breakpoint (bpdefnum)
606 "Take ownership of the specified breakpoint if it matches the
607current buffer."
608 (let ((bpdef (car bpdefnum))
609 (bpnum (cadr bpdefnum)))
610 ;; Check if breakpoint's file name matches. If it does, try to
611 ;; convert the breakpoint definition to a breakpoint overlay in
612 ;; the current buffer.
613 (if (and (string-equal (gds-bpdef:file-name bpdef) buffer-file-name)
614 (gds-make-breakpoint-overlay bpdef bpnum))
615 ;; That all succeeded, so this breakpoint is no longer
616 ;; bufferless.
617 (setq gds-bufferless-breakpoints
618 (delq bpdefnum gds-bufferless-breakpoints)))))
619
620(defun gds-make-breakpoint-overlay (bpdef &optional bpnum)
621 ;; If no explicit number given, assign the next available breakpoint
622 ;; number.
623 (or bpnum
624 (setq gds-breakpoint-number (+ gds-breakpoint-number 1)
625 bpnum gds-breakpoint-number))
626 ;; First decide where the overlay should be, and create it there.
627 (let ((o (cond ((eq (gds-bpdef:type bpdef) 'at)
628 (save-excursion
629 (goto-line (+ (car (gds-bpdef:lc bpdef)) 1))
630 (move-to-column (cdr (gds-bpdef:lc bpdef)))
631 (make-overlay (point) (1+ (point)))))
632 ((eq (gds-bpdef:type bpdef) 'in)
633 (save-excursion
634 (goto-char (point-min))
635 (and (re-search-forward (concat "^(define +(?\\("
636 (regexp-quote
637 (gds-bpdef:proc-name
638 bpdef))
639 "\\>\\)")
640 nil t)
641 (make-overlay (match-beginning 1) (match-end 1)))))
642 (t
643 (error "Bad breakpoint type")))))
644 ;; If that succeeded, initialize the overlay's properties.
645 (if o
646 (progn
647 (overlay-put o 'evaporate t)
648 (overlay-put o 'face 'gds-breakpoint-face)
649 (overlay-put o 'gds-breakpoint-number bpnum)
650 (overlay-put o 'gds-breakpoint-definition bpdef)
651 (overlay-put o 'help-echo (format "Breakpoint %d: %S" bpnum bpdef))
652 (overlay-put o 'priority 1000)
653 ;; Make sure that the current buffer is included in
654 ;; `gds-breakpoint-buffers'.
655 (or (memq (current-buffer) gds-breakpoint-buffers)
656 (setq gds-breakpoint-buffers
657 (cons (current-buffer) gds-breakpoint-buffers)))
658 ;; Add the new breakpoint to this buffer's cache.
659 (setq gds-breakpoint-cache
660 (cons (list bpdef bpnum) gds-breakpoint-cache))
661 ;; If this buffer is associated with a client, tell the
662 ;; client about the new breakpoint.
663 (if gds-client (gds-send-breakpoint-to-client bpnum bpdef))))
664 ;; Return the overlay, or nil if we weren't able to convert the
665 ;; breakpoint definition.
666 o))
667
668(defun gds-send-breakpoint-to-client (bpnum bpdef)
669 "Send specified breakpoint to this buffer's Guile client."
670 (gds-send (format "set-breakpoint %d %S" bpnum bpdef) gds-client))
671
672(add-hook 'scheme-mode-hook (function gds-adopt-breakpoints))
673
674(defcustom gds-default-breakpoint-type 'debug
675 "The type of breakpoint set by `C-x SPC'."
676 :group 'gds
677 :type '(choice (const :tag "debug" debug) (const :tag "trace" trace)))
678
679(defun gds-set-breakpoint ()
680 "Create a new GDS breakpoint at point."
681 (interactive)
682 ;; Set up beg and end according to whether the mark is active.
683 (if mark-active
684 ;; Set new breakpoints on all opening parentheses in the region.
685 (let ((beg (region-beginning))
686 (end (region-end)))
687 (save-excursion
688 (goto-char beg)
689 (beginning-of-defun)
690 (let ((defun-start (point)))
691 (goto-char beg)
692 (while (search-forward "(" end t)
693 (let ((state (parse-partial-sexp defun-start (point)))
694 (pos (- (point) 1)))
695 (or (nth 3 state)
696 (nth 4 state)
697 (gds-breakpoint-overlays-at pos)
698 (gds-make-breakpoint-overlay (list gds-default-breakpoint-type
699 'at
700 buffer-file-name
701 (gds-line-and-column
702 pos)))))))))
703 ;; Set a new breakpoint on the defun at point.
704 (let ((region (gds-defun-name-region)))
705 ;; Complain if there is no defun at point.
706 (or region
707 (error "Point is not in a procedure definition"))
708 ;; Don't create another breakpoint if there is already one here.
709 (if (gds-breakpoint-overlays-at (car region))
710 (error "There is already a breakpoint here"))
711 ;; Create and return the new breakpoint overlay.
712 (gds-make-breakpoint-overlay (list gds-default-breakpoint-type
713 'in
714 buffer-file-name
715 (buffer-substring-no-properties
716 (car region)
717 (cdr region))))))
718 ;; Update the persistent breakpoints file.
719 (gds-write-breakpoints-file))
720
721(defun gds-defun-name-region ()
722 "If point is in a defun, return the beginning and end positions of
723the identifier being defined."
724 (save-excursion
725 (let ((p (point)))
726 (beginning-of-defun)
727 ;; Check that we are looking at some kind of procedure
728 ;; definition.
729 (and (looking-at "(define +(?\\(\\(\\s_\\|\\w\\)+\\)")
730 (let ((beg (match-beginning 1))
731 (end (match-end 1)))
732 (end-of-defun)
733 ;; Check here that we have reached past the original point
734 ;; position.
735 (and (>= (point) p)
736 (cons beg end)))))))
737
738(defun gds-breakpoint-overlays-at (pos)
739 "Return a list of GDS breakpoint overlays at the specified position."
740 (let ((os (overlays-at pos))
741 (breakpoint-os nil))
742 ;; Of the overlays at POS, select all those that have a
743 ;; gds-breakpoint-definition property.
744 (while os
745 (if (overlay-get (car os) 'gds-breakpoint-definition)
746 (setq breakpoint-os (cons (car os) breakpoint-os)))
747 (setq os (cdr os)))
748 breakpoint-os))
749
750(defun gds-write-breakpoints-file ()
751 "Write the persistent breakpoints file, if configured."
752 (if gds-breakpoints-file-name
753 (let ((bpdefs (gds-fold-breakpoints (function (lambda (bpnum bpdef init)
754 (cons bpdef init)))
755 t)))
756 (or (equal bpdefs gds-bpdefs-cache)
757 (with-current-buffer (find-file-noselect gds-breakpoints-file-name)
758 (erase-buffer)
759 (pp (reverse bpdefs) (current-buffer))
760 (setq gds-bpdefs-cache bpdefs)
761 (let ((auto-fill-function normal-auto-fill-function))
762 (newline)))))))
763
764(defun gds-fold-breakpoints (fn &optional foldp init)
765 ;; Run through bufferless breakpoints first.
766 (let ((bbs gds-bufferless-breakpoints))
767 (while bbs
768 (let ((bpnum (cadr (car bbs)))
769 (bpdef (caar bbs)))
770 (if foldp
771 (setq init (funcall fn bpnum bpdef init))
772 (funcall fn bpnum bpdef)))
773 (setq bbs (cdr bbs))))
774 ;; Now run through breakpoint buffers.
775 (let ((outbuf (current-buffer))
776 (bpbufs gds-breakpoint-buffers))
777 (while bpbufs
778 (let ((buf (car bpbufs)))
779 (if (buffer-live-p buf)
780 (with-current-buffer buf
781 (save-restriction
782 (widen)
783 (let ((os (overlays-in (point-min) (point-max))))
784 (while os
785 (let ((bpnum (overlay-get (car os)
786 'gds-breakpoint-number))
787 (bpdef (overlay-get (car os)
788 'gds-breakpoint-definition)))
789 (if bpdef
790 (with-current-buffer outbuf
791 (if foldp
792 (setq init (funcall fn bpnum bpdef init))
793 (funcall fn bpnum bpdef)))))
794 (setq os (cdr os))))))))
795 (setq bpbufs (cdr bpbufs))))
796 init)
797
798(defun gds-delete-breakpoints ()
799 "Delete GDS breakpoints in the region or at point."
800 (interactive)
801 (if mark-active
802 ;; Delete all breakpoints in the region.
803 (let ((os (overlays-in (region-beginning) (region-end))))
804 (while os
805 (if (overlay-get (car os) 'gds-breakpoint-definition)
806 (gds-delete-breakpoint (car os)))
807 (setq os (cdr os))))
808 ;; Delete the breakpoint "at point".
809 (call-interactively (function gds-delete-breakpoint))))
810
811(defun gds-delete-breakpoint (o)
812 (interactive (list (or (gds-breakpoint-at-point)
813 (error "There is no breakpoint here"))))
814 (let ((bpdef (overlay-get o 'gds-breakpoint-definition))
815 (bpnum (overlay-get o 'gds-breakpoint-number)))
816 ;; If this buffer is associated with a client, tell the client
817 ;; that the breakpoint has been deleted.
818 (if (and bpnum gds-client)
819 (gds-send (format "delete-breakpoint %d" bpnum) gds-client))
820 ;; Remove this breakpoint from the cache also, so it isn't later
821 ;; detected as having been "lost".
822 (setq gds-breakpoint-cache
823 (delq (assq bpdef gds-breakpoint-cache) gds-breakpoint-cache)))
824 ;; Remove the overlay from its buffer.
825 (delete-overlay o)
826 ;; If that was the last breakpoint in this buffer, remove this
827 ;; buffer from gds-breakpoint-buffers.
828 (or gds-breakpoint-cache
829 (setq gds-breakpoint-buffers
830 (delq (current-buffer) gds-breakpoint-buffers)))
831 ;; Update the persistent breakpoints file.
832 (gds-write-breakpoints-file))
833
834(defun gds-breakpoint-at-point ()
835 "Find and return the overlay for a breakpoint `at' the current
836cursor position. This is intended for use in other functions'
837interactive forms, so it intentionally uses the minibuffer in some
838situations."
839 (let* ((region (gds-defun-name-region))
840 (os (gds-union (gds-breakpoint-overlays-at (point))
841 (and region
842 (gds-breakpoint-overlays-at (car region))))))
843 ;; Switch depending whether we found 0, 1 or more overlays.
844 (cond ((null os)
845 ;; None found: return nil.
846 nil)
847 ((= (length os) 1)
848 ;; One found: return it.
849 (car os))
850 (t
851 ;; More than 1 found: ask the user to choose.
852 (gds-user-selected-breakpoint os)))))
853
854(defun gds-union (first second &rest others)
855 (if others
856 (gds-union first (apply 'gds-union second others))
857 (progn
858 (while first
859 (or (memq (car first) second)
860 (setq second (cons (car first) second)))
861 (setq first (cdr first)))
862 second)))
863
864(defun gds-user-selected-breakpoint (os)
865 "Ask the user to choose one of the given list of breakpoints, and
866return the one that they chose."
867 (let ((table (mapcar
868 (lambda (o)
869 (cons (format "%S"
870 (overlay-get o 'gds-breakpoint-definition))
871 o))
872 os)))
873 (cdr (assoc (completing-read "Which breakpoint do you mean? "
874 table nil t)
875 table))))
876
877(defun gds-describe-breakpoints ()
878 "Describe all breakpoints and their programming status."
879 (interactive)
880 (with-current-buffer (get-buffer-create "*GDS Breakpoints*")
881 (erase-buffer)
882 (gds-fold-breakpoints (function gds-describe-breakpoint))
883 (display-buffer (current-buffer))))
884
885(defun gds-describe-breakpoint (bpnum bpdef)
886 (insert (format "Breakpoint %d: %S\n" bpnum bpdef))
887 (let ((bpproglist (cdr (assq bpnum gds-breakpoint-programming))))
888 (mapcar (lambda (clientprog)
889 (let ((client (car clientprog))
890 (traplist (cdr clientprog)))
891 (mapcar (lambda (trap)
892 (insert (format " Client %d: %S\n" client trap)))
893 traplist)))
894 bpproglist)))
895
896(defun gds-after-save-update-breakpoints ()
897 "Function called when a buffer containing breakpoints is saved."
898 (if (eq (derived-mode-class major-mode) 'scheme-mode)
899 (save-restriction
900 (widen)
901 ;; Get the current breakpoint overlays.
902 (let ((os (overlays-in (point-min) (point-max)))
903 (cache (copy-sequence gds-breakpoint-cache)))
904 ;; Identify any overlays that have disappeared by comparing
905 ;; against this buffer's definition cache, and
906 ;; simultaneously rebuild the cache to reflect the current
907 ;; set of overlays.
908 (setq gds-breakpoint-cache nil)
909 (while os
910 (let* ((o (car os))
911 (bpdef (overlay-get o 'gds-breakpoint-definition))
912 (bpnum (overlay-get o 'gds-breakpoint-number)))
913 (if bpdef
914 ;; o and bpdef describe a current breakpoint.
915 (progn
916 ;; Remove this breakpoint from the old cache list,
917 ;; so we don't think it got lost.
918 (setq cache (delq (assq bpdef cache) cache))
919 ;; Check whether this breakpoint's location has
920 ;; moved. If it has, update the breakpoint
921 ;; definition and the associated client.
922 (let ((lcnow (gds-line-and-column (overlay-start o))))
923 (if (equal lcnow (gds-bpdef:lc bpdef))
924 nil ; Breakpoint hasn't moved.
925 (gds-bpdef:setlc bpdef lcnow)
926 (if gds-client
927 (gds-send-breakpoint-to-client bpnum bpdef))))
928 ;; Add this breakpoint to the new cache list.
929 (setq gds-breakpoint-cache
930 (cons (list bpdef bpnum) gds-breakpoint-cache)))))
931 (setq os (cdr os)))
932 ;; cache now holds the set of lost breakpoints. If we are
933 ;; supposed to explicitly delete these from the associated
934 ;; client, do that now.
935 (if (and gds-delete-lost-breakpoints gds-client)
936 (while cache
937 (gds-send (format "delete-breakpoint %d" (cadr (car cache)))
938 gds-client)
939 (setq cache (cdr cache)))))
940 ;; If this buffer now has no breakpoints, remove it from
941 ;; gds-breakpoint-buffers.
942 (or gds-breakpoint-cache
943 (setq gds-breakpoint-buffers
944 (delq (current-buffer) gds-breakpoint-buffers)))
945 ;; Update the persistent breakpoints file.
946 (gds-write-breakpoints-file))))
947
948(add-hook 'after-save-hook (function gds-after-save-update-breakpoints))
949
950;;;; Dispatcher for non-debug protocol.
951
952(defun gds-nondebug-protocol (client proc args)
953 (cond (;; (eval-results ...) - Results of evaluation.
954 (eq proc 'eval-results)
955 (gds-display-results client (car args) (cadr args) (cddr args))
956 ;; If these results indicate an error, set
957 ;; gds-completion-results to non-nil in case the error arose
958 ;; when trying to do a completion.
959 (if (eq (caar args) 'error)
960 (setq gds-completion-results 'error)))
961
962 (;; (completion-result ...) - Available completions.
963 (eq proc 'completion-result)
964 (setq gds-completion-results (or (car args) t)))
965
966 (;; (breakpoint NUM STATUS) - Breakpoint set.
967 (eq proc 'breakpoint)
968 (let* ((bpnum (car args))
969 (traplist (cdr args))
970 (bpentry (assq bpnum gds-breakpoint-programming)))
971 (message "Breakpoint %d: %s" bpnum traplist)
972 (if bpentry
973 (let ((cliententry (assq client (cdr bpentry))))
974 (if cliententry
975 (setcdr cliententry traplist)
976 (setcdr bpentry
977 (cons (cons client traplist) (cdr bpentry)))))
978 (setq gds-breakpoint-programming
979 (cons (list bpnum (cons client traplist))
980 gds-breakpoint-programming)))))
981
982 (;; (get-breakpoints) - Set all breakpoints.
983 (eq proc 'get-breakpoints)
984 (let ((gds-client client))
985 (gds-fold-breakpoints (function gds-send-breakpoint-to-client)))
986 (gds-send "continue" client))
987
988 (;; (note ...) - For debugging only.
989 (eq proc 'note))
990
991 (;; (trace ...) - Tracing.
992 (eq proc 'trace)
993 (with-current-buffer (get-buffer-create "*GDS Trace*")
994 (save-excursion
995 (goto-char (point-max))
996 (or (bolp) (insert "\n"))
997 (insert "[client " (number-to-string client) "] " (car args) "\n"))))
998
999 (t
1000 ;; Unexpected.
1001 (error "Bad protocol: %S" form))))
1002
1003;;;; Scheme mode keymap items.
1004
1005(define-key scheme-mode-map "\M-\C-x" 'gds-eval-defun)
1006(define-key scheme-mode-map "\C-x\C-e" 'gds-eval-last-sexp)
1007(define-key scheme-mode-map "\C-c\C-e" 'gds-eval-expression)
1008(define-key scheme-mode-map "\C-c\C-r" 'gds-eval-region)
1009(define-key scheme-mode-map "\C-hg" 'gds-help-symbol)
1010(define-key scheme-mode-map "\C-h\C-g" 'gds-apropos)
6d6a3fe2
NJ
1011(define-key scheme-mode-map "\C-hG" 'gds-apropos)
1012(define-key scheme-mode-map "\C-hS" 'gds-show-last-stack)
731bcf73
NJ
1013(define-key scheme-mode-map "\e\t" 'gds-complete-symbol)
1014(define-key scheme-mode-map "\C-x " 'gds-set-breakpoint)
1015
1016(define-prefix-command 'gds-breakpoint-map)
1017(define-key scheme-mode-map "\C-c\C-b" 'gds-breakpoint-map)
1018(define-key gds-breakpoint-map " " 'gds-set-breakpoint)
1019(define-key gds-breakpoint-map "d"
1020 (function (lambda ()
1021 (interactive)
1022 (let ((gds-default-breakpoint-type 'debug))
1023 (gds-set-breakpoint)))))
1024(define-key gds-breakpoint-map "t"
1025 (function (lambda ()
1026 (interactive)
1027 (let ((gds-default-breakpoint-type 'trace))
1028 (gds-set-breakpoint)))))
1029(define-key gds-breakpoint-map "T"
1030 (function (lambda ()
1031 (interactive)
1032 (let ((gds-default-breakpoint-type 'trace-subtree))
1033 (gds-set-breakpoint)))))
1034(define-key gds-breakpoint-map [backspace] 'gds-delete-breakpoints)
1035(define-key gds-breakpoint-map "?" 'gds-describe-breakpoints)
1036
1037;;;; The end!
1038
1039(provide 'gds-scheme)
1040
1041;;; gds-scheme.el ends here.