diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el index 6c7a0d2..d8596aa 100644 --- a/lisp/emacs-lisp/debug.el +++ b/lisp/emacs-lisp/debug.el @@ -494,9 +494,13 @@ removes itself from that hook." (forward-line 1) (while (progn (forward-char 2) - (if (= (following-char) ?\() - (forward-sexp 1) - (forward-sexp 2)) + (cond ((debugger--locals-visible-p) + (goto-char (next-single-char-property-change + (1+ (point)) 'debugger-locals-visible-p))) + ((= (following-char) ?\() + (forward-sexp 1)) + (t + (forward-sexp 2))) (forward-line 1) (<= (point) opoint)) (if (looking-at " *;;;") @@ -558,6 +562,89 @@ The environment used is the one when entering the activation frame at point." (prin1 val t) (let ((str (eval-expression-print-format val))) (if str (princ str t)))))))) + +(defun debugger--backtrace-base () + "Return the function name that marks the top of the backtrace. +See `backtrace-frame'." + (cond ((eq 'debug--implement-debug-on-entry + (cadr (backtrace-frame 1 'debug))) + 'debug--implement-debug-on-entry) + (t 'debug))) + +(defun debugger--locals-visible-p () + "Are the local variables of the current stack frame visible?" + (save-excursion + (move-to-column 2) + (get-text-property (point) 'debugger-locals-visible-p))) + +(defun debugger--insert-locals (locals) + "Insert the local variables LOCALS at point." + ;; A bit messy because we need to deal with lexical frames, i.e. + ;; the ones with the magic symbol 'internal-interpreter-environment. + (when (null locals) + (insert "\n [no locals]")) + (dolist (s+v locals) + (let ((symbol (car s+v)) + (value (cdr s+v)) + (print-escape-newlines t) + (insert (lambda (s v) + (insert "\n ") + (prin1 s (current-buffer)) + (insert ": ") + (prin1 v (current-buffer))))) + (cond ((string= (symbol-name symbol) "internal-interpreter-environment") + (cond ((or (null value) + (and (null (cdr value)) + (symbol (car value)))) + (insert "\n [no locals]")) + (t + (dolist (s+v value) + (unless (symbolp s+v) + (let ((symbol (car s+v)) + (value (cdr s+v))) + (funcall insert symbol value))))))) + (t + (funcall insert symbol value)))))) + +(defun debugger--show-locals () + "For the frame at point, insert locals and add text properties." + (let* ((nframe (debugger-frame-number 'skip-base)) + (base (debugger--backtrace-base)) + (locals (backtrace-locals nframe base)) + (inhibit-read-only t)) + (save-excursion + (let ((start (progn + (beginning-of-line) + (skip-chars-forward " ") + (point)))) + (end-of-line) + (debugger--insert-locals locals) + (add-text-properties start (point) '(debugger-locals-visible-p t)))))) + +(defun debugger--hide-locals () + "Delete local variables and remove the text property." + (let* ((col (current-column)) + (end (progn + (move-to-column 2) + (next-single-char-property-change + (point) 'debugger-locals-visible-p))) + (start (previous-single-char-property-change + end 'debugger-locals-visible-p)) + (inhibit-read-only t)) + (goto-char start) + (end-of-line) + (remove-text-properties start end '(debugger-locals-visible-p)) + (delete-region (point) end) + (move-to-column col))) + +(defun debugger-toggle-locals () + "Show or hide local variables of the current stack frame." + (interactive) + (cond ((debugger--locals-visible-p) + (debugger--hide-locals)) + (t + (debugger--show-locals)))) + (defvar debugger-mode-map (let ((map (make-keymap)) @@ -575,6 +662,7 @@ The environment used is the one when entering the activation frame at point." (define-key map "h" 'describe-mode) (define-key map "q" 'top-level) (define-key map "e" 'debugger-eval-expression) + (define-key map "t" 'debugger-toggle-locals) (define-key map " " 'next-line) (define-key map "R" 'debugger-record-expression) (define-key map "\C-m" 'debug-help-follow) diff --git a/src/eval.c b/src/eval.c index d3fcec5..a455d95 100644 --- a/src/eval.c +++ b/src/eval.c @@ -3576,6 +3576,53 @@ NFRAMES and BASE specify the activation frame to use, as in `backtrace-frame'. from the debugger. */ return unbind_to (count, eval_sub (exp)); } + +DEFUN ("backtrace-locals", Fbacktrace_locals, Sbacktrace_locals, 1, 2, NULL, + doc: /* Return names and values of local variables of a stack frame. +NFRAMES and BASE specify the activation frame to use, as in `backtrace-frame'. +The result is an alist. */) + (Lisp_Object nframes, Lisp_Object base) +{ + union specbinding *frame = get_backtrace_frame (nframes, base); + union specbinding *olderframe = + get_backtrace_frame (make_number (XFASTINT (nframes) + 1), base); + ptrdiff_t distance = specpdl_ptr - olderframe; + Lisp_Object result = Qnil; + eassert (distance >= 0); + + if (!backtrace_p (frame)) + error ("Activation frame not found!"); + if (!backtrace_p (olderframe)) + error ("Activation frame not found!"); + + /* move values to specpdl */ + backtrace_eval_unrewind (distance); + + /* grab values */ + { + union specbinding *tmp = frame; + for (; tmp > olderframe; tmp--) + { + switch (tmp->kind) + { + case SPECPDL_LET: + case SPECPDL_LET_DEFAULT: + case SPECPDL_LET_LOCAL: + { + Lisp_Object sym = specpdl_symbol (tmp); + Lisp_Object val = specpdl_old_value (tmp); + result = Fcons (Fcons (sym, val), result); + } + } + } + } + + /* restore values from specpdl to orignal place */ + backtrace_eval_unrewind (-distance); + + return result; +} + void mark_specpdl (void) @@ -3824,6 +3871,7 @@ alist of active lexical bindings. */); defsubr (&Sbacktrace); defsubr (&Sbacktrace_frame); defsubr (&Sbacktrace_eval); + defsubr (&Sbacktrace_locals); defsubr (&Sspecial_variable_p); defsubr (&Sfunctionp); }