Need to wrap EVAL-WHEN around USE-PACKAGE since use-package doesn't
[projects/cmucl/cmucl.git] / src / interface / debug.lisp
CommitLineData
7a90c402 1;;;; -*- Mode: Lisp ; Package: Debug -*-
2;;;
0f6159f4 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.
dae4cf60 6;;;
7(ext:file-comment
99a5797f 8 "$Header: src/interface/debug.lisp $")
0f6159f4 9;;;
10;;; **********************************************************************
11;;;
12;;; Written by Michael Garland
13;;;
14;;; This file implements the graphical interface to the debugger.
15;;;
7a90c402 16
17(in-package "DEBUG")
a176515c
RT
18(eval-when (:compile-toplevel :load-toplevel :execute)
19 (use-package '("TOOLKIT" "INTERFACE")))
7a90c402 20
fe121aad 21;;; We need to record three things globally:
22;;; - The structure decribing the current debugger display
23;;; - The frame displays which are currently active (ie. visible)
24;;; - The husks of old debugger displays for reuse
25;;;
7a90c402 26(defvar *current-debug-display* nil)
27(defvar *debug-active-frames* nil)
28(defvar *old-display-frames* nil)
29
30\f
31
32;;;; Structures used by the graphical debugger
33
34(defstruct (debug-display
35 (:conc-name dd-info-)
36 (:print-function print-debug-display)
37 (:constructor make-debug-display
0f6159f4 38 (debug-pane errmsg restarts backtrace)))
7a90c402 39 (debug-pane nil :type (or null widget))
0f6159f4 40 (errmsg nil :type (or null widget))
7a90c402 41 (restarts nil :type (or null widget))
42 (backtrace nil :type (or null widget))
43 (level 0 :type fixnum)
44 (connection nil :type (or null xt::motif-connection)))
45
46(defun print-debug-display (info stream d)
47 (declare (ignore d))
48 (format stream "#<Debugger Display Info level ~d" (dd-info-level info)))
49
50\f
51
fe121aad 52;;;; Callback functions used by debugger
7a90c402 53
0f6159f4 54(defun quit-debugger-callback (widget call-data)
7a90c402 55 (declare (ignore widget call-data))
0f6159f4 56 (close-motif-debugger *debug-condition*)
7a90c402 57 (throw 'lisp::top-level-catcher nil))
58
59(defun restart-callback (widget call-data restart)
60 (declare (ignore widget call-data))
61 (invoke-restart-interactively restart))
62
63(defun stack-frame-callback (widget call-data frame)
64 (declare (ignore widget call-data))
65 (unless (assoc frame *debug-active-frames*)
66 ;; Should wrap this in a busy cursor
67 (debug-display-frame frame)))
68
7a90c402 69(defun close-all-callback (widget call-data)
70 (declare (ignore widget call-data))
71 (dolist (info *debug-active-frames*)
72 (destroy-widget (cdr info)))
73 (setf *debug-active-frames* nil))
74
fe121aad 75;;; This is to provide a means for recording the stack backtrace. In
76;;; particular, this is important for sending bug reports.
77;;;
78(defun dump-backtrace-callback (widget call-data)
79 (declare (ignore widget call-data))
80 (let ((*current-frame* (di:top-frame)))
81 (format t "~%Stack Backtrace:~%")
82 (backtrace)))
83
7a90c402 84(defun frame-view-callback (widget call-data thing)
85 (declare (ignore widget call-data))
86 ;; Should wrap this in a busy cursor
87 (inspect thing))
88
89(defun close-frame-callback (widget call-data frame)
90 (declare (ignore widget call-data))
91 (setf *debug-active-frames*
92 (delete frame *debug-active-frames*
93 :test #'(lambda (a b) (eql a (car b)))))
94 (destroy-interface-pane frame))
95
fe121aad 96(defun edit-source-callback (widget call-data frame)
7a90c402 97 (declare (ignore widget call-data))
fe121aad 98 (handler-case
99 (let ((*current-frame* frame))
100 (funcall (debug-command-p :edit-source)))
101 (error (cond)
92dc7a2d 102 (interface-error (safe-condition-message cond)))))
7a90c402 103
104(defun frame-eval-callback (widget call-data frame output)
105 (declare (ignore call-data))
106 (let* ((input (car (get-values widget :value)))
107 (mark (text-get-last-position output))
108 (response
109 (format nil "Eval>> ~a~%~a--------------------~%"
110 input
111 (handler-case
112 (multiple-value-bind
113 (out val)
114 (let ((*current-frame* frame))
115 (grab-output-as-string
116 (di:eval-in-frame frame (read-from-string input))))
117 (format nil "~a~s~%" out val))
118 (error (cond)
92dc7a2d 119 (safe-condition-message cond)))))
120 (length (length response)))
7a90c402 121 (declare (simple-string response))
122
123 (text-set-string widget "")
124 (text-insert output mark response)
125 ;; This is to make sure that things stay visible
126 (text-set-insertion-position output (+ length mark))))
127
128(defun source-verbosity-callback (widget call-data frame srcview delta)
129 (declare (ignore widget call-data))
130 (let* ((current (car (get-values srcview :user-data)))
131 (new (+ current delta)))
132 (when (minusp new)
133 (setf new 0))
134 (let ((source (handler-case
135 (grab-output-as-string
136 (print-code-location-source-form
137 (di:frame-code-location frame) new))
138 (di:debug-condition (cond)
139 (declare (ignore cond))
140 "Source form not available."))))
141 (set-values srcview
142 :label-string source
143 :user-data new))))
144
145\f
146
fe121aad 147;;; DEBUG-DISPLAY-FRAME-LOCALS -- Internal
148;;;
149;;; This sets up the display of the available local variables for the given
150;;; stack frame.
151;;;
7a90c402 152(defun debug-display-frame-locals (frame debug-fun location frame-view)
153 (let (widgets)
154 (if (di:debug-variable-info-available debug-fun)
155 (let ((any-p nil)
156 (any-valid-p nil))
157 (di:do-debug-function-variables (v debug-fun)
158 (unless any-p
159 (setf any-p t)
cfa32fc4 160 (push (create-label frame-view "localsLabel"
7a90c402 161 :font-list *header-font*
162 :label-string "Local variables:")
163 widgets))
164 (when (eq (di:debug-variable-validity v location) :valid)
165 (let ((value (di:debug-variable-value v frame))
166 (id (di:debug-variable-id v)))
167 (setf any-valid-p t)
168 (push
169 (create-value-box frame-view
170 (format nil " ~A~:[#~D~;~*~]:"
171 (di:debug-variable-name v)
172 (zerop id) id)
173 value
174 :callback 'frame-view-callback)
175 widgets))))
176 (cond
177 ((not any-p)
178 (push
cfa32fc4 179 (create-label frame-view "noLocals"
7a90c402 180 :font-list *italic-font*
181 :label-string
182 " No local variables in function.")
183 widgets))
184 ((not any-valid-p)
185 (push
cfa32fc4 186 (create-label frame-view "noValidLocals"
7a90c402 187 :font-list *italic-font*
188 :label-string
189 " All variables have invalid values.")
190 widgets))))
191
cfa32fc4 192 (push (create-label frame-view "noVariableInfo"
7a90c402 193 :font-list *italic-font*
194 :label-string
195 " No variable information available.")
196 widgets))
197 (apply #'manage-children widgets)))
198
fe121aad 199;;; DEBUG-DISPLAY-FRAME-PROMPT -- Internal
200;;;
201;;; Every frame window has a Frame Eval area. This function creates the
202;;; Eval area and attaches the necessary callbacks.
203;;;
7a90c402 204(defun debug-display-frame-prompt (frame frame-view)
205 (let* ((form (create-form frame-view "promptForm"))
cfa32fc4 206 (label (create-label form "framePrompt"
7a90c402 207 :label-string "Frame Eval:"
208 :font-list *header-font*))
209 (entry (create-text form "frameEval"
210 :top-attachment :attach-widget
211 :top-widget label
212 :left-attachment :attach-form
213 :right-attachment :attach-form))
214 (output (create-text form "frameOutput"
215 :edit-mode :multi-line-edit
216 :editable nil
217 :rows 8
218 :columns 40
219 :top-attachment :attach-widget
220 :top-widget entry
221 :bottom-attachment :attach-form
222 :left-attachment :attach-form
223 :right-attachment :attach-form)))
224
225 (manage-child form)
226 (manage-children label entry output)
227 (add-callback entry :activate-callback 'frame-eval-callback
228 frame output)))
229
fe121aad 230;;; DEBUG-DISPLAY-FRAME -- Internal
231;;;
232;;; Function to generate the graphical display for the given frame. Each
233;;; frame window is composed of the following parts:
234;;; - The function called
235;;; - The source form
236;;; - Local variables
237;;; - Frame Eval window
238;;;
7a90c402 239(defun debug-display-frame (frame)
240 (let* ((debug-fun (di:frame-debug-function frame))
241 (location (di:frame-code-location frame))
242 (name (di:debug-function-name debug-fun))
243 (title (format nil "Stack Frame: ~A" name))
244 (frame-shell (create-interface-pane-shell title frame))
245 (frame-view (create-row-column frame-shell "debugFrameView"))
246 (menu-bar (create-menu-bar frame-view "frameMenu"))
cfa32fc4 247 (fcall (create-label frame-view "frameCall"
7a90c402 248 :label-string
249 (format nil "Frame Call: ~a"
250 (grab-output-as-string
251 (print-frame-call frame)))))
252 (fbox (create-value-box frame-view "Function:"
253 name
254 :callback 'frame-view-callback
255 :client-data
256 (di:debug-function-function debug-fun)))
cfa32fc4 257 (slabel (create-label frame-view "sourceLabel"
7a90c402 258 :font-list *header-font*
259 :label-string "Source form:"))
260 (swindow (create-scrolled-window frame-view "frameSourceWindow"
261 :scrolling-policy :automatic
262 :scroll-bar-placement :bottom-right))
263
264 (source (handler-case
265 (grab-output-as-string
266 (print-code-location-source-form location 0))
267 (di:debug-condition (cond)
268 (declare (ignore cond))
269 "Source form not available.")))
cfa32fc4 270 (srcview (create-label swindow "sourceForm"
271 :alignment :alignment-beginning
272 :user-data 0
273 :label-string source))
7a90c402 274 (cascade1
275 (create-interface-menu menu-bar "Frame"
fe121aad 276 `(("Edit Source" edit-source-callback ,frame)
7a90c402 277 ("Expand Source Form" source-verbosity-callback ,frame ,srcview 1)
278 ("Shrink Source Form" source-verbosity-callback ,frame ,srcview -1)
279 ("Close Frame" close-frame-callback ,frame))))
280 (cascade2 (create-cached-menu menu-bar "Debug")))
281
282 (manage-child frame-view)
283 (manage-children menu-bar fcall fbox slabel swindow)
284 (manage-child srcview)
285 (manage-children cascade1 cascade2)
286
287 (debug-display-frame-locals frame debug-fun location frame-view)
288 (debug-display-frame-prompt frame frame-view)
289
290 (popup-interface-pane frame-shell)
291 (push (cons frame frame-shell) *debug-active-frames*)))
292
293\f
294
295;;;; Functions to display the debugger control panel
296
fe121aad 297;;; DEBUG-DISPLAY-ERROR -- Internal
298;;;
299;;; Fills in the given widget with the error message for the given
300;;; condition.
301;;;
7a90c402 302(defun debug-display-error (errmsg condition)
92dc7a2d 303 (set-values errmsg :label-string (safe-condition-message condition)))
7a90c402 304
fe121aad 305;;; DEBUG-DISPLAY-RESTARTS -- Internal
306;;;
307;;; Fills in a RowColumn box with buttons corresponding to the currently
308;;; active restarts.
309;;;
0f6159f4 310(defun debug-display-restarts (restart-view)
311 (let ((widgets (reverse (xti:widget-children restart-view)))
312 (used-ones))
313
7a90c402 314 (dolist (r *debug-restarts*)
0f6159f4 315 (let* ((label (format nil "~A" r))
316 (button (if widgets
317 (let ((w (pop widgets)))
318 (set-values w :label-string label)
319 (remove-all-callbacks w :activate-callback)
320 w)
321 (create-highlight-button restart-view
322 "restartButton"
323 label))))
324
7a90c402 325 (add-callback button :activate-callback 'restart-callback r)
0f6159f4 326 (push button used-ones)))
327 (apply #'manage-children used-ones)
328 (when widgets
329 (apply #'unmanage-children widgets))))
7a90c402 330
fe121aad 331;;; DEBUG-DISPLAY-STACK -- Internal
332;;;
333;;; Fills in a RowColumn box with buttons corresponding to the stack frames
334;;; found on the stack.
335;;;
7a90c402 336(defun debug-display-stack (backtrace)
0f6159f4 337 (let ((widgets (reverse (xti:widget-children backtrace)))
338 (used-ones)
339 (frames))
340
7a90c402 341 (do ((frame *current-frame* (di:frame-down frame)))
342 ((null frame))
0f6159f4 343 (push frame frames))
344 (setf frames (nreverse frames))
345
346 (dolist (frame frames)
347 (let* ((label (grab-output-as-string
348 (print-frame-call frame)))
349 (button (if widgets
350 (let ((w (pop widgets)))
351 (set-values w :label-string label)
352 (remove-all-callbacks w :activate-callback)
353 w)
354 (create-highlight-button
355 backtrace "stackFrame" label))))
7a90c402 356 (add-callback button :activate-callback 'stack-frame-callback frame)
0f6159f4 357 (push button used-ones)))
358 (apply #'manage-children used-ones)
359 (when widgets
360 (apply #'unmanage-children widgets))))
7a90c402 361
fe121aad 362;;; REALLY-CREATE-DEBUGGER -- Internal
363;;;
364;;; This creates all the widgets used by the main debugger window. It
365;;; calls various sub-functions such as DEBUG-DISPLAY-STACK to fill in the
366;;; various display sections. It should only be called if there are no old
367;;; debugger panes available for recycling.
368;;;
0f6159f4 369(defun really-create-debugger (condition)
7a90c402 370 (let* ((debug-pane (create-interface-pane-shell "Debugger" condition))
371 (frame (create-frame debug-pane "debugFrame"))
372 (form (create-form frame "debugForm"))
373 (menu-bar (create-menu-bar form "debugMenu"
374 :left-attachment :attach-form
375 :right-attachment :attach-form))
376 (cascade (create-cached-menu
377 menu-bar "Debug"
0f6159f4 378 '(("Close All Frames" close-all-callback)
fe121aad 379 ("Dump Backtrace" dump-backtrace-callback)
0f6159f4 380 ("Quit Debugger" quit-debugger-callback))))
cfa32fc4 381 (errlabel (create-label form "errorLabel"
7a90c402 382 :top-attachment :attach-widget
383 :top-widget menu-bar
384 :left-attachment :attach-form
385 :font-list *header-font*
386 :label-string "Error Message:"))
cfa32fc4 387 (errmsg (create-label form "errorMessage"
7a90c402 388 :top-attachment :attach-widget
389 :top-widget errlabel
390 :left-attachment :attach-form
391 :right-attachment :attach-form))
cfa32fc4 392 (rlabel (create-label form "restartLabel"
7a90c402 393 :top-attachment :attach-widget
394 :top-widget errmsg
395 :left-attachment :attach-form
396 :font-list *header-font*))
397 (restarts (create-row-column form "debugRestarts"
398 :adjust-last nil
399 :top-attachment :widget
400 :top-widget rlabel
401 :left-attachment :attach-form
402 :right-attachment :attach-form
403 :left-offset 10))
cfa32fc4 404 (btlabel (create-label form "backtraceLabel"
7a90c402 405 :label-string "Stack Backtrace:"
406 :font-list *header-font*
407 :top-attachment :attach-widget
408 :top-widget restarts
409 :left-attachment :attach-form))
410 (btwindow (create-scrolled-window form "backtraceWindow"
411 :scrolling-policy :automatic
412 :scroll-bar-placement :bottom-right
413 :left-attachment :attach-form
414 :right-attachment :attach-form
415 :left-offset 4
416 :right-offset 4
417 :bottom-offset 4
418 :bottom-attachment :attach-form
419 :top-attachment :attach-widget
420 :top-widget btlabel))
421 (backtrace (create-row-column btwindow "debugBacktrace"
422 :adjust-last nil
423 :spacing 1)))
424
425 (manage-child frame) (manage-child form)
426 (manage-children menu-bar errlabel errmsg rlabel restarts btlabel btwindow)
427 (manage-child backtrace)
428 (manage-child cascade)
429
430 (debug-display-error errmsg condition)
431
432 (if *debug-restarts*
433 (progn
434 (set-values rlabel :label-string "Restarts:")
435 (debug-display-restarts restarts))
436 (set-values rlabel :label-string "No restarts available"))
437
0f6159f4 438 (let ((quick-stack (create-highlight-button backtrace "quickStack"
439 "Display Stack")))
440 (add-callback quick-stack :activate-callback
441 #'(lambda (w c) (declare (ignore w c))
442 (debug-display-stack backtrace)))
443 (manage-child quick-stack))
444
7a90c402 445
446 (setf *current-debug-display*
0f6159f4 447 (make-debug-display debug-pane errmsg restarts backtrace))
7a90c402 448
449 (popup-interface-pane debug-pane)
450 debug-pane))
0f6159f4 451
fe121aad 452;;; REUSE-DEBUGGER -- Internal
453;;;
454;;; Takes an old debugger pane and a new condition. It renovates the old
455;;; display to reflect the current debugging state. This should be used
456;;; whenever possible since it is quite a bit faster than creating a new
457;;; debugger pane from scratch.
458;;;
0f6159f4 459(defun reuse-debugger (condition info)
460 (let ((debug-pane (dd-info-debug-pane info))
461 (errmsg (dd-info-errmsg info))
462 (restarts (dd-info-restarts info))
463 (backtrace (dd-info-backtrace info)))
464
465 (debug-display-error errmsg condition)
466 (debug-display-restarts restarts)
467
468 (let* ((buttons (xti:widget-children backtrace))
469 (quick-stack (car buttons)))
470 (remove-all-callbacks quick-stack :activate-callback)
471 (set-values quick-stack :label-string "Display Stack")
472 (add-callback quick-stack :activate-callback
473 #'(lambda (w c) (declare (ignore w c))
474 (debug-display-stack backtrace)))
475 (manage-child quick-stack)
476 (apply #'unmanage-children (cdr buttons)))
477
478 (setf *current-debug-display* info)
479 (popup-interface-pane debug-pane)
480 debug-pane))
481
fe121aad 482;;; CREATE-DEBUGGER -- Internal
483;;;
484;;; Creates a graphical debugger display for the given condition. It will
485;;; attempt to reuse any available old panes. However, if none are
486;;; available, it will create a new display frame.
487;;;
0f6159f4 488(defun create-debugger (condition)
489 (if *old-display-frames*
490 (reuse-debugger condition (pop *old-display-frames*))
491 (really-create-debugger condition)))
492
fe121aad 493;;; CLOSE-MOTIF-DEBUGGER -- Internal
494;;;
495;;; This function should always be called before leaving the context of the
496;;; debugger. It closes down the frame windows and marks the main debug
497;;; display pane as ready for recycling.
498;;;
7a90c402 499(defun close-motif-debugger (condition)
0f6159f4 500 (declare (ignore condition))
7a90c402 501 (push *current-debug-display* *old-display-frames*)
502 ;;
503 ;; Destroy all frame panes
504 (dolist (info *debug-active-frames*)
505 (destroy-widget (cdr info)))
506 (setf *debug-active-frames* nil)
507 ;;
0f6159f4 508 ;; Remove the restart/backtrace window
509 (popdown (dd-info-debug-pane *current-debug-display*))
7a90c402 510 (setf *current-debug-display* nil)
7a90c402 511
512 (format t "Leaving debugger.~%"))
513
fe121aad 514;;; INVOKE-MOTIF-DEBUGGER -- Internal
515;;;
516;;; This function essentially mimics the functions which manage the TTY
517;;; debugger, but uses a graphical debugging display instead.
518;;;
7a90c402 519(defun invoke-motif-debugger (condition)
fe121aad 520 (let* ((*in-the-debugger* t)
521 (frame (di:top-frame))
7a90c402 522 (previous-display *current-debug-display*)
523 (*current-debug-display* nil)
524 (*debug-active-frames* nil))
525 (declare (ignore previous-display))
526 (verify-system-server-exists)
527 (multiple-value-bind (shell connection)
528 (create-interface-shell)
529 (declare (ignore shell))
9befdffb 530 (if connection
531 (with-motif-connection (connection)
532 (let ((pane (find-interface-pane condition))
533 (*current-frame* frame))
534 (unless pane
535 (setf pane (create-debugger condition)))
536 (unless (is-managed pane)
537 (popup-interface-pane pane))
538 (setf (dd-info-level *current-debug-display*)
539 *debug-command-level*)
540 (setf (dd-info-connection *current-debug-display*) connection)
541 (unwind-protect
542 (handler-case
543 (loop
544 (system:serve-event))
545 (error (err)
546 (if *flush-debug-errors*
92dc7a2d 547 (interface-error (safe-condition-message err)
548 pane)
9befdffb 549 (interface-error
550 "Do not yet support recursive debugging"
551 pane))))
552 (when (and connection *current-debug-display*)
553 (with-motif-connection (connection)
554 (close-motif-debugger condition))))))
555 (invoke-tty-debugger condition)))))
7a90c402 556
557\f
558
a64ae2c1 559;;; Used to prevent recursive invocations of the windowing debugger.
560;;;
561(defvar *in-windowing-debugger* nil)
562
9befdffb 563
92dc7a2d 564;;; REAL-INVOKE-DEBUGGER -- Internal
565;;;
566;;; Invokes the Lisp debugger. It decides whether to invoke the TTY
567;;; debugger or the Motif debugger.
568;;;
569(defun real-invoke-debugger (condition)
570 (if (or (not (use-graphics-interface))
571 *in-windowing-debugger*
572 (typep condition 'xti:toolkit-error))
573 (invoke-tty-debugger condition)
574 (let ((*in-windowing-debugger* t))
575 (write-line "Invoking debugger...")
576 (invoke-motif-debugger condition))))