10

When getting an Elisp error and trying to debug the problem, the backtrace buffer is quite hard to read with its long lines.

Is there a way to get a pretty-printed backtrace buffer?

2 Answers 2

8

Is there a way to get a pretty-printed backtrace buffer?

There is, but you'll have to wait for Emacs 27 to be released or get a build of it in the meantime. Quoth its etc/NEWS file:

* Changes in Specialized Modes and Packages in Emacs 27.1
** Debugger
*** The Lisp Debugger is now based on 'backtrace-mode'.
Backtrace mode adds fontification and commands for changing the
appearance of backtrace frames.  See the node "(elisp) Backtraces" in
the Elisp manual for documentation of the new mode and its commands.

* New Modes and Packages in Emacs 27.1
** Backtrace mode improves viewing of Elisp backtraces.
Backtrace mode adds pretty printing, fontification and ellipsis
expansion to backtrace buffers produced by the Lisp debugger, Edebug
and ERT.  See the node "(elisp) Backtraces" in the Elisp manual for
documentation of the new mode and its commands.

And quoth "(elisp) Backtraces":

   In backtraces, the tails of long lists and the ends of long strings,
vectors or structures, as well as objects which are deeply nested, will
be printed as underlined “...”.  You can click with the mouse on a
“...”, or type <RET> while point is on it, to show the part of the
object that was hidden.  To control how much abbreviation is done,
customize ‘backtrace-line-length’.

   Here is a list of commands for navigating and viewing backtraces:

‘+’
     Add line breaks and indentation to the top-level Lisp form at point
     to make it more readable.

‘-’
     Collapse the top-level Lisp form at point back to a single line.

Here's an example of the new functionality. Let's say you insert the following into the *scratch* buffer:

(progn
  (setq debug-on-error t)
  (+ (mapcar (lambda (n)
               (cons n (number-to-string n)))
             (number-sequence 0 7))))

and then evaluate it by typing C-xC-e. You should be greeted by the following backtrace:

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p ((0 . "0") (1 . "1") (2 . "2") (3 . "3") (4 . "4") (5 . "5") (6 . "6") (7 . "7")))
  +(((0 . "0") (1 . "1") (2 . "2") (3 . "3") (4 . "4") (5 . "5") (6 . "6") (7 . "7")))
  (progn (setq debug-on-error t) (+ (mapcar #'(lambda (n) (cons n (number-to-string n))) (number-sequence 0 7))))
  (progn (progn (setq debug-on-error t) (+ (mapcar #'(lambda (n) (cons n (number-to-string n))) (number-sequence 0 7)))))
  eval((progn (progn (setq debug-on-error t) (+ (mapcar #'(lambda (n) (cons n ...)) (number-sequence 0 7))))) t)
  elisp--eval-last-sexp(nil)
  eval-last-sexp(nil)
  funcall-interactively(eval-last-sexp nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)

If you then type + (backtrace-multi-line), the first line will be pretty-printed as follows:

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p
                                                   ((0 . "0")
                                                    (1 . "1")
                                                    (2 . "2")
                                                    (3 . "3")
                                                    (4 . "4")
                                                    (5 . "5")
                                                    (6 . "6")
                                                    (7 . "7")))
  +(((0 . "0") (1 . "1") (2 . "2") (3 . "3") (4 . "4") (5 . "5") (6 . "6") (7 . "7")))
  (progn (setq debug-on-error t) (+ (mapcar #'(lambda (n) (cons n (number-to-string n))) (number-sequence 0 7))))
  (progn (progn (setq debug-on-error t) (+ (mapcar #'(lambda (n) (cons n (number-to-string n))) (number-sequence 0 7)))))
  eval((progn (progn (setq debug-on-error t) (+ (mapcar #'(lambda (n) (cons n ...)) (number-sequence 0 7))))) t)
  elisp--eval-last-sexp(nil)
  eval-last-sexp(nil)
  funcall-interactively(eval-last-sexp nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)

Typing - (backtrace-single-line) will return the trace to its original formatting.


Edit

Here's a quick way to toggle between folding and unfolding all frames in the current backtrace:

(define-minor-mode my-backtrace-multi-line-mode
  "Toggle pretty-printing of all sexps in the current backtrace."
  :group 'debugger
  (if (not (derived-mode-p #'backtrace-mode))
      (progn (message "Not in a Backtrace mode buffer")
             (setq my-backtrace-multi-line-mode nil))
    ;; Save some information in order to return to original form.
    (let ((idx (backtrace-get-index))
          ;; If idx is nil we are either on the first line or at EOB.
          (eob (eobp))
          (fun (if my-backtrace-multi-line-mode
                   #'backtrace-multi-line
                 #'backtrace-single-line)))
      (goto-char (point-min))
      (while (ignore-errors
               (funcall fun)
               (backtrace-forward-frame)
               t))
      (cond (idx (while (/= (backtrace-get-index) idx)
                   (backtrace-backward-frame)))
            (eob (goto-char (point-max)))
            (t   (goto-char (point-min))
                 (let ((eol (line-end-position)))
                   (search-forward ":" eol t)
                   (search-forward " " eol t)))))))

(Defining a full-blown minor mode for this is overkill but it saved me having to write the handy toggling logic.)

Here's how you could bind it to, say, = in *Backtrace* buffers:

(with-eval-after-load 'debug
  (define-key debugger-mode-map "=" #'my-backtrace-multi-line-mode))
3
  • Good to know, it's coming, thanks. Though, I'd appreciate an "expand all" feature too, that is pretty printing all the forms in the backtrace buffer, instead of doing it one line at a time.
    – Tom
    Commented Dec 12, 2019 at 12:30
  • @Tom I've edited the answer to give an example of how one could achieve that themselves. I suggest you submit this as a feature request using M-x report-emacs-bug, unless someone has already done so. I may eventually get around to doing so, but not any time soon.
    – Basil
    Commented Dec 12, 2019 at 15:01
  • sweet. actually excited for 27 now, just for this!
    – nega
    Commented Dec 13, 2019 at 17:00
1

I haven't used it myself, but there is this gist born from this reddit post. A comment on the reddit post includes this screen shot. Maybe it meets your needs.

screen shot of elisp backtrace and its pretty-printed version

4
  • Thanks, I'll give it a try. Strange there is not built in way to toggle between the current and pretty printed display. I don't even really get why the current format where everything is on one line is good for debugging.
    – Tom
    Commented Dec 12, 2019 at 8:24
  • I think it's just a very old case of "it's good enough". At least backtraces aren't spewing bytecode anymore!
    – nega
    Commented Dec 12, 2019 at 8:26
  • Well, I don't think it's good enough. In my experience it's pretty bad, quite hard to read when you try to decipher what's happening in a longer backtrace.
    – Tom
    Commented Dec 12, 2019 at 8:28
  • "Good enough" in the sense that it wasn't painful enough for the experts to fix. There may be more but I only remember one major update to backtraces (prior to 27) in the past 20-some years.
    – nega
    Commented Dec 13, 2019 at 17:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.