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?
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.
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))
M-x report-emacs-bug
, unless someone has already done so. I may eventually get around to doing so, but not any time soon.
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.