Commit | Line | Data |
---|---|---|
79b1c5b6 NJ |
1 | ;;; gds.el -- Guile debugging frontend |
2 | ||
3 | ;;;; Copyright (C) 2003 Free Software Foundation, Inc. | |
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 | ||
22 | ;;;; Prerequisites. | |
23 | ||
24 | (require 'widget) | |
25 | (require 'wid-edit) | |
26 | ||
27 | ||
28 | ;;;; Debugging (of this code!). | |
29 | ||
30 | (defsubst dmessage (msg &rest args) | |
31 | ;;(apply (function message) msg args) | |
32 | ) | |
33 | ||
34 | ||
35 | ;;;; Customization group setup. | |
36 | ||
37 | (defgroup gds nil | |
38 | "Customization options for Guile Debugging." | |
39 | :group 'scheme) | |
40 | ||
41 | ||
42 | ;;;; Communication with the (ice-9 debugger ui-server) subprocess. | |
43 | ||
44 | ;; The subprocess object. | |
45 | (defvar gds-process nil) | |
46 | ||
47 | ;; Subprocess output goes into the `*GDS Process*' buffer, and | |
48 | ;; is then read from there one form at a time. `gds-read-cursor' is | |
49 | ;; the buffer position of the start of the next unread form. | |
50 | (defvar gds-read-cursor nil) | |
51 | ||
52 | ;; Start (or restart) the subprocess. | |
53 | (defun gds-start () | |
54 | (if gds-process (gds-shutdown)) | |
55 | (with-current-buffer (get-buffer-create "*GDS Process*") | |
56 | (erase-buffer) | |
57 | (setq gds-process | |
58 | (let ((process-connection-type nil)) ; use a pipe | |
59 | (start-process "gds" | |
60 | (current-buffer) | |
61 | "guile" | |
62 | "-q" | |
63 | "--debug" | |
64 | "-e" | |
65 | "run" | |
66 | "-s" | |
67 | "/home/neil/Guile/cvs/guile-core/ice-9/debugger/ui-server.scm")))) | |
68 | (setq gds-read-cursor (point-min)) | |
69 | (set-process-filter gds-process (function gds-filter)) | |
70 | (set-process-sentinel gds-process (function gds-sentinel)) | |
71 | (set-process-coding-system gds-process 'latin-1-unix)) | |
72 | ||
73 | ;; Shutdown the subprocess and cleanup all associated data. | |
74 | (defun gds-shutdown () | |
75 | ;; Do cleanup for all clients. | |
76 | (while gds-names | |
77 | (gds-client-cleanup (caar gds-names))) | |
78 | ;; Reset any remaining variables. | |
79 | (setq gds-displayed-client nil | |
80 | gds-waiting nil) | |
81 | ;; If the timer is running, cancel it. | |
82 | (if gds-timer | |
83 | (cancel-timer gds-timer)) | |
84 | (setq gds-timer nil) | |
85 | ;; Kill the subprocess. | |
86 | (process-kill-without-query gds-process) | |
87 | (condition-case nil | |
88 | (progn | |
89 | (kill-process gds-process) | |
90 | (accept-process-output gds-process 0 200)) | |
91 | (error)) | |
92 | (setq gds-process nil)) | |
93 | ||
94 | ;; Subprocess output filter: inserts normally into the process buffer, | |
95 | ;; then tries to reread the output one form at a time and delegates | |
96 | ;; processing of each form to `gds-handle-input'. | |
97 | (defun gds-filter (proc string) | |
98 | (with-current-buffer (process-buffer proc) | |
99 | (save-excursion | |
100 | (goto-char (process-mark proc)) | |
101 | (insert-before-markers string)) | |
102 | (goto-char gds-read-cursor) | |
103 | (while (let ((form (condition-case nil | |
104 | (read (current-buffer)) | |
105 | (error nil)))) | |
106 | (if form | |
107 | (save-excursion | |
108 | (gds-handle-input form))) | |
109 | form) | |
110 | (setq gds-read-cursor (point))))) | |
111 | ||
112 | ;; Subprocess sentinel: do nothing. (Currently just here to avoid | |
113 | ;; inserting un-`read'able process status messages into the process | |
114 | ;; buffer.) | |
115 | (defun gds-sentinel (proc event) | |
116 | ) | |
117 | ||
118 | ;; Send input to the subprocess. | |
119 | (defun gds-send (string) | |
120 | (process-send-string gds-process string)) | |
121 | ||
122 | ||
123 | ;;;; Multiple application scheduling. | |
124 | ||
125 | ;; At any moment one Guile application has the focus of the frontend | |
126 | ;; code. `gds-displayed-client' holds the port number of that client. | |
127 | ;; If there are no Guile applications wanting the focus - that is, | |
128 | ;; ready for debugging instructions - `gds-displayed-client' is nil. | |
129 | (defvar gds-displayed-client nil) | |
130 | ||
131 | ;; The list of other Guile applications waiting for focus, referenced | |
132 | ;; by their port numbers. | |
133 | (defvar gds-waiting nil) | |
134 | ||
135 | ;; An idle timer that we use to avoid confusing any user work when | |
136 | ;; popping up debug buffers. `gds-timer' is non-nil whenever the | |
137 | ;; timer is running and nil whenever it is not running. | |
138 | (defvar gds-timer nil) | |
139 | ||
140 | ;; Debug the specified client. If it already has the focus, do so | |
141 | ;; immediately, but using the idle timer to ensure that it doesn't | |
142 | ;; confuse any work the user may be doing. Non-structural work is | |
143 | ;; delegated to `gds-display-state'. | |
144 | (defun gds-debug (&optional client) | |
145 | (dmessage "gds-debug") | |
146 | ;; If `client' is specified, add it to the end of `gds-waiting', | |
147 | ;; unless that client is already the current client or it is already | |
148 | ;; in the waiting list. | |
149 | (if (and client | |
150 | (not (eq client gds-displayed-client)) | |
151 | (not (memq client gds-waiting))) | |
152 | (setq gds-waiting (append gds-waiting (list client)))) | |
153 | ;; Now update `client' to be the next client in the list. | |
154 | (setq client (or gds-displayed-client (car gds-waiting))) | |
155 | ;; If conditions are right, start the idle timer. | |
156 | (if (and client | |
157 | (or (null gds-displayed-client) | |
158 | (eq gds-displayed-client client))) | |
159 | (gds-display-state (or gds-displayed-client | |
160 | (prog1 (car gds-waiting) | |
161 | (setq gds-waiting | |
162 | (cdr gds-waiting))))))) | |
163 | ||
164 | ;; Give up focus because debugging is done for now. Display detail in | |
165 | ;; case of no waiting clients is delegated to `gds-clear-display'. | |
166 | (defun gds-focus-done () | |
167 | (gds-clear-display) | |
168 | (gds-debug)) | |
169 | ||
170 | ;; Although debugging of this client isn't done, yield focus to the | |
171 | ;; next waiting client. | |
172 | (defun gds-focus-yield () | |
173 | (interactive) | |
174 | (if (and (null gds-waiting) | |
175 | (y-or-n-p "No other clients waiting - bury *Guile Debug* buffer? ")) | |
176 | (bury-buffer) | |
177 | (or (memq gds-displayed-client gds-waiting) | |
178 | (setq gds-waiting (append gds-waiting (list gds-displayed-client)))) | |
179 | (gds-focus-done))) | |
180 | ||
181 | ||
182 | ;;;; Per-client state information. | |
183 | ||
184 | ;; Alist mapping client port numbers to application names. The names | |
185 | ;; in this list have been uniquified by `gds-uniquify'. | |
186 | (defvar gds-names nil) | |
187 | ||
188 | ;; Return unique form of NAME. | |
189 | (defun gds-uniquify (name) | |
190 | (let ((count 1) | |
191 | (maybe-unique name)) | |
192 | (while (member maybe-unique (mapcar (function cdr) gds-names)) | |
193 | (setq count (1+ count) | |
194 | maybe-unique (concat name "<" (number-to-string count) ">"))) | |
195 | maybe-unique)) | |
196 | ||
197 | ;; Alist mapping client port numbers to last known status. | |
198 | ;; | |
199 | ;; Status is one of the following symbols. | |
200 | ;; | |
201 | ;; `running' - application is running. | |
202 | ;; | |
203 | ;; `waiting-for-input' - application is blocked waiting for | |
204 | ;; instruction from the frontend. | |
205 | ;; | |
206 | ;; `ready-for-input' - application is not blocked but can also | |
207 | ;; accept asynchronous instructions from the frontend. | |
208 | ;; | |
209 | (defvar gds-statuses nil) | |
210 | ||
211 | ;; Alist mapping client port numbers to last printed outputs. | |
212 | (defvar gds-outputs nil) | |
213 | ||
214 | ;; Alist mapping client port numbers to last known stacks. | |
215 | (defvar gds-stacks nil) | |
216 | ||
217 | ;; Alist mapping client port numbers to module information. This | |
218 | ;; looks like: | |
219 | ;; | |
220 | ;; ((4 ((guile) t sym1 sym2 ...) ((guile-user)) ((ice-9 debug) nil sym3 sym4) ...) ...) | |
221 | ;; | |
222 | ;; So, for example: | |
223 | ;; | |
224 | ;; (assq client gds-modules) | |
225 | ;; => | |
226 | ;; (4 ((guile) t sym1 sym2 ...) ((guile-user)) ((ice-9 debug) nil sym3 sym4) ...) | |
227 | ;; | |
228 | ;; The t or nil after the module name indicates whether the module is | |
229 | ;; displayed in expanded form (that is, showing the bindings in that | |
230 | ;; module). | |
231 | ;; | |
232 | ;; The syms are actually all strings, because some Guile symbols are | |
233 | ;; not readable by Emacs. | |
234 | (defvar gds-modules nil) | |
235 | ||
236 | ||
237 | ;;;; Handling debugging instructions. | |
238 | ||
239 | ;; General dispatch function called by the subprocess filter. | |
240 | (defun gds-handle-input (form) | |
241 | (dmessage "Form: %S" form) | |
242 | (let ((client (car form))) | |
243 | (cond ((eq client '*)) | |
244 | (t | |
245 | (let ((proc (cadr form))) | |
246 | ||
247 | (cond ((eq proc 'name) | |
248 | ;; (name ...) - Application's name. | |
249 | (setq gds-names | |
250 | (cons (cons client (gds-uniquify (caddr form))) | |
251 | gds-names))) | |
252 | ||
253 | ((eq proc 'stack) | |
254 | ;; (stack ...) - Stack at an error or breakpoint. | |
255 | (gds-set gds-stacks client (cddr form))) | |
256 | ||
257 | ((eq proc 'modules) | |
258 | ;; (modules ...) - Application's loaded modules. | |
259 | (gds-set gds-modules client | |
260 | (mapcar (function list) (cddr form)))) | |
261 | ||
262 | ((eq proc 'output) | |
263 | ;; (output ...) - Last printed output. | |
264 | (gds-set gds-outputs client (caddr form))) | |
265 | ||
266 | ((eq proc 'status) | |
267 | ;; (status ...) - Application status indication. | |
268 | (let ((status (caddr form))) | |
269 | (gds-set gds-statuses client status) | |
270 | (cond ((eq status 'waiting-for-input) | |
271 | (gds-debug client)) | |
272 | ((eq status 'running) | |
273 | (if (eq client gds-displayed-client) | |
274 | (gds-display-state client))) | |
275 | (t | |
276 | (error "Unexpected status: %S" status))))) | |
277 | ||
278 | ((eq proc 'module) | |
279 | ;; (module MODULE ...) - The specified module's bindings. | |
280 | (let* ((modules (assq client gds-modules)) | |
281 | (minfo (assoc (caddr form) modules))) | |
282 | (if minfo | |
283 | (setcdr (cdr minfo) (cdddr form))))) | |
284 | ||
285 | ((eq proc 'closed) | |
286 | ;; (closed) - Client has gone away. | |
287 | (gds-client-cleanup client)) | |
288 | ||
289 | )))))) | |
290 | ||
291 | ;; Store latest status, stack or module list for the specified client. | |
292 | (defmacro gds-set (alist client val) | |
293 | `(let ((existing (assq ,client ,alist))) | |
294 | (if existing | |
295 | (setcdr existing ,val) | |
296 | (setq ,alist | |
297 | (cons (cons client ,val) ,alist))))) | |
298 | ||
299 | ;; Cleanup processing when CLIENT goes away. | |
300 | (defun gds-client-cleanup (client) | |
301 | (if (eq client gds-displayed-client) | |
302 | (gds-focus-done)) | |
303 | (setq gds-names | |
304 | (delq (assq client gds-names) gds-names)) | |
305 | (setq gds-stacks | |
306 | (delq (assq client gds-stacks) gds-stacks)) | |
307 | (setq gds-modules | |
308 | (delq (assq client gds-modules) gds-modules))) | |
309 | ||
310 | ||
311 | ;;;; Displaying debugging information. | |
312 | ||
313 | (defvar gds-client-buffer nil) | |
314 | ||
315 | (define-derived-mode gds-mode | |
316 | fundamental-mode | |
317 | "Guile Debugging" | |
318 | "Major mode for Guile debugging information buffers.") | |
319 | ||
320 | (defun gds-set-client-buffer (&optional client) | |
321 | (if (and gds-client-buffer | |
322 | (buffer-live-p gds-client-buffer)) | |
323 | (set-buffer gds-client-buffer) | |
324 | (setq gds-client-buffer (get-buffer-create "*Guile Debug*")) | |
325 | (set-buffer gds-client-buffer) | |
326 | (gds-mode)) | |
327 | ;; Rename to something we don't want first. Otherwise, if the | |
328 | ;; buffer is already correctly named, we get a confusing change | |
329 | ;; from, say, `*Guile Debug: REPL*' to `*Guile Debug: REPL*<2>'. | |
330 | (rename-buffer "*Guile Debug Fake Buffer Name*" t) | |
331 | (rename-buffer (if client | |
332 | (concat "*Guile Debug: " | |
333 | (cdr (assq client gds-names)) | |
334 | "*") | |
335 | "*Guile Debug*") | |
336 | t) ; Rename uniquely if needed, | |
337 | ; although it shouldn't be. | |
338 | (force-mode-line-update t)) | |
339 | ||
340 | (defun gds-clear-display () | |
341 | ;; Clear the client buffer. | |
342 | (gds-set-client-buffer) | |
343 | (let ((inhibit-read-only t)) | |
344 | (erase-buffer) | |
345 | (insert "Stack:\nNo clients ready for debugging.\n") | |
346 | (goto-char (point-min))) | |
347 | (setq gds-displayed-stack 'no-clients) | |
348 | (setq gds-displayed-modules nil) | |
349 | (setq gds-displayed-client nil) | |
350 | (bury-buffer)) | |
351 | ||
352 | ;; Determine whether the client display buffer is visible in the | |
353 | ;; currently selected frame (i.e. where the user is editing). | |
354 | (defun gds-buffer-visible-in-selected-frame-p () | |
355 | (let ((visible-p nil)) | |
356 | (walk-windows (lambda (w) | |
357 | (if (eq (window-buffer w) gds-client-buffer) | |
358 | (setq visible-p t)))) | |
359 | visible-p)) | |
360 | ||
361 | ;; Cached display variables for `gds-display-state'. | |
362 | (defvar gds-displayed-stack nil) | |
363 | (defvar gds-displayed-modules nil) | |
364 | ||
365 | ;; Types of display areas in the *Guile Debug* buffer. | |
366 | (defvar gds-display-types '("Status" "Stack" "Modules")) | |
367 | (defvar gds-display-type-regexp | |
368 | (concat "^\\(" | |
369 | (substring (apply (function concat) | |
370 | (mapcar (lambda (type) | |
371 | (concat "\\|" type)) | |
372 | gds-display-types)) | |
373 | 2) | |
374 | "\\):")) | |
375 | ||
376 | (defun gds-maybe-delete-region (type) | |
377 | (let ((beg (save-excursion | |
378 | (goto-char (point-min)) | |
379 | (and (re-search-forward (concat "^" | |
380 | (regexp-quote type) | |
381 | ":") | |
382 | nil t) | |
383 | (match-beginning 0))))) | |
384 | (if beg | |
385 | (delete-region beg | |
386 | (save-excursion | |
387 | (goto-char beg) | |
388 | (end-of-line) | |
389 | (or (and (re-search-forward gds-display-type-regexp | |
390 | nil t) | |
391 | (match-beginning 0)) | |
392 | (point-max))))))) | |
393 | ||
394 | (defun gds-maybe-skip-region (type) | |
395 | (if (looking-at (regexp-quote type)) | |
396 | (if (re-search-forward gds-display-type-regexp nil t 2) | |
397 | (beginning-of-line) | |
398 | (goto-char (point-max))))) | |
399 | ||
400 | (defun gds-display-state (client) | |
401 | (dmessage "gds-display-state") | |
402 | ;; Avoid continually popping up the last associated source buffer | |
403 | ;; unless it really is still current. | |
404 | (setq gds-selected-frame-source-buffer nil) | |
405 | (gds-set-client-buffer client) | |
406 | (let ((stack (cdr (assq client gds-stacks))) | |
407 | (modules (cdr (assq client gds-modules))) | |
408 | (inhibit-read-only t) | |
409 | (p (if (eq client gds-displayed-client) | |
410 | (point) | |
411 | (point-min))) | |
412 | stack-changed) | |
413 | ;; Start at top of buffer. | |
414 | (goto-char (point-min)) | |
415 | ;; Display status; too simple to be worth caching. | |
416 | (gds-maybe-delete-region "Status") | |
417 | (widget-insert "Status: " | |
418 | (cdr (assq (cdr (assq client gds-statuses)) | |
419 | '((running . "running") | |
420 | (waiting-for-input . "waiting for input") | |
421 | (ready-for-input . "ready for input")))) | |
422 | "\n\n") | |
423 | (let ((output (cdr (assq client gds-outputs)))) | |
424 | (if (> (length output) 0) | |
425 | (widget-insert output "\n\n"))) | |
426 | ;; Display stack. | |
427 | (dmessage "insert stack") | |
428 | (if (equal stack gds-displayed-stack) | |
429 | (gds-maybe-skip-region "Stack") | |
430 | ;; Note that stack has changed. | |
431 | (if stack (setq stack-changed t)) | |
432 | ;; Delete existing stack. | |
433 | (gds-maybe-delete-region "Stack") | |
434 | ;; Insert new stack. | |
435 | (if stack (gds-insert-stack stack)) | |
436 | ;; Record displayed stack. | |
437 | (setq gds-displayed-stack stack)) | |
438 | ;; Display module list. | |
439 | (dmessage "insert modules") | |
440 | (if (equal modules gds-displayed-modules) | |
441 | (gds-maybe-skip-region "Modules") | |
442 | ;; Delete existing module list. | |
443 | (gds-maybe-delete-region "Modules") | |
444 | ;; Insert new list. | |
445 | (if modules (gds-insert-modules modules)) | |
446 | ;; Record displayed list. | |
447 | (setq gds-displayed-modules (copy-tree modules))) | |
448 | ;; Finish off. | |
449 | (dmessage "widget-setup") | |
450 | (widget-setup) | |
451 | (if stack-changed | |
452 | ;; Stack is being seen for the first time, so make sure top of | |
453 | ;; buffer is visible. | |
454 | (progn | |
455 | (goto-char (point-min)) | |
456 | (re-search-forward "^Stack:") | |
457 | (forward-line (+ 1 (cadr stack)))) | |
458 | ;; Restore point from before buffer was redrawn. | |
459 | (goto-char p))) | |
460 | (setq gds-displayed-client client) | |
461 | (dmessage "consider display") | |
462 | (if (eq (window-buffer (selected-window)) gds-client-buffer) | |
463 | ;; *Guile Debug* buffer already selected. | |
464 | (gds-display-buffers) | |
465 | (dmessage "Running GDS timer") | |
466 | (setq gds-timer | |
467 | (run-with-idle-timer 0.5 | |
468 | nil | |
469 | (lambda () | |
470 | (setq gds-timer nil) | |
471 | (gds-display-buffers)))))) | |
472 | ||
473 | (defun gds-display-buffers () | |
474 | ;; If there's already a window showing the *Guile Debug* buffer, use | |
475 | ;; it. | |
476 | (let ((window (get-buffer-window gds-client-buffer t))) | |
477 | (if window | |
478 | (progn | |
479 | (make-frame-visible (window-frame window)) | |
480 | (raise-frame (window-frame window)) | |
481 | (select-frame (window-frame window)) | |
482 | (select-window window)) | |
483 | (switch-to-buffer gds-client-buffer))) | |
484 | ;; If there is an associated source buffer, display it as well. | |
485 | (if gds-selected-frame-source-buffer | |
486 | (let ((window (display-buffer gds-selected-frame-source-buffer))) | |
487 | (set-window-point window | |
488 | (overlay-start gds-selected-frame-source-overlay)))) | |
489 | ;; Force redisplay. | |
490 | (sit-for 0)) | |
491 | ||
492 | (defun old-stuff () | |
493 | (if (gds-buffer-visible-in-selected-frame-p) | |
494 | ;; Buffer already visible enough. | |
495 | nil | |
496 | ;; Delete any views of the buffer in other frames - we don't want | |
497 | ;; views all over the place. | |
498 | (delete-windows-on gds-client-buffer) | |
499 | ;; Run idle timer to display the buffer as soon as user isn't in | |
500 | ;; the middle of something else. | |
501 | )) | |
502 | ||
503 | (defun gds-insert-stack (stack) | |
504 | (let ((frames (car stack)) | |
505 | (index (cadr stack)) | |
506 | (flags (caddr stack)) | |
507 | frame items) | |
508 | (widget-insert "Stack: " (prin1-to-string flags) "\n") | |
509 | (let ((i -1)) | |
510 | (gds-show-selected-frame (caddr (nth index frames))) | |
511 | (while frames | |
512 | (setq frame (car frames) | |
513 | frames (cdr frames) | |
514 | i (+ i 1) | |
515 | items (cons (list 'item | |
516 | (let ((s (cadr frame))) | |
517 | (put-text-property 0 1 'index i s) | |
518 | s)) | |
519 | items)))) | |
520 | (setq items (nreverse items)) | |
521 | (apply (function widget-create) | |
522 | 'radio-button-choice | |
523 | :value (cadr (nth index items)) | |
524 | :notify (function gds-select-stack-frame) | |
525 | items) | |
526 | (widget-insert "\n"))) | |
527 | ||
528 | (defun gds-select-stack-frame (widget &rest ignored) | |
529 | (let* ((s (widget-value widget)) | |
530 | (ind (memq 'index (text-properties-at 0 s)))) | |
531 | (gds-send (format "(%S debugger-command frame %d)\n" | |
532 | gds-displayed-client | |
533 | (cadr ind))))) | |
534 | ||
535 | ;; Overlay used to highlight the source expression corresponding to | |
536 | ;; the selected frame. | |
537 | (defvar gds-selected-frame-source-overlay nil) | |
538 | ||
539 | ;; Buffer containing source for the selected frame. | |
540 | (defvar gds-selected-frame-source-buffer nil) | |
541 | ||
542 | (defun gds-show-selected-frame (source) | |
543 | ;; Highlight the frame source, if possible. | |
544 | (if (and source | |
545 | (file-readable-p (car source))) | |
546 | (with-current-buffer (find-file-noselect (car source)) | |
547 | (if gds-selected-frame-source-overlay | |
548 | nil | |
549 | (setq gds-selected-frame-source-overlay (make-overlay 0 0)) | |
550 | (overlay-put gds-selected-frame-source-overlay 'face 'highlight)) | |
551 | ;; Move to source line. Note that Guile line numbering is | |
552 | ;; 0-based, while Emacs numbering is 1-based. | |
553 | (save-restriction | |
554 | (widen) | |
555 | (goto-line (+ (cadr source) 1)) | |
556 | (move-to-column (caddr source)) | |
557 | (move-overlay gds-selected-frame-source-overlay | |
558 | (point) | |
559 | (if (not (looking-at ")")) | |
560 | (save-excursion (forward-sexp 1) (point)) | |
561 | ;; It seems that the source coordinates for | |
562 | ;; backquoted expressions are at the end of | |
563 | ;; the sexp rather than the beginning... | |
564 | (save-excursion (forward-char 1) | |
565 | (backward-sexp 1) (point))) | |
566 | (current-buffer))) | |
567 | (setq gds-selected-frame-source-buffer (current-buffer))) | |
568 | (if gds-selected-frame-source-overlay | |
569 | (move-overlay gds-selected-frame-source-overlay 0 0)))) | |
570 | ||
571 | (defcustom gds-module-filter '(t (guile nil) (ice-9 nil) (oop nil)) | |
572 | "Specification of which Guile modules the debugger should display. | |
573 | This is a list with structure (DEFAULT EXCEPTION EXCEPTION...), where | |
574 | DEFAULT is `t' or `nil' and each EXCEPTION has the structure (SYMBOL | |
575 | DEFAULT EXCEPTION EXCEPTION...). | |
576 | ||
577 | A Guile module name `(x y z)' is matched against this filter as | |
578 | follows. If one of the top level EXCEPTIONs has SYMBOL `x', continue | |
579 | by matching the rest of the module name, in this case `(y z)', against | |
580 | that SYMBOL's DEFAULT and next level EXCEPTION list. Otherwise, if | |
581 | the current DEFAULT is `t' display the module, and if the current | |
582 | DEFAULT is `nil', don't display it. | |
583 | ||
584 | This variable is usually set to exclude Guile system modules that are | |
585 | not of primary interest when debugging application code." | |
586 | :type 'sexp | |
587 | :group 'gds) | |
588 | ||
589 | (defun gds-show-module-p (name) | |
590 | ;; Determine whether to display the NAMEd module by matching NAME | |
591 | ;; against `gds-module-filter'. | |
592 | (let ((default (car gds-module-filter)) | |
593 | (exceptions (cdr gds-module-filter))) | |
594 | (let ((exception (assq (car name) exceptions))) | |
595 | (if exception | |
596 | (let ((gds-module-filter (cdr exception))) | |
597 | (gds-show-module-p (cdr name))) | |
598 | default)))) | |
599 | ||
600 | (defun gds-insert-modules (modules) | |
601 | (insert "Modules:\n") | |
602 | (while modules | |
603 | (let ((minfo (car modules))) | |
604 | (if (gds-show-module-p (car minfo)) | |
605 | (let ((w (widget-create 'push-button | |
606 | :notify (function gds-module-notify) | |
607 | (if (and (cdr minfo) | |
608 | (cadr minfo)) | |
609 | "-" "+")))) | |
610 | (widget-put w :module (cons client (car minfo))) | |
611 | (widget-insert " " (prin1-to-string (car minfo)) "\n") | |
612 | (if (cadr minfo) | |
613 | (let ((syms (cddr minfo))) | |
614 | (while syms | |
615 | (widget-insert " > " (car syms) "\n") | |
616 | (setq syms (cdr syms)))))))) | |
617 | (setq modules (cdr modules)))) | |
618 | ||
619 | (defun gds-module-notify (w &rest ignore) | |
620 | (let* ((module (widget-get w :module)) | |
621 | (client (car module)) | |
622 | (name (cdr module)) | |
623 | (modules (assq client gds-modules)) | |
624 | (minfo (assoc name modules))) | |
625 | (if (cdr minfo) | |
626 | ;; Just toggle expansion state. | |
627 | (progn | |
628 | (setcar (cdr minfo) (not (cadr minfo))) | |
629 | (gds-display-state client)) | |
630 | ;; Set flag to indicate module expanded. | |
631 | (setcdr minfo (list t)) | |
632 | ;; Get symlist from Guile. | |
633 | (gds-send (format "(%S query-module %S)\n" client name))))) | |
634 | ||
635 | ||
636 | ;;;; Guile Debugging keymap. | |
637 | ||
638 | (set-keymap-parent gds-mode-map widget-keymap) | |
639 | (define-key gds-mode-map "g" (function gds-go)) | |
640 | (define-key gds-mode-map "b" (function gds-set-breakpoint)) | |
641 | (define-key gds-mode-map "q" (function gds-quit)) | |
642 | (define-key gds-mode-map "y" (function gds-yield)) | |
643 | (define-key gds-mode-map " " (function gds-next)) | |
644 | (define-key gds-mode-map "e" (function gds-evaluate)) | |
645 | (define-key gds-mode-map "i" (function gds-step-in)) | |
646 | (define-key gds-mode-map "o" (function gds-step-out)) | |
647 | (define-key gds-mode-map "t" (function gds-trace-finish)) | |
648 | ||
649 | (defun gds-client-waiting () | |
650 | (eq (cdr (assq gds-displayed-client gds-statuses)) 'waiting-for-input)) | |
651 | ||
652 | (defun gds-go () | |
653 | (interactive) | |
654 | (gds-send (format "(%S debugger-command continue)\n" gds-displayed-client))) | |
655 | ||
656 | (defun gds-quit () | |
657 | (interactive) | |
658 | (if (gds-client-waiting) | |
659 | (if (y-or-n-p "Client is waiting for instruction - tell it to continue? ") | |
660 | (gds-go))) | |
661 | (gds-yield)) | |
662 | ||
663 | (defun gds-yield () | |
664 | (interactive) | |
665 | (if (gds-client-waiting) | |
666 | (gds-focus-yield) | |
667 | (gds-focus-done))) | |
668 | ||
669 | (defun gds-next () | |
670 | (interactive) | |
671 | (gds-send (format "(%S debugger-command next 1)\n" gds-displayed-client))) | |
672 | ||
673 | (defun gds-evaluate (expr) | |
674 | (interactive "sEvaluate (in this stack frame): ") | |
675 | (gds-send (format "(%S debugger-command evaluate %s)\n" | |
676 | gds-displayed-client | |
677 | (prin1-to-string expr)))) | |
678 | ||
679 | (defun gds-step-in () | |
680 | (interactive) | |
681 | (gds-send (format "(%S debugger-command step 1)\n" gds-displayed-client))) | |
682 | ||
683 | (defun gds-step-out () | |
684 | (interactive) | |
685 | (gds-send (format "(%S debugger-command finish)\n" gds-displayed-client))) | |
686 | ||
687 | (defun gds-trace-finish () | |
688 | (interactive) | |
689 | (gds-send (format "(%S debugger-command trace-finish)\n" | |
690 | gds-displayed-client))) | |
691 | ||
692 | (defun gds-set-breakpoint () | |
693 | (interactive) | |
694 | (cond ((gds-in-source-buffer) | |
695 | (gds-set-source-breakpoint)) | |
696 | ((gds-in-stack) | |
697 | (gds-set-stack-breakpoint)) | |
698 | ((gds-in-modules) | |
699 | (gds-set-module-breakpoint)) | |
700 | (t | |
701 | (error "No way to set a breakpoint from here")))) | |
702 | ||
703 | (defun gds-in-source-buffer () | |
704 | ;; Not yet worked out what will be available in Scheme source | |
705 | ;; buffers. | |
706 | nil) | |
707 | ||
708 | (defun gds-in-stack () | |
709 | (and (eq (current-buffer) gds-client-buffer) | |
710 | (save-excursion | |
711 | (and (re-search-backward "^\\(Stack\\|Modules\\):" nil t) | |
712 | (looking-at "Stack"))))) | |
713 | ||
714 | (defun gds-in-modules () | |
715 | (and (eq (current-buffer) gds-client-buffer) | |
716 | (save-excursion | |
717 | (and (re-search-backward "^\\(Stack\\|Modules\\):" nil t) | |
718 | (looking-at "Modules"))))) | |
719 | ||
720 | (defun gds-set-module-breakpoint () | |
721 | (let ((sym (save-excursion | |
722 | (beginning-of-line) | |
723 | (and (looking-at " > \\([^ \n\t]+\\)") | |
724 | (match-string 1)))) | |
725 | (module (save-excursion | |
726 | (and (re-search-backward "^\\[[+---]\\] \\(([^)]+)\\)" nil t) | |
727 | (match-string 1))))) | |
728 | (or sym | |
729 | (error "Couldn't find procedure name on current line")) | |
730 | (or module | |
731 | (error "Couldn't find module name for current line")) | |
732 | (let ((behaviour | |
733 | (completing-read | |
734 | (format "Behaviour for breakpoint at %s:%s (default debug-here): " | |
735 | module sym) | |
736 | '(("debug-here") | |
737 | ("trace-here") | |
738 | ("trace-subtree")) | |
739 | nil | |
740 | t | |
741 | nil | |
742 | nil | |
743 | "debug-here"))) | |
744 | (gds-send (format "(%S set-breakpoint %s %s %s)\n" | |
745 | gds-displayed-client | |
746 | module | |
747 | sym | |
748 | behaviour))))) |