1 ;;;; -*- Mode: Lisp ; Package: Debug -*-
3 ;;; **********************************************************************
4 ;;; This code was written as part of the CMU Common Lisp project at
5 ;;; Carnegie Mellon University, and has been placed in the public domain.
8 "$Header: src/interface/debug.lisp $")
10 ;;; **********************************************************************
12 ;;; Written by Michael Garland
14 ;;; This file implements the graphical interface to the debugger.
18 (use-package '("TOOLKIT" "INTERFACE"))
20 ;;; We need to record three things globally:
21 ;;; - The structure decribing the current debugger display
22 ;;; - The frame displays which are currently active (ie. visible)
23 ;;; - The husks of old debugger displays for reuse
25 (defvar *current-debug-display* nil)
26 (defvar *debug-active-frames* nil)
27 (defvar *old-display-frames* nil)
31 ;;;; Structures used by the graphical debugger
33 (defstruct (debug-display
35 (:print-function print-debug-display)
36 (:constructor make-debug-display
37 (debug-pane errmsg restarts backtrace)))
38 (debug-pane nil :type (or null widget))
39 (errmsg nil :type (or null widget))
40 (restarts nil :type (or null widget))
41 (backtrace nil :type (or null widget))
42 (level 0 :type fixnum)
43 (connection nil :type (or null xt::motif-connection)))
45 (defun print-debug-display (info stream d)
47 (format stream "#<Debugger Display Info level ~d" (dd-info-level info)))
51 ;;;; Callback functions used by debugger
53 (defun quit-debugger-callback (widget call-data)
54 (declare (ignore widget call-data))
55 (close-motif-debugger *debug-condition*)
56 (throw 'lisp::top-level-catcher nil))
58 (defun restart-callback (widget call-data restart)
59 (declare (ignore widget call-data))
60 (invoke-restart-interactively restart))
62 (defun stack-frame-callback (widget call-data frame)
63 (declare (ignore widget call-data))
64 (unless (assoc frame *debug-active-frames*)
65 ;; Should wrap this in a busy cursor
66 (debug-display-frame frame)))
68 (defun close-all-callback (widget call-data)
69 (declare (ignore widget call-data))
70 (dolist (info *debug-active-frames*)
71 (destroy-widget (cdr info)))
72 (setf *debug-active-frames* nil))
74 ;;; This is to provide a means for recording the stack backtrace. In
75 ;;; particular, this is important for sending bug reports.
77 (defun dump-backtrace-callback (widget call-data)
78 (declare (ignore widget call-data))
79 (let ((*current-frame* (di:top-frame)))
80 (format t "~%Stack Backtrace:~%")
83 (defun frame-view-callback (widget call-data thing)
84 (declare (ignore widget call-data))
85 ;; Should wrap this in a busy cursor
88 (defun close-frame-callback (widget call-data frame)
89 (declare (ignore widget call-data))
90 (setf *debug-active-frames*
91 (delete frame *debug-active-frames*
92 :test #'(lambda (a b) (eql a (car b)))))
93 (destroy-interface-pane frame))
95 (defun edit-source-callback (widget call-data frame)
96 (declare (ignore widget call-data))
98 (let ((*current-frame* frame))
99 (funcall (debug-command-p :edit-source)))
101 (interface-error (safe-condition-message cond)))))
103 (defun frame-eval-callback (widget call-data frame output)
104 (declare (ignore call-data))
105 (let* ((input (car (get-values widget :value)))
106 (mark (text-get-last-position output))
108 (format nil "Eval>> ~a~%~a--------------------~%"
113 (let ((*current-frame* frame))
114 (grab-output-as-string
115 (di:eval-in-frame frame (read-from-string input))))
116 (format nil "~a~s~%" out val))
118 (safe-condition-message cond)))))
119 (length (length response)))
120 (declare (simple-string response))
122 (text-set-string widget "")
123 (text-insert output mark response)
124 ;; This is to make sure that things stay visible
125 (text-set-insertion-position output (+ length mark))))
127 (defun source-verbosity-callback (widget call-data frame srcview delta)
128 (declare (ignore widget call-data))
129 (let* ((current (car (get-values srcview :user-data)))
130 (new (+ current delta)))
133 (let ((source (handler-case
134 (grab-output-as-string
135 (print-code-location-source-form
136 (di:frame-code-location frame) new))
137 (di:debug-condition (cond)
138 (declare (ignore cond))
139 "Source form not available."))))
146 ;;; DEBUG-DISPLAY-FRAME-LOCALS -- Internal
148 ;;; This sets up the display of the available local variables for the given
151 (defun debug-display-frame-locals (frame debug-fun location frame-view)
153 (if (di:debug-variable-info-available debug-fun)
156 (di:do-debug-function-variables (v debug-fun)
159 (push (create-label frame-view "localsLabel"
160 :font-list *header-font*
161 :label-string "Local variables:")
163 (when (eq (di:debug-variable-validity v location) :valid)
164 (let ((value (di:debug-variable-value v frame))
165 (id (di:debug-variable-id v)))
168 (create-value-box frame-view
169 (format nil " ~A~:[#~D~;~*~]:"
170 (di:debug-variable-name v)
173 :callback 'frame-view-callback)
178 (create-label frame-view "noLocals"
179 :font-list *italic-font*
181 " No local variables in function.")
185 (create-label frame-view "noValidLocals"
186 :font-list *italic-font*
188 " All variables have invalid values.")
191 (push (create-label frame-view "noVariableInfo"
192 :font-list *italic-font*
194 " No variable information available.")
196 (apply #'manage-children widgets)))
198 ;;; DEBUG-DISPLAY-FRAME-PROMPT -- Internal
200 ;;; Every frame window has a Frame Eval area. This function creates the
201 ;;; Eval area and attaches the necessary callbacks.
203 (defun debug-display-frame-prompt (frame frame-view)
204 (let* ((form (create-form frame-view "promptForm"))
205 (label (create-label form "framePrompt"
206 :label-string "Frame Eval:"
207 :font-list *header-font*))
208 (entry (create-text form "frameEval"
209 :top-attachment :attach-widget
211 :left-attachment :attach-form
212 :right-attachment :attach-form))
213 (output (create-text form "frameOutput"
214 :edit-mode :multi-line-edit
218 :top-attachment :attach-widget
220 :bottom-attachment :attach-form
221 :left-attachment :attach-form
222 :right-attachment :attach-form)))
225 (manage-children label entry output)
226 (add-callback entry :activate-callback 'frame-eval-callback
229 ;;; DEBUG-DISPLAY-FRAME -- Internal
231 ;;; Function to generate the graphical display for the given frame. Each
232 ;;; frame window is composed of the following parts:
233 ;;; - The function called
234 ;;; - The source form
235 ;;; - Local variables
236 ;;; - Frame Eval window
238 (defun debug-display-frame (frame)
239 (let* ((debug-fun (di:frame-debug-function frame))
240 (location (di:frame-code-location frame))
241 (name (di:debug-function-name debug-fun))
242 (title (format nil "Stack Frame: ~A" name))
243 (frame-shell (create-interface-pane-shell title frame))
244 (frame-view (create-row-column frame-shell "debugFrameView"))
245 (menu-bar (create-menu-bar frame-view "frameMenu"))
246 (fcall (create-label frame-view "frameCall"
248 (format nil "Frame Call: ~a"
249 (grab-output-as-string
250 (print-frame-call frame)))))
251 (fbox (create-value-box frame-view "Function:"
253 :callback 'frame-view-callback
255 (di:debug-function-function debug-fun)))
256 (slabel (create-label frame-view "sourceLabel"
257 :font-list *header-font*
258 :label-string "Source form:"))
259 (swindow (create-scrolled-window frame-view "frameSourceWindow"
260 :scrolling-policy :automatic
261 :scroll-bar-placement :bottom-right))
263 (source (handler-case
264 (grab-output-as-string
265 (print-code-location-source-form location 0))
266 (di:debug-condition (cond)
267 (declare (ignore cond))
268 "Source form not available.")))
269 (srcview (create-label swindow "sourceForm"
270 :alignment :alignment-beginning
272 :label-string source))
274 (create-interface-menu menu-bar "Frame"
275 `(("Edit Source" edit-source-callback ,frame)
276 ("Expand Source Form" source-verbosity-callback ,frame ,srcview 1)
277 ("Shrink Source Form" source-verbosity-callback ,frame ,srcview -1)
278 ("Close Frame" close-frame-callback ,frame))))
279 (cascade2 (create-cached-menu menu-bar "Debug")))
281 (manage-child frame-view)
282 (manage-children menu-bar fcall fbox slabel swindow)
283 (manage-child srcview)
284 (manage-children cascade1 cascade2)
286 (debug-display-frame-locals frame debug-fun location frame-view)
287 (debug-display-frame-prompt frame frame-view)
289 (popup-interface-pane frame-shell)
290 (push (cons frame frame-shell) *debug-active-frames*)))
294 ;;;; Functions to display the debugger control panel
296 ;;; DEBUG-DISPLAY-ERROR -- Internal
298 ;;; Fills in the given widget with the error message for the given
301 (defun debug-display-error (errmsg condition)
302 (set-values errmsg :label-string (safe-condition-message condition)))
304 ;;; DEBUG-DISPLAY-RESTARTS -- Internal
306 ;;; Fills in a RowColumn box with buttons corresponding to the currently
309 (defun debug-display-restarts (restart-view)
310 (let ((widgets (reverse (xti:widget-children restart-view)))
313 (dolist (r *debug-restarts*)
314 (let* ((label (format nil "~A" r))
316 (let ((w (pop widgets)))
317 (set-values w :label-string label)
318 (remove-all-callbacks w :activate-callback)
320 (create-highlight-button restart-view
324 (add-callback button :activate-callback 'restart-callback r)
325 (push button used-ones)))
326 (apply #'manage-children used-ones)
328 (apply #'unmanage-children widgets))))
330 ;;; DEBUG-DISPLAY-STACK -- Internal
332 ;;; Fills in a RowColumn box with buttons corresponding to the stack frames
333 ;;; found on the stack.
335 (defun debug-display-stack (backtrace)
336 (let ((widgets (reverse (xti:widget-children backtrace)))
340 (do ((frame *current-frame* (di:frame-down frame)))
343 (setf frames (nreverse frames))
345 (dolist (frame frames)
346 (let* ((label (grab-output-as-string
347 (print-frame-call frame)))
349 (let ((w (pop widgets)))
350 (set-values w :label-string label)
351 (remove-all-callbacks w :activate-callback)
353 (create-highlight-button
354 backtrace "stackFrame" label))))
355 (add-callback button :activate-callback 'stack-frame-callback frame)
356 (push button used-ones)))
357 (apply #'manage-children used-ones)
359 (apply #'unmanage-children widgets))))
361 ;;; REALLY-CREATE-DEBUGGER -- Internal
363 ;;; This creates all the widgets used by the main debugger window. It
364 ;;; calls various sub-functions such as DEBUG-DISPLAY-STACK to fill in the
365 ;;; various display sections. It should only be called if there are no old
366 ;;; debugger panes available for recycling.
368 (defun really-create-debugger (condition)
369 (let* ((debug-pane (create-interface-pane-shell "Debugger" condition))
370 (frame (create-frame debug-pane "debugFrame"))
371 (form (create-form frame "debugForm"))
372 (menu-bar (create-menu-bar form "debugMenu"
373 :left-attachment :attach-form
374 :right-attachment :attach-form))
375 (cascade (create-cached-menu
377 '(("Close All Frames" close-all-callback)
378 ("Dump Backtrace" dump-backtrace-callback)
379 ("Quit Debugger" quit-debugger-callback))))
380 (errlabel (create-label form "errorLabel"
381 :top-attachment :attach-widget
383 :left-attachment :attach-form
384 :font-list *header-font*
385 :label-string "Error Message:"))
386 (errmsg (create-label form "errorMessage"
387 :top-attachment :attach-widget
389 :left-attachment :attach-form
390 :right-attachment :attach-form))
391 (rlabel (create-label form "restartLabel"
392 :top-attachment :attach-widget
394 :left-attachment :attach-form
395 :font-list *header-font*))
396 (restarts (create-row-column form "debugRestarts"
398 :top-attachment :widget
400 :left-attachment :attach-form
401 :right-attachment :attach-form
403 (btlabel (create-label form "backtraceLabel"
404 :label-string "Stack Backtrace:"
405 :font-list *header-font*
406 :top-attachment :attach-widget
408 :left-attachment :attach-form))
409 (btwindow (create-scrolled-window form "backtraceWindow"
410 :scrolling-policy :automatic
411 :scroll-bar-placement :bottom-right
412 :left-attachment :attach-form
413 :right-attachment :attach-form
417 :bottom-attachment :attach-form
418 :top-attachment :attach-widget
419 :top-widget btlabel))
420 (backtrace (create-row-column btwindow "debugBacktrace"
424 (manage-child frame) (manage-child form)
425 (manage-children menu-bar errlabel errmsg rlabel restarts btlabel btwindow)
426 (manage-child backtrace)
427 (manage-child cascade)
429 (debug-display-error errmsg condition)
433 (set-values rlabel :label-string "Restarts:")
434 (debug-display-restarts restarts))
435 (set-values rlabel :label-string "No restarts available"))
437 (let ((quick-stack (create-highlight-button backtrace "quickStack"
439 (add-callback quick-stack :activate-callback
440 #'(lambda (w c) (declare (ignore w c))
441 (debug-display-stack backtrace)))
442 (manage-child quick-stack))
445 (setf *current-debug-display*
446 (make-debug-display debug-pane errmsg restarts backtrace))
448 (popup-interface-pane debug-pane)
451 ;;; REUSE-DEBUGGER -- Internal
453 ;;; Takes an old debugger pane and a new condition. It renovates the old
454 ;;; display to reflect the current debugging state. This should be used
455 ;;; whenever possible since it is quite a bit faster than creating a new
456 ;;; debugger pane from scratch.
458 (defun reuse-debugger (condition info)
459 (let ((debug-pane (dd-info-debug-pane info))
460 (errmsg (dd-info-errmsg info))
461 (restarts (dd-info-restarts info))
462 (backtrace (dd-info-backtrace info)))
464 (debug-display-error errmsg condition)
465 (debug-display-restarts restarts)
467 (let* ((buttons (xti:widget-children backtrace))
468 (quick-stack (car buttons)))
469 (remove-all-callbacks quick-stack :activate-callback)
470 (set-values quick-stack :label-string "Display Stack")
471 (add-callback quick-stack :activate-callback
472 #'(lambda (w c) (declare (ignore w c))
473 (debug-display-stack backtrace)))
474 (manage-child quick-stack)
475 (apply #'unmanage-children (cdr buttons)))
477 (setf *current-debug-display* info)
478 (popup-interface-pane debug-pane)
481 ;;; CREATE-DEBUGGER -- Internal
483 ;;; Creates a graphical debugger display for the given condition. It will
484 ;;; attempt to reuse any available old panes. However, if none are
485 ;;; available, it will create a new display frame.
487 (defun create-debugger (condition)
488 (if *old-display-frames*
489 (reuse-debugger condition (pop *old-display-frames*))
490 (really-create-debugger condition)))
492 ;;; CLOSE-MOTIF-DEBUGGER -- Internal
494 ;;; This function should always be called before leaving the context of the
495 ;;; debugger. It closes down the frame windows and marks the main debug
496 ;;; display pane as ready for recycling.
498 (defun close-motif-debugger (condition)
499 (declare (ignore condition))
500 (push *current-debug-display* *old-display-frames*)
502 ;; Destroy all frame panes
503 (dolist (info *debug-active-frames*)
504 (destroy-widget (cdr info)))
505 (setf *debug-active-frames* nil)
507 ;; Remove the restart/backtrace window
508 (popdown (dd-info-debug-pane *current-debug-display*))
509 (setf *current-debug-display* nil)
511 (format t "Leaving debugger.~%"))
513 ;;; INVOKE-MOTIF-DEBUGGER -- Internal
515 ;;; This function essentially mimics the functions which manage the TTY
516 ;;; debugger, but uses a graphical debugging display instead.
518 (defun invoke-motif-debugger (condition)
519 (let* ((*in-the-debugger* t)
520 (frame (di:top-frame))
521 (previous-display *current-debug-display*)
522 (*current-debug-display* nil)
523 (*debug-active-frames* nil))
524 (declare (ignore previous-display))
525 (verify-system-server-exists)
526 (multiple-value-bind (shell connection)
527 (create-interface-shell)
528 (declare (ignore shell))
530 (with-motif-connection (connection)
531 (let ((pane (find-interface-pane condition))
532 (*current-frame* frame))
534 (setf pane (create-debugger condition)))
535 (unless (is-managed pane)
536 (popup-interface-pane pane))
537 (setf (dd-info-level *current-debug-display*)
538 *debug-command-level*)
539 (setf (dd-info-connection *current-debug-display*) connection)
543 (system:serve-event))
545 (if *flush-debug-errors*
546 (interface-error (safe-condition-message err)
549 "Do not yet support recursive debugging"
551 (when (and connection *current-debug-display*)
552 (with-motif-connection (connection)
553 (close-motif-debugger condition))))))
554 (invoke-tty-debugger condition)))))
558 ;;; Used to prevent recursive invocations of the windowing debugger.
560 (defvar *in-windowing-debugger* nil)
563 ;;; REAL-INVOKE-DEBUGGER -- Internal
565 ;;; Invokes the Lisp debugger. It decides whether to invoke the TTY
566 ;;; debugger or the Motif debugger.
568 (defun real-invoke-debugger (condition)
569 (if (or (not (use-graphics-interface))
570 *in-windowing-debugger*
571 (typep condition 'xti:toolkit-error))
572 (invoke-tty-debugger condition)
573 (let ((*in-windowing-debugger* t))
574 (write-line "Invoking debugger...")
575 (invoke-motif-debugger condition))))