[comp.emacs] elisp debugging

liberte@M.CS.UIUC.EDU (Daniel LaLiberte) (11/19/88)

Here is edebug.el.  This is a debugger for GNU Emacs Lisp that allows
you to step through code, set breakpoints by pointing at the text, etc.

I am posting it because it appears to be reliable
and I expect people will want to use it.  I posted it to bug-gnu-emacs
several months ago and didnt get any bug reports.

Dan LaLiberte
uiucdcs!liberte
liberte@cs.uiuc.edu
liberte%a.cs.uiuc.edu@uiucvmd.bitnet

==================================================

;; edebug.el  electric-debug mode or elisp-debug mode.
;; Copyright (C) 1988 Free Software Foundation, Inc. and Daniel LaLiberte

;; This file is part of GNU Emacs. - or could be if they want it.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.

;;;================================================================
;;; This mode allows the user to step through elisp source code
;;; while executing, set breakpoints, etc.  It works very well so far,
;;; but below is a list of things still to do.  Help yourself 
;;; if you feel inclinded to make enhancements and
;;; let me know if you come up with any ideas, bugs, or fixes.

;;; Daniel LaLiberte   217-333-7937
;;; University of Illinois, Urbana-Champaign
;;; Department of Computer Science
;;; 1304 W Springfield
;;; Urbana, IL  61801

;;; uiucdcs!liberte
;;; liberte@cs.uiuc.edu
;;; liberte%a.cs.uiuc.edu@uiucvmd.bitnet

;;;========================================================================

;;; To use edebug, simply evaluate a defun with edebug-defun.  Before
;;; calling the function, the buffer that the function is defined in
;;; must be available.  Then call the function normally.  In the debugger
;;; try the "h" command for a list of commands.  

;;; Initially, you will be in step mode which stops before every
;;; expression.  Continue to the next step with " " or "d".
;;; You can change to trace mode, which pauses one second before each
;;; expression, by using the "t" command.  "T" is a fast trace with
;;; zero pause time.  You can set a breakpoint with "b" and clear it
;;; with "B" inside the debugger or "C-xXb" and "C-xXB" outside.
;;; "c" continues until a break.  "C" continues ignoring breaks.
;;; During trace or continue modes, hit any character to interrupt.
;;; You can view the window configuration outside of edebug with "v"
;;; and pop back to the edebug windows with "w" in a edebug window or
;;; "C-xXw" in any window.  Quit to top level with "q" or abort one
;;; level with "a".

;; Put the following two forms in your .emacs file.  If you desire, change
;; the global-set-key to local-set-key inside your emacs-lisp-mode-hook.  

;;(global-set-key "\^Xx" 'edebug-defun)
;;(autoload 'edebug-defun "edebug")

;; Use setq to change the value of global-edebug-prefix before loading 
;; edebug.el.
(defvar global-edebug-prefix "\^XX"
  "Prefix key for global edebug commands.")

(defvar allow-recursive-debug t
  "*If non-nil, recursive calls to edebug are not questioned.")


;;;=====================================================================
;; todo list  * means difficult; may require mods to emacs
;; -------------------------------------------------------
;; Bug: occasionally, your elisp buffer will remain read-only.
;; *Annoyance: the debugging mode (trace, step, etc) is not reset on restart.
;; *Call edebug on exit from a function call.
;; *Previous result command.
;; Command to not descend but step to next call at same or higher level.
;;   Maybe augment mark-list with next-at-same-level pointers.
;;   *Maybe use some trick with backtrace-debug.
;; Make it work with selective display - dont stop in hidden lines
;; Command to display backtrace.
;; Handle macros?
;; Handle interactive form if the argument is not a string.
;; Per-function debugging mode?
;; Customizable default modes.
;; Should overlay-arrow-position and -string be buffer local?
;; Explicit calls to edebug in user code.
;; Use just one mark per function; compute relative position.
;;   Or allow arbitrary editing of buffer - switch to outside keymap.
;; *Need to be able to recognize if a command has been executed at
;;   a lower level since edebug was last called.
;; Remember window configuration inside debugger between edebug calls
;;   and remember original configuration on the first call to edebug
;;   after an interactive command at a lower level.
;; Write a replacement for, or fix, pop-to-buffer to avoid window-hopping.
;; Use copy of current-local-map instead of emacs-lisp-mode-map
;;   (but only copy the first time after lower level command - to save time).
;; *Better integration with standard debug.
;; *Catch errors that would go to debug if in an edebug function.
;; Use inhibit-quit while edebugging?  
;; Crawl mode would sit-for 0 or 1 in the outside window configuration
;;   between each edebug step.
;;   Maybe it should be a separate option that applies to trace as well.
;; Customizable sit-for time for trace and crawl modes.
;; Pause and continue at breakpoints if in continue mode?
;;   (be sure to catch keyboard interrupt in time).
;; Conditional breakpoints.  Enter expression to be evaluated.
;;   Counting conditions - nth time through.
;;   Environmental conditions - like nesting depth.
;; minibuffer mode - show the current line in the minibuffer instead.
;; Trace to buffer mode - print enclosing function and function at point.
;; Performance monitoring - summarize trace data.
;; Check that buffers havent disappeared under us.
;; List side effects of using edebug, e.g. last-command, undo, etc.
;; Handle expressions outside of defuns.  Store mark-list in buffer-local var.
;; Debug just one subexpression of a defun - the rest is evalled normally.
;; Speed up edebug-defun.
;; *Investigate whether eval could do what edebug-defun does, only alot faster.
;; Shadow eval-defun or eval-region temporarily.

;;;======================================================================
;;; The first half of this file is the edebug compiler, edebug-defun.
;;; The second half is the debugger, edebug and edebug-mode.
;;; Some general utilities are at the end.

;(setq debug-on-error t)



(defun edebug-defun ()
  "Evaluate the defun that point is in or before,
but set it up for edebug.  Print its name in the minibuffer."
  (interactive)
  (let ((tmp-point (point))
	(defun-name)
	(defun-args)
	(defun-doc-string)
	(defun-interactive)
	(parse-sexp-ignore-comments t)
	sexp token
	(edebug-sexp-count 0)
	(edebug-mark-list nil)
	)

    (save-excursion
      (end-of-defun)
      (beginning-of-defun)
      (down-list 1)

      (if (not (eq 'defun (edebug-read-one)))
	  (error "Not in a defun"))
      (setq defun-name (edebug-read-one))
      (if (not (symbolp defun-name))
	  (error "Bad defun name."))
      (setq defun-args (edebug-read-one))
      (if (not (listp defun-args))
	  (error "Bad arg list."))

      (setq tmp-point (point))
      (if (eq 'atom (edebug-next-token-class))
	  (setq token (edebug-read-one)))
      (if (stringp token)
	  (progn
	    (setq defun-doc-string token)
	    (setq tmp-point (point))
	    ))

      (if (eq 'lparen (edebug-next-token-class))
	  (progn
	    (forward-char 1)
	    (if (eq 'atom (edebug-next-token-class))
		(progn
		  (setq token (edebug-read-one))
		  (goto-char tmp-point)
		  (if (eq 'interactive token)
		      (progn
			(setq defun-interactive (edebug-read-one))
			(setq tmp-point (point))
			)
		    )))))

      (goto-char tmp-point)

      ;; the remainder is a list of sexps
      (fset defun-name (` (lambda
			    (, defun-args)
			    (, defun-doc-string)
			    (, defun-interactive)
			    (,@ (edebug-sexp-list t))
			    )))
      )

    (put defun-name 'edebug
	 (cons nil (vconcat (nreverse edebug-mark-list)))
	 )
    (message "edebug: %s" defun-name)
    ))



(defun edebug-sexp  ()
  "Return the debug form for the following sexp.  Move point to
immediately after the sexp."

  (let (sexp
	class
	mark
	count
	)
    (setq mark (point-marker)
	  count (1- (setq edebug-sexp-count (1+ edebug-sexp-count)))
	  edebug-mark-list (cons mark edebug-mark-list))
    (setq sexp
	  (cond
	   ((eq 'lparen (edebug-next-token-class)) (edebug-form))
	   (t (edebug-read-one))
	   ))
    (list 'progn
	  (list 'edebug (list 'quote defun-name) count) 
	  sexp)
    ))
	  

(defun edebug-sexp-list (debuggable)
  "Return a list built from the sexp list following point in the
current buffer. If DEBUGGABLE then wrap edebug calls around each sexp.
Leave point before the trailing right paren."

  (let ((sexp-list)
	sexp
	class
	)
    (while (not (eq 'rparen (setq class (edebug-next-token-class))))

      (setq sexp (if debuggable
		     (edebug-sexp)
		   (edebug-read-one)))

;;      (message "sexp after debug: %s" sexp) (sit-for 2)
      (setq sexp-list (cons sexp sexp-list))
      )
    (nreverse sexp-list)
    ))


(defun edebug-form ()
  "Return a list built from the following list form with debug calls inserted
as appropriate to the form.  Leave point after the right paren."
  (let ((beginning (point))
	token)

    (forward-char 1)    ; skip \(
    (if (eq 'atom (edebug-next-token-class))
	(setq token (edebug-read-one))
      (error "Bad form."))
;;    (message "head token: %s" token) (sit-for 2)

    (prog1
	(cons token
	  (cond
	   ;; handle all special cases with unevaluated arguments - any more??
	   ((memq token '(let let*)) (edebug-let))
	   ((memq token '(setq setq-default)) (edebug-setq))
	   ((eq token 'cond) (edebug-cond))
	   ((eq token 'condition-case) (edebug-condition-case))

	   ((memq token '(quote function defun defvar defconst defmacro))
	    (edebug-sexp-list nil))

	   ;; is it a lisp macro?
	   ((macrop token) (edebug-sexp-list nil))

	   (t (edebug-sexp-list t))
	   ))
      (forward-char 1) ; skip \)
      )
    ))


(defun edebug-let ()
  (let (var-value-list
	token
	class)

    (cons
     ;; first process the var/value list
     (if (not (eq 'lparen (edebug-next-token-class)))
	 (edebug-read-one)

       (forward-char 1)			; lparen
       (while (not (eq 'rparen (setq class (edebug-next-token-class))))
	 (setq var-value-list
	       (cons
		(if (not (eq 'lparen class))
		    (edebug-read-one)
		  (forward-char 1)	; lparen
		  (prog1
		      (edebug-var-value)
		    (forward-char 1))	; rparen
		  )
		var-value-list)))
       (forward-char 1)			; rparen
       (nreverse var-value-list)
       )
     
     ;; now process the expression list
     (progn
       (edebug-next-token)
       (edebug-sexp-list t))
     )))

(defun edebug-var-value ()
  "Return the debug form of the var and value that follow point."
  (list
   (edebug-read-one) ; the variable
   (and (not (eq 'rparen (edebug-next-token-class)))
	(edebug-sexp))
   ))
		     
(defun edebug-setq ()
  "Return the debug form of the setq var-value list."
  (let (var-value-list)
    (while (not (eq 'rparen (edebug-next-token-class)))
      (setq var-value-list
	    (append var-value-list
		    (edebug-var-value))))
    var-value-list
    ))

(defun edebug-cond ()
  "Return the debug form of the cond condition-expressionlist pairs."
  (let (value-value-list
	class)
    (while (not (eq 'rparen (setq class (edebug-next-token-class))))
      (setq value-value-list
	    (cons
	     (if (not (eq 'lparen class))
		 (error "Bad condition in cond")
	       (forward-char 1) ; \(
	       (prog1
		   (list
		    (edebug-sexp)
		    (if (eq 'rparen (edebug-next-token-class))
			nil
		      (edebug-sexp-list t)))
		 (if (not (eq 'rparen (edebug-next-token-class)))
		     (error "Bad condition in cond"))
		 (forward-char 1) ; \)
		 ))
	     value-value-list))
      )
    (nreverse value-value-list)
    ))

(defun edebug-condition-case ()
  "Return the debug form of a condition-case."
  (let (symb-sexp-list
	class)
    (cons
     (if (not (eq 'atom (edebug-next-token-class)))
	 (error "Bad variable in condition-case")
       (edebug-read-one)
       (edebug-next-token)
       )

     (cons
      (edebug-sexp)  ; the form

      ;; process handlers
      (progn
	(while (not (eq 'rparen (setq class (edebug-next-token-class))))
	  (setq symb-sexp-list
		(cons
		 (if (not (eq 'lparen class))
		     (error "Bad handler in condition-case")
		   (forward-char 1)	; \(
		   (prog1
		       (edebug-var-value)
		     (forward-char 1))	; \)
		   )
		 symb-sexp-list)))
	(nreverse symb-sexp-list)
	)))))



;;------------------------------------------------
;; Parser utilities

(defun edebug-next-token ()
  "Leave point before the next token skipping comments."
  (skip-chars-forward " \t\r\n\f")
  (while (= (following-char) ?\;)
      (end-of-line)
      (skip-chars-forward " \t\r\n\f")
      ))

(defun edebug-read-one ()
  "Read one sexp from the current buffer starting at point.  Leave
point immediately after it.  A sexp can be a list or atom (symbol,
character, string, vector).  Sexps are translated to internal form."
  ;;  This is gummed up by parser inconsistencies (bugs?)
  (let ((parse-sexp-ignore-comments t))
    (edebug-next-token)
    (if (= (following-char) ?\[)  ; scan-sexps doesnt read vectors correctly
	(read (current-buffer))   ; but read does
      (prog1
	  (save-excursion
	    ;; read goes one too far if (quoted) string or symbol
	    ;; is immediately followed by non-whitespace
	    (read (current-buffer)))
	(goto-char (scan-sexps (point) 1)))
      )))

(defun edebug-next-token-class ()
  "Move to the next token and return its class.  We only care about
parens, dot, quote, and (anything else) atom."
  (edebug-next-token)
  (cond
   ((= (following-char) ?\() 'lparen)
   ((= (following-char) ?\)) 'rparen)
   ((= (following-char) ?\.) 'dot)
   ((= (following-char) ?\') 'quote)
   (t 'atom)))


;;; The next two defs are not used any more, but make interesting reading.

;;; (defvar edebug-defun-name nil
;;;   "Global variable for use by edebug-wrapper since names are bound
;;; at byte-compile time.")

;;; defmacros must appear before macro calls in order to byte-compile.
   
;;; (defmacro edebug-wrapper (count sexp)
;;;   "Wrap the COUNTth SEXP in a call to edebug.  
;;; Uses global edebug-defun-name."
;;;   (` (list 'progn 
;;;	   (list 'edebug (list 'quote '(, edebug-defun-name)) (, count))
;;;	   (, sexp))
;;;      ))



;;;=================================================================
;;; The debugger itself

;;; add minor-mode-alist entry
(or (assq 'edebug-active minor-mode-alist)
    (setq minor-mode-alist (cons (list 'edebug-active " *EDebugging*")
				 minor-mode-alist)))

(defconst edebug-arrow-alist
  '((continue . "*>")
    (trace . "->")
    (fast . ">")
    (step . "=>"))
  "Association list of arrows for each edebug mode.")




(defvar edebug-depth 0
  "Current debug editing depth.")

;; These variables need to be maintained between edebug calls.
;; But recursive edebug calls could confuse them.

(defvar edebug-window-start 0
  "Remember where each buffers' window starts.  This is to avoid
spurious recentering and also to auto adjust window start.")
;; what about a buffer with more than one window?
(setq-default edebug-window-start 0)
(make-variable-buffer-local 'edebug-window-start)

(defvar edebug-inside-window-configuration nil
  "Configuration of debugger windows.")

(defvar edebug-outside-window-configuration nil
  "Configuration of windows before debugger.")

;;-------------------------------------------------

(defun edebug (edebug-func edebug-mark-index)
  "Debug FUNC.  The position of the current sexp is given by MARK-INDEX
which is used to index a mark in the mark vector in the functions
'edebug property.  edebug is called from functions compiled with edebug-defun."

  ;; remember, nothing is safe until save-excursion etc.
  (if (or (= 0 edebug-depth) allow-recursive-debug
	  (y-or-n-p "Recursive debug?? "))
      (if (and (eq edebug-mode 'sleep) (not (input-pending-p)))
	  nil

	;; some variables are declared here to allow recursive debugging
	(let ((edebug-match-data (match-data))
	      edebug-outside-map ; keymap before edebug
	      (edebug-outside-buffer (current-buffer))
	      edebug-read-only
	      (edebug-outside-point (point))
	      (edebug-active t)
	      edebug-buffer
	      (edebug-step-after-exit nil)
	      (cursor-in-echo-area nil)	; ??
	      (edebug-data  (get edebug-func 'edebug))
	      edebug-breakpoints
	      edebug-break		; true if we should break now
	      edebug-mark
	      edebug-recursive

	      (edebug-outside-arrow-position overlay-arrow-position)
	      (edebug-outside-arrow-string overlay-arrow-string)
	      )

	  (save-excursion
	    (unwind-protect
		(progn
		  (setq edebug-outside-window-configuration
			(current-window-configuration))
		  (setq edebug-depth (1+ edebug-depth))
		  (setq edebug-breakpoints (car edebug-data))
		  (setq edebug-mark (aref (cdr edebug-data)
					  edebug-mark-index))
		  (setq edebug-buffer (marker-buffer edebug-mark))
		  (setq edebug-break
			(memq edebug-mark-index edebug-breakpoints))

		  (if (or (not (eq 'continue edebug-mode))
			  edebug-break
			  (input-pending-p))
		      (progn
			(if (input-pending-p)
			    (progn
			      ;;(setq edebug-mode 'step)
			      (setq edebug-recursive t)
			      (message "Interrupted")
			      ;;(discard-input) ; can we use this input safely?
			      ;;(sit-for 1)
			      ))

			;;(make-local-variable 'overlay-arrow-position)
			;;(make-local-variable 'overlay-arrow-string)


;;; Dont use the following since we dont know if
;;; this is the first time in debug since last command at lower level.
;;; 		        (if edebug-inside-window-configuration
;;;			  (set-window-configuration 
;;;		             edebug-inside-window-configuration))

			;; avoid window-hopping for > 2 windows
			;; doesnt catch all cases yet
			(if (and (not (get-buffer-window edebug-buffer))
				 (not (eq (selected-window)
					  (next-window (next-window
							(selected-window))))))
			    (progn
;			      (message "flipping windows")  (sit-for 2)
			      (select-window (get-lru-window))
			      ))

			;; a substitute for pop-to-buffer might be simpler
			(pop-to-buffer edebug-buffer)
			(set-buffer edebug-buffer)
			(goto-char edebug-mark)

			;; adjust window to fit as much as possible
			(set-window-start (selected-window)
					  edebug-window-start)
			(if (not (pos-visible-in-window-p))
			    (let ((start (window-start)))
			      (set-window-start
			       (selected-window)
			       (setq edebug-window-start
				     (save-excursion
				       (forward-line
					(if (< edebug-mark start) -1
					  (- (/ (window-height) 2))))
				       (beginning-of-line)
				       (point))))))
			    
			(edebug-overlay-arrow)
			;; (sit-for 0)

			(if (and (not edebug-break)
				 (memq edebug-mode '(trace fast)))
			    (if (eq edebug-mode 'trace)
				(sit-for 1) ; parameterize time
			      (sit-for 0))
			  (if (and edebug-break
				   (not (eq 'step edebug-mode)))
			      (message "Break"))
			  (setq edebug-recursive t)
			  )))
	      
		  (if edebug-recursive
		      (progn
			(edebug-mode)
			(setq edebug-read-only buffer-read-only)
			(let ((buffer-read-only t)) ; could make this optional
			  (setq edebug-inside-window-configuration
				(current-window-configuration))
			  (recursive-edit)
			  (set-buffer edebug-buffer) ; may have changed buffers
			  )))
		  )
	    
	      ;; unwind-protect cleanup
	      (if edebug-recursive
		  (progn
		    (save-excursion
		      (set-buffer edebug-buffer)
		      (use-local-map edebug-outside-map))
		      (if (not (eq buffer-read-only edebug-read-only))
			  (error "This should not happen! Read only changed from %s to %s"
				 edebug-read-only
				 buffer-read-only))
		    ))

;;	      (message "Unwinding. depth=%d" (recursion-depth)) (sit-for 2)
	      (restore-match-data edebug-match-data)
	      (setq edebug-depth (1- edebug-depth))
	      (set-window-configuration edebug-outside-window-configuration)

	      (setq overlay-arrow-position edebug-outside-arrow-position)
	      (setq overlay-arrow-string edebug-outside-arrow-string)

	      ) ; unwind-protect
	    )))))

(defun edebug-overlay-arrow ()
  "Set up the overlay arrow at beginning-of-line in current buffer.
The arrow string is derived from edebug-arrow-alist and edebug-mode."
  (let* ((pos))
    (save-excursion
      (beginning-of-line)
      (setq pos (point)))
    (setq overlay-arrow-string
	  (cdr (assq edebug-mode edebug-arrow-alist)))
    (setq overlay-arrow-position (make-marker))
    (set-marker overlay-arrow-position pos (current-buffer))
    ))




(defun edebug-modify-breakpoint (flag)
  "Modify the breakpoint for the form at point or after it according to
FLAG: set if t, clear if nil.  Then move to that point."
  ;; must find the right debugger data first
  (let (defun-name edebug-data edebug-breakpoints)
    (save-excursion
      (end-of-defun)
      (beginning-of-defun)
      (down-list 1)
      (forward-sexp 1)
      (setq defun-name (read (current-buffer))))

    (setq edebug-data (get defun-name 'edebug))
    (if (not edebug-data)
	(error "%s must first be evaluated with edebug-defun." defun-name))

    (setq edebug-breakpoints (car edebug-data))
  
    (let ((mark-vector (cdr edebug-data))
	  (pnt (point))
	  len i)
      ;; assume that the marks are in order
      (setq len (length mark-vector))
      (setq i 0)
      (while (and (< i len) (> pnt (aref mark-vector i)))
	(setq i (1+ i)))
      (if (<= pnt (aref mark-vector i))
	  (progn  ; found it
	    (setq edebug-breakpoints (delq i edebug-breakpoints))
	    (if flag
		(progn
		  (setq edebug-breakpoints (cons i edebug-breakpoints))
		  (message "Breakpoint set."))
	      (message "Breakpoint cleared."))
	    
	    (setq edebug-data
		  (cons edebug-breakpoints mark-vector))
	    (put defun-name 'edebug edebug-data)
	    (goto-char (aref mark-vector i))
	    ))
      )))

(defun edebug-set-breakpoint ()
  "Set the breakpoint of sexp following point."
  (interactive)
  (edebug-modify-breakpoint t))

(defun edebug-clear-breakpoint ()
  "Clear the breakpoint of sexp following point."
  (interactive)
  (edebug-modify-breakpoint nil))


(defun edebug-step-through ()
  "Proceed to next debug step."
  (interactive)
  (setq edebug-mode 'step)
  (if (< 0 (recursion-depth))
      (if (eq (current-buffer) edebug-buffer)
	  (exit-recursive-edit))
    (message "edebug will stop before next eval.")))

(defun edebug-sleep ()
  "Continue, evaluating without debugging."
  (interactive)
  (message "Sleep...")
  (setq edebug-mode 'sleep)
  (if (< 0 (recursion-depth))
      (if (eq (current-buffer) edebug-buffer)
	  (exit-recursive-edit))
    (message "edebug will sleep through breaks.")))

(defun edebug-continue ()
  "Continue, evaluating until break."
  (interactive)
  (message "Continue...")
  (setq edebug-mode 'continue)
  (if (< 0 (recursion-depth))
      (if (eq (current-buffer) edebug-buffer)
	  (exit-recursive-edit))
    (message "edebug will continue until break." )))

(defun edebug-trace-fast ()
  "Trace with no wait at each step."
  (interactive)
  (message "Trace fast...")
  (setq edebug-mode 'fast)
  (if (< 0 (recursion-depth))
      (if (eq (current-buffer) edebug-buffer)
	  (exit-recursive-edit))
    (message "edebug will trace without pause.")))

(defun edebug-trace ()
  "Begin trace mode."
  (interactive)
  (message "Tracing...")
  (sit-for 1)  ; or delay time
  (setq edebug-mode 'trace)
  (if (< 0 (recursion-depth))
      (if (eq (current-buffer) edebug-buffer)
	  (exit-recursive-edit))
    (message "edebug will trace with pause.")))


(defun edebug-where ()
  "Show the debug windows and where we stopped in program."
  (interactive)
  (set-window-configuration edebug-inside-window-configuration)
  (set-buffer edebug-buffer)
  (select-window (get-buffer-window edebug-buffer))  ; is this needed?
  (goto-char edebug-mark)
  (message "Window configuration inside of edebug.")
  )

(defun edebug-view-outside ()
  "Show the windows before edebug was called."
  (interactive)
  (set-window-configuration edebug-outside-window-configuration)
  (goto-char edebug-outside-point)
  (message "Window configuration outside of edebug.  Return with %s"
	   (substitute-command-keys "\\<global-map>\\[edebug-where]"))
  )

(defun edebug-bounce-point ()
  "Show the point in the outside current buffer by bouncing.  If the
buffer has a current window, then dont do a set-window-configuration"
  (interactive)
  (save-excursion
    (if (not (get-buffer-window edebug-outside-buffer))
	(progn
	  (set-window-configuration edebug-outside-window-configuration)
	  (sit-for 1)
	  (set-window-configuration edebug-inside-window-configuration)
	  )
      (save-window-excursion
	(select-window (get-buffer-window edebug-outside-buffer))
	(goto-char edebug-outside-point)
	(sit-for 0)  ; I want to sit one second after update is finished!
	(sit-for 1))
      )))

(defun edebug-interrupt ()
  "Useful for exiting from trace loop."
  (interactive)
  (message "Interrupted")
  (ding t))
      

(defun edebug-eval-expression (exp)
  "Prompt and evaluate an expression"
  (interactive "xEval: ")
  (save-excursion
    (if (null (buffer-name edebug-outside-buffer))
	;; outside buffer deleted
	(setq edebug-outside-buffer (current-buffer)))
    (set-buffer edebug-outside-buffer)
    (eval-expression exp)))


;;;------------------
;;; edebug-mode stuff

(defvar edebug-mode-map nil)

(if edebug-mode-map
    nil
  (let ((loop ? ))  ; what's this for?

    ;; should use copy of current local map
    (setq edebug-mode-map (copy-keymap emacs-lisp-mode-map))
;;    (suppress-keymap edebug-mode-map)
    (define-key edebug-mode-map "-" 'negative-argument)
    (define-key edebug-mode-map "c" 'edebug-continue)
    (define-key edebug-mode-map "C" 'edebug-sleep)
    (define-key edebug-mode-map "t" 'edebug-trace)
    (define-key edebug-mode-map "T" 'edebug-trace-fast)
    (define-key edebug-mode-map " " 'edebug-step-through)
    (define-key edebug-mode-map "d" 'edebug-step-through)
    (define-key edebug-mode-map "w" 'edebug-where)
    (define-key edebug-mode-map "v" 'edebug-view-outside)
    (define-key edebug-mode-map "p" 'edebug-bounce-point)
    (define-key edebug-mode-map "h" 'edebug-help)
    (define-key edebug-mode-map "?" 'edebug-help)
    (define-key edebug-mode-map "q" 'top-level)
    (define-key edebug-mode-map "a" 'abort-recursive-edit)
    (define-key edebug-mode-map "e" 'edebug-eval-expression)
    (define-key edebug-mode-map "b" 'edebug-set-breakpoint)
    (define-key edebug-mode-map "B" 'edebug-clear-breakpoint)
    (define-key edebug-mode-map "i" 'edebug-interrupt)
    (define-key edebug-mode-map "\^g" 'edebug-interrupt)
    ))


(defvar global-edebug-map (make-sparse-keymap))

(define-key global-edebug-map "d" 'edebug-step-through)
(define-key global-edebug-map "c" 'edebug-continue)
(define-key global-edebug-map "C" 'edebug-sleep)
(define-key global-edebug-map "t" 'edebug-trace)
(define-key global-edebug-map "T" 'edebug-trace-fast)

(define-key global-edebug-map "b" 'edebug-set-breakpoint)
(define-key global-edebug-map "B" 'edebug-clear-breakpoint)
(define-key global-edebug-map "w" 'edebug-where)

(global-set-key global-edebug-prefix global-edebug-map)


(defvar edebug-mode 'step
  "Current edebug mode set by user.")


(defun edebug-help ()
  (interactive)
  (describe-function 'edebug-mode))



(defun edebug-mode ()
  "Mode for elisp buffers while in edebug.  Under construction.

There are both buffer local and global key bindings to several
functions.  E.g. edebug-step-through is bound to
\\[edebug-step-through] in the debug buffer and
\\<global-map>\\[edebug-step-through] in any buffer.

Global commands prefixed by global-edbug-prefix:
\\{global-edebug-map}
Debugger buffer commands
\\{edebug-mode-map}
"
  (setq edebug-outside-map (current-local-map))
  (use-local-map edebug-mode-map)
  )


;;;--------------------
;;; Utilities

(defun window-list ()
  "Return a list of window, in order of next-window."
  (let* ((first-window (selected-window))
	 (window-list (list first-window))
	 (next (next-window first-window)))
    (while (not (eq next first-window))
      (setq window-list (cons next window-list))
      (setq next (next-window next)))
    (nreverse window-list)))


(defun restore-match-data (data)
  "Restore the match data DATA safely."
  (catch 'foo
    (let ((d data))
      (while d
	(and (car d)
	     (null (marker-buffer (car d)))
	     ;; match-data buffer is deleted.
	     (throw 'foo nil))
	(setq d (cdr d)))
      (store-match-data data)
      )))


(defun macrop (object)
  "Return true if OBJECTs function is a lisp macro, directly or indirectly."
  (while (and (symbolp object) (fboundp object))
    (setq object (symbol-function object)))
  (and (listp object)
       (eq 'macro (car object)))
  )

grunwald@m.cs.uiuc.edu (11/21/88)

re: edebug

	I'd like to give a testimonial for ``edebug''. It makes debugging
so easy that I now use Emacs for things where I used to use Awk, sed, etc.
It drastically reduces debugging time, cures warts and is fun to use.

Dirk ``A Statisfied Customer'' Grunwald