mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2025-01-14 16:50:58 +00:00
Merge branch 'scratch/backtrace-mode'
This commit is contained in:
commit
da0054c307
@ -81,7 +81,8 @@ debugger recursively. @xref{Recursive Editing}.
|
||||
* Function Debugging:: Entering it when a certain function is called.
|
||||
* Variable Debugging:: Entering it when a variable is modified.
|
||||
* Explicit Debug:: Entering it at a certain point in the program.
|
||||
* Using Debugger:: What the debugger does; what you see while in it.
|
||||
* Using Debugger:: What the debugger does.
|
||||
* Backtraces:: What you see while in the debugger.
|
||||
* Debugger Commands:: Commands used while in the debugger.
|
||||
* Invoking the Debugger:: How to call the function @code{debug}.
|
||||
* Internals of Debugger:: Subroutines of the debugger, and global variables.
|
||||
@ -392,32 +393,82 @@ this is not what you want, you can either set
|
||||
@code{eval-expression-debug-on-error} to @code{nil}, or set
|
||||
@code{debug-on-error} to @code{nil} in @code{debugger-mode-hook}.
|
||||
|
||||
@cindex current stack frame
|
||||
The backtrace buffer shows you the functions that are executing and
|
||||
their argument values. It also allows you to specify a stack frame by
|
||||
moving point to the line describing that frame. (A stack frame is the
|
||||
place where the Lisp interpreter records information about a particular
|
||||
invocation of a function.) The frame whose line point is on is
|
||||
considered the @dfn{current frame}. Some of the debugger commands
|
||||
operate on the current frame. If a line starts with a star, that means
|
||||
that exiting that frame will call the debugger again. This is useful
|
||||
for examining the return value of a function.
|
||||
|
||||
If a function name is underlined, that means the debugger knows
|
||||
where its source code is located. You can click with the mouse on
|
||||
that name, or move to it and type @key{RET}, to visit the source code.
|
||||
|
||||
The debugger itself must be run byte-compiled, since it makes
|
||||
assumptions about how many stack frames are used for the debugger
|
||||
itself. These assumptions are false if the debugger is running
|
||||
interpreted.
|
||||
assumptions about the state of the Lisp interpreter. These
|
||||
assumptions are false if the debugger is running interpreted.
|
||||
|
||||
@node Backtraces
|
||||
@subsection Backtraces
|
||||
@cindex backtrace buffer
|
||||
|
||||
Debugger mode is derived from Backtrace mode, which is also used to
|
||||
show backtraces by Edebug and ERT. (@pxref{Edebug}, and @ref{Top,the
|
||||
ERT manual,, ert, ERT: Emacs Lisp Regression Testing}.)
|
||||
|
||||
@cindex stack frame
|
||||
The backtrace buffer shows you the functions that are executing and
|
||||
their argument values. When a backtrace buffer is created, it shows
|
||||
each stack frame on one, possibly very long, line. (A stack frame is
|
||||
the place where the Lisp interpreter records information about a
|
||||
particular invocation of a function.) The most recently called
|
||||
function will be at the top.
|
||||
|
||||
@cindex current stack frame
|
||||
In a backtrace you can specify a stack frame by moving point to a line
|
||||
describing that frame. The frame whose line point is on is considered
|
||||
the @dfn{current frame}.
|
||||
|
||||
If a function name is underlined, that means Emacs knows where its
|
||||
source code is located. You can click with the mouse on that name, or
|
||||
move to it and type @key{RET}, to visit the source code. You can also
|
||||
type @key{RET} while point is on any name of a function or variable
|
||||
which is not underlined, to see help information for that symbol in a
|
||||
help buffer, if any exists. The @code{xref-find-definitions} command,
|
||||
bound to @key{M-.}, can also be used on any identifier in a backtrace
|
||||
(@pxref{Looking Up Identifiers,,,emacs, The GNU Emacs Manual}).
|
||||
|
||||
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 @key{RET} while point is on it, to show the part
|
||||
of the object that was hidden. To control how much abbreviation is
|
||||
done, customize @code{backtrace-line-length}.
|
||||
|
||||
Here is a list of commands for navigating and viewing backtraces:
|
||||
|
||||
@table @kbd
|
||||
@item v
|
||||
Toggle the display of local variables of the current stack frame.
|
||||
|
||||
@item p
|
||||
Move to the beginning of the frame, or to the beginning
|
||||
of the previous frame.
|
||||
|
||||
@item n
|
||||
Move to the beginning of the next frame.
|
||||
|
||||
@item +
|
||||
Add line breaks and indentation to the top-level Lisp form at point to
|
||||
make it more readable.
|
||||
|
||||
@item -
|
||||
Collapse the top-level Lisp form at point back to a single line.
|
||||
|
||||
@item #
|
||||
Toggle @code{print-circle} for the frame at point.
|
||||
|
||||
@item .
|
||||
Expand all the forms abbreviated with ``...'' in the frame at point.
|
||||
|
||||
@end table
|
||||
|
||||
@node Debugger Commands
|
||||
@subsection Debugger Commands
|
||||
@cindex debugger command list
|
||||
|
||||
The debugger buffer (in Debugger mode) provides special commands in
|
||||
addition to the usual Emacs commands. The most important use of
|
||||
addition to the usual Emacs commands and to the Backtrace mode commands
|
||||
described in the previous section. The most important use of
|
||||
debugger commands is for stepping through code, so that you can see
|
||||
how control flows. The debugger can step through the control
|
||||
structures of an interpreted function, but cannot do so in a
|
||||
@ -427,6 +478,11 @@ the same function. (To do this, visit the source for the function and
|
||||
type @kbd{C-M-x} on its definition.) You cannot use the Lisp debugger
|
||||
to step through a primitive function.
|
||||
|
||||
Some of the debugger commands operate on the current frame. If a
|
||||
frame starts with a star, that means that exiting that frame will call the
|
||||
debugger again. This is useful for examining the return value of a
|
||||
function.
|
||||
|
||||
@c FIXME: Add @findex for the following commands? --xfq
|
||||
Here is a list of Debugger mode commands:
|
||||
|
||||
@ -502,8 +558,6 @@ Display a list of functions that will invoke the debugger when called.
|
||||
This is a list of functions that are set to break on entry by means of
|
||||
@code{debug-on-entry}.
|
||||
|
||||
@item v
|
||||
Toggle the display of local variables of the current stack frame.
|
||||
@end table
|
||||
|
||||
@node Invoking the Debugger
|
||||
@ -624,20 +678,19 @@ of @code{debug} (@pxref{Invoking the Debugger}).
|
||||
@cindex run time stack
|
||||
@cindex call stack
|
||||
This function prints a trace of Lisp function calls currently active.
|
||||
This is the function used by @code{debug} to fill up the
|
||||
@file{*Backtrace*} buffer. It is written in C, since it must have access
|
||||
to the stack to determine which function calls are active. The return
|
||||
value is always @code{nil}.
|
||||
The trace is identical to the one that @code{debug} would show in the
|
||||
@file{*Backtrace*} buffer. The return value is always nil.
|
||||
|
||||
In the following example, a Lisp expression calls @code{backtrace}
|
||||
explicitly. This prints the backtrace to the stream
|
||||
@code{standard-output}, which, in this case, is the buffer
|
||||
@samp{backtrace-output}.
|
||||
|
||||
Each line of the backtrace represents one function call. The line shows
|
||||
the values of the function's arguments if they are all known; if they
|
||||
are still being computed, the line says so. The arguments of special
|
||||
forms are elided.
|
||||
Each line of the backtrace represents one function call. The line
|
||||
shows the function followed by a list of the values of the function's
|
||||
arguments if they are all known; if they are still being computed, the
|
||||
line consists of a list containing the function and its unevaluated
|
||||
arguments. Long lists or deeply nested structures may be elided.
|
||||
|
||||
@smallexample
|
||||
@group
|
||||
@ -654,7 +707,7 @@ forms are elided.
|
||||
@group
|
||||
----------- Buffer: backtrace-output ------------
|
||||
backtrace()
|
||||
(list ...computing arguments...)
|
||||
(list 'testing (backtrace))
|
||||
@end group
|
||||
(progn ...)
|
||||
eval((progn (1+ var) (list 'testing (backtrace))))
|
||||
@ -685,7 +738,7 @@ example would look as follows:
|
||||
@group
|
||||
----------- Buffer: backtrace-output ------------
|
||||
(backtrace)
|
||||
(list ...computing arguments...)
|
||||
(list 'testing (backtrace))
|
||||
@end group
|
||||
(progn ...)
|
||||
(eval (progn (1+ var) (list 'testing (backtrace))))
|
||||
|
@ -442,8 +442,16 @@ Redisplay the most recently known expression result in the echo area
|
||||
Display a backtrace, excluding Edebug's own functions for clarity
|
||||
(@code{edebug-backtrace}).
|
||||
|
||||
You cannot use debugger commands in the backtrace buffer in Edebug as
|
||||
you would in the standard debugger.
|
||||
@xref{Backtraces}, for a description of backtraces
|
||||
and the commands which work on them.
|
||||
|
||||
If you would like to see Edebug's functions in the backtrace,
|
||||
use @kbd{M-x edebug-backtrace-show-instrumentation}. To hide them
|
||||
again use @kbd{M-x edebug-backtrace-hide-instrumentation}.
|
||||
|
||||
If a backtrace frame starts with @samp{>} that means that Edebug knows
|
||||
where the source code for the frame is located. Use @kbd{s} to jump
|
||||
to the source code for the current frame.
|
||||
|
||||
The backtrace buffer is killed automatically when you continue
|
||||
execution.
|
||||
|
@ -273,9 +273,11 @@ moving point to it and typing @kbd{@key{RET}} jumps to its definition.
|
||||
@cindex backtrace of a failed test
|
||||
Pressing @kbd{r} re-runs the test near point on its own. Pressing
|
||||
@kbd{d} re-runs it with the debugger enabled. @kbd{.} jumps to the
|
||||
definition of the test near point (@kbd{@key{RET}} has the same effect if
|
||||
point is on the name of the test). On a failed test, @kbd{b} shows
|
||||
the backtrace of the failure.
|
||||
definition of the test near point (@kbd{@key{RET}} has the same effect
|
||||
if point is on the name of the test). On a failed test, @kbd{b} shows
|
||||
the backtrace of the failure. @xref{Debugging,, Backtraces, elisp,
|
||||
GNU Emacs Lisp Reference Manual}, for more information about
|
||||
backtraces.
|
||||
|
||||
@kindex l@r{, in ert results buffer}
|
||||
@kbd{l} shows the list of @code{should} forms executed in the test.
|
||||
|
40
etc/NEWS
40
etc/NEWS
@ -466,6 +466,14 @@ the shift key.
|
||||
*** Isearch now remembers the regexp-based search mode for words/symbols
|
||||
and case-sensitivity together with search strings in the search ring.
|
||||
|
||||
** 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 "Backtraces" in the Elisp
|
||||
manual for documentation of the new mode and its commands.
|
||||
|
||||
** Edebug
|
||||
|
||||
+++
|
||||
@ -475,14 +483,27 @@ using the new variables 'edebug-behavior-alist',
|
||||
'edebug-new-definition-function'. Edebug's behavior can be changed
|
||||
globally or for individual definitions.
|
||||
|
||||
+++
|
||||
*** Edebug's backtrace buffer now uses 'backtrace-mode'.
|
||||
Backtrace mode adds fontification, links and commands for changing the
|
||||
appearance of backtrace frames. See the node "Backtraces" in the Elisp
|
||||
manual for documentation of the new mode and its commands.
|
||||
|
||||
The binding of 'd' in Edebug's keymap is now 'edebug-pop-to-backtrace'
|
||||
which replaces 'edebug-backtrace'. Consequently Edebug's backtrace
|
||||
windows now behave like those of the Lisp Debugger and of ERT, in that
|
||||
when they appear they will be the selected window.
|
||||
|
||||
The new 'backtrace-goto-source' command, bound to 's', works in
|
||||
Edebug's backtraces on backtrace frames whose source code has
|
||||
been instrumented by Edebug.
|
||||
|
||||
** Enhanced xterm support
|
||||
|
||||
*** New variable 'xterm-set-window-title' controls whether Emacs sets
|
||||
the XTerm window title. This feature is experimental and is disabled
|
||||
by default.
|
||||
|
||||
** Gamegrid
|
||||
|
||||
** grep
|
||||
|
||||
+++
|
||||
@ -499,6 +520,14 @@ The abbreviation can be disabled by the new option
|
||||
*** New variable 'ert-quiet' allows to make ERT output in batch mode
|
||||
less verbose by removing non-essential information.
|
||||
|
||||
+++
|
||||
*** ERT's backtrace buffer now uses 'backtrace-mode'.
|
||||
Backtrace mode adds fontification and commands for changing the
|
||||
appearance of backtrace frames. See the node "Backtraces" in the Elisp
|
||||
manual for documentation of the new mode and its commands.
|
||||
|
||||
** Gamegrid
|
||||
|
||||
---
|
||||
*** Gamegrid now determines its default glyph size based on display
|
||||
dimensions, instead of always using 16 pixels. As a result, Tetris,
|
||||
@ -669,6 +698,13 @@ transport strategies as well as a separate API to use them. A
|
||||
transport implementation for process-based communication, such as is
|
||||
used by the Language Server Protocol (LSP), is readily available.
|
||||
|
||||
+++
|
||||
** 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 "Backtraces" in the Elisp manual for
|
||||
documentation of the new mode and its commands.
|
||||
|
||||
|
||||
* Incompatible Lisp Changes in Emacs 27.1
|
||||
|
||||
|
916
lisp/emacs-lisp/backtrace.el
Normal file
916
lisp/emacs-lisp/backtrace.el
Normal file
@ -0,0 +1,916 @@
|
||||
;;; backtrace.el --- generic major mode for Elisp backtraces -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2018 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Gemini Lasswell
|
||||
;; Keywords: lisp, tools, maint
|
||||
;; Version: 1.0
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This file defines Backtrace mode, a generic major mode for displaying
|
||||
;; Elisp stack backtraces, which can be used as is or inherited from
|
||||
;; by another mode.
|
||||
|
||||
;; For usage information, see the documentation of `backtrace-mode'.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl-lib))
|
||||
(eval-when-compile (require 'pcase))
|
||||
(eval-when-compile (require 'subr-x)) ; if-let
|
||||
(require 'help-mode) ; Define `help-function-def' button type.
|
||||
(require 'lisp-mode)
|
||||
|
||||
;;; Options
|
||||
|
||||
(defgroup backtrace nil
|
||||
"Viewing of Elisp backtraces."
|
||||
:group 'lisp)
|
||||
|
||||
(defcustom backtrace-fontify t
|
||||
"If non-nil, fontify Backtrace buffers.
|
||||
Set to nil to disable fontification, which may be necessary in
|
||||
order to debug the code that does fontification."
|
||||
:type 'boolean
|
||||
:group 'backtrace
|
||||
:version "27.1")
|
||||
|
||||
(defcustom backtrace-line-length 5000
|
||||
"Target length for lines in Backtrace buffers.
|
||||
Backtrace mode will attempt to abbreviate printing of backtrace
|
||||
frames to make them shorter than this, but success is not
|
||||
guaranteed. If set to nil or zero, Backtrace mode will not
|
||||
abbreviate the forms it prints."
|
||||
:type 'integer
|
||||
:group 'backtrace
|
||||
:version "27.1")
|
||||
|
||||
;;; Backtrace frame data structure
|
||||
|
||||
(cl-defstruct
|
||||
(backtrace-frame
|
||||
(:constructor backtrace-make-frame))
|
||||
evald ; Non-nil if argument evaluation is complete.
|
||||
fun ; The function called/to call in this frame.
|
||||
args ; Either evaluated or unevaluated arguments to the function.
|
||||
flags ; A plist, possible properties are :debug-on-exit and :source-available.
|
||||
locals ; An alist containing variable names and values.
|
||||
buffer ; If non-nil, the buffer in use by eval-buffer or eval-region.
|
||||
pos ; The position in the buffer.
|
||||
)
|
||||
|
||||
(cl-defun backtrace-get-frames
|
||||
(&optional base &key (constructor #'backtrace-make-frame))
|
||||
"Collect all frames of current backtrace into a list.
|
||||
The list will contain objects made by CONSTRUCTOR, which
|
||||
defaults to `backtrace-make-frame' and which, if provided, should
|
||||
be the constructor of a structure which includes
|
||||
`backtrace-frame'. If non-nil, BASE should be a function, and
|
||||
frames before its nearest activation frame are discarded."
|
||||
(let ((frames nil)
|
||||
(eval-buffers eval-buffer-list))
|
||||
(mapbacktrace (lambda (evald fun args flags)
|
||||
(push (funcall constructor
|
||||
:evald evald :fun fun
|
||||
:args args :flags flags)
|
||||
frames))
|
||||
(or base 'backtrace-get-frames))
|
||||
(setq frames (nreverse frames))
|
||||
;; Add local variables to each frame, and the buffer position
|
||||
;; to frames containing eval-buffer or eval-region.
|
||||
(dotimes (idx (length frames))
|
||||
(let ((frame (nth idx frames)))
|
||||
;; `backtrace--locals' gives an error when idx is 0. But the
|
||||
;; locals for frame 0 are not needed, because when we get here
|
||||
;; from debug-on-entry, the locals aren't bound yet, and when
|
||||
;; coming from Edebug or ERT there is an Edebug or ERT
|
||||
;; function at frame 0.
|
||||
(when (> idx 0)
|
||||
(setf (backtrace-frame-locals frame)
|
||||
(backtrace--locals idx (or base 'backtrace-get-frames))))
|
||||
(when (and eval-buffers (memq (backtrace-frame-fun frame)
|
||||
'(eval-buffer eval-region)))
|
||||
;; This will get the wrong result if there are two nested
|
||||
;; eval-region calls for the same buffer. That's not a very
|
||||
;; useful case.
|
||||
(with-current-buffer (pop eval-buffers)
|
||||
(setf (backtrace-frame-buffer frame) (current-buffer))
|
||||
(setf (backtrace-frame-pos frame) (point))))))
|
||||
frames))
|
||||
|
||||
;; Button definition for jumping to a buffer position.
|
||||
|
||||
(define-button-type 'backtrace-buffer-pos
|
||||
'action #'backtrace--pop-to-buffer-pos
|
||||
'help-echo "mouse-2, RET: Show reading position")
|
||||
|
||||
(defun backtrace--pop-to-buffer-pos (button)
|
||||
"Pop to the buffer and position for the BUTTON at point."
|
||||
(let* ((buffer (button-get button 'backtrace-buffer))
|
||||
(pos (button-get button 'backtrace-pos)))
|
||||
(if (buffer-live-p buffer)
|
||||
(progn
|
||||
(pop-to-buffer buffer)
|
||||
(goto-char (max (point-min) (min (point-max) pos))))
|
||||
(message "Buffer has been killed"))))
|
||||
|
||||
;; Font Locking support
|
||||
|
||||
(defconst backtrace--font-lock-keywords
|
||||
'((backtrace--match-ellipsis-in-string
|
||||
(1 'button prepend)))
|
||||
"Expressions to fontify in Backtrace mode.
|
||||
Fontify these in addition to the expressions Emacs Lisp mode
|
||||
fontifies.")
|
||||
|
||||
(defconst backtrace-font-lock-keywords
|
||||
(append lisp-el-font-lock-keywords-for-backtraces
|
||||
backtrace--font-lock-keywords)
|
||||
"Default expressions to highlight in Backtrace mode.")
|
||||
(defconst backtrace-font-lock-keywords-1
|
||||
(append lisp-el-font-lock-keywords-for-backtraces-1
|
||||
backtrace--font-lock-keywords)
|
||||
"Subdued level highlighting for Backtrace mode.")
|
||||
(defconst backtrace-font-lock-keywords-2
|
||||
(append lisp-el-font-lock-keywords-for-backtraces-2
|
||||
backtrace--font-lock-keywords)
|
||||
"Gaudy level highlighting for Backtrace mode.")
|
||||
|
||||
(defun backtrace--match-ellipsis-in-string (bound)
|
||||
;; Fontify ellipses within strings as buttons.
|
||||
;; This is necessary because ellipses are text property buttons
|
||||
;; instead of overlay buttons, which is done because there could
|
||||
;; be a large number of them.
|
||||
(when (re-search-forward "\\(\\.\\.\\.\\)\"" bound t)
|
||||
(and (get-text-property (- (point) 2) 'cl-print-ellipsis)
|
||||
(get-text-property (- (point) 3) 'cl-print-ellipsis)
|
||||
(get-text-property (- (point) 4) 'cl-print-ellipsis))))
|
||||
|
||||
;;; Xref support
|
||||
|
||||
(defun backtrace--xref-backend () 'elisp)
|
||||
|
||||
;;; Backtrace mode variables
|
||||
|
||||
(defvar-local backtrace-frames nil
|
||||
"Stack frames displayed in the current Backtrace buffer.
|
||||
This should be a list of `backtrace-frame' objects.")
|
||||
|
||||
(defvar-local backtrace-view nil
|
||||
"A plist describing how to render backtrace frames.
|
||||
Possible entries are :show-flags, :show-locals and :print-circle.")
|
||||
|
||||
(defvar-local backtrace-insert-header-function nil
|
||||
"Function for inserting a header for the current Backtrace buffer.
|
||||
If nil, no header will be created. Note that Backtrace buffers
|
||||
are fontified as in Emacs Lisp Mode, the header text included.")
|
||||
|
||||
(defvar backtrace-revert-hook nil
|
||||
"Hook run before reverting a Backtrace buffer.
|
||||
This is commonly used to recompute `backtrace-frames'.")
|
||||
|
||||
(defvar-local backtrace-print-function #'cl-prin1
|
||||
"Function used to print values in the current Backtrace buffer.")
|
||||
|
||||
(defvar-local backtrace-goto-source-functions nil
|
||||
"Abnormal hook used to jump to the source code for the current frame.
|
||||
Each hook function is called with no argument, and should return
|
||||
non-nil if it is able to switch to the buffer containing the
|
||||
source code. Execution of the hook will stop if one of the
|
||||
functions returns non-nil. When adding a function to this hook,
|
||||
you should also set the :source-available flag for the backtrace
|
||||
frames where the source code location is known.")
|
||||
|
||||
(defvar backtrace-mode-map
|
||||
(let ((map (copy-keymap special-mode-map)))
|
||||
(set-keymap-parent map button-buffer-map)
|
||||
(define-key map "n" 'backtrace-forward-frame)
|
||||
(define-key map "p" 'backtrace-backward-frame)
|
||||
(define-key map "v" 'backtrace-toggle-locals)
|
||||
(define-key map "#" 'backtrace-toggle-print-circle)
|
||||
(define-key map "s" 'backtrace-goto-source)
|
||||
(define-key map "\C-m" 'backtrace-help-follow-symbol)
|
||||
(define-key map "+" 'backtrace-multi-line)
|
||||
(define-key map "-" 'backtrace-single-line)
|
||||
(define-key map "." 'backtrace-expand-ellipses)
|
||||
(define-key map [follow-link] 'mouse-face)
|
||||
(define-key map [mouse-2] 'mouse-select-window)
|
||||
(easy-menu-define nil map ""
|
||||
'("Backtrace"
|
||||
["Next Frame" backtrace-forward-frame
|
||||
:help "Move cursor forwards to the start of a backtrace frame"]
|
||||
["Previous Frame" backtrace-backward-frame
|
||||
:help "Move cursor backwards to the start of a backtrace frame"]
|
||||
"--"
|
||||
["Show Variables" backtrace-toggle-locals
|
||||
:style toggle
|
||||
:active (backtrace-get-index)
|
||||
:selected (plist-get (backtrace-get-view) :show-locals)
|
||||
:help "Show or hide the local variables for the frame at point"]
|
||||
["Expand \"...\"s" backtrace-expand-ellipses
|
||||
:help "Expand all the abbreviated forms in the current frame"]
|
||||
["Show on Multiple Lines" backtrace-multi-line
|
||||
:help "Use line breaks and indentation to make a form more readable"]
|
||||
["Show on Single Line" backtrace-single-line]
|
||||
"--"
|
||||
["Go to Source" backtrace-goto-source
|
||||
:active (and (backtrace-get-index)
|
||||
(plist-get (backtrace-frame-flags
|
||||
(nth (backtrace-get-index) backtrace-frames))
|
||||
:source-available))
|
||||
:help "Show the source code for the current frame"]
|
||||
["Help for Symbol" backtrace-help-follow-symbol
|
||||
:help "Show help for symbol at point"]
|
||||
["Describe Backtrace Mode" describe-mode
|
||||
:help "Display documentation for backtrace-mode"]))
|
||||
map)
|
||||
"Local keymap for `backtrace-mode' buffers.")
|
||||
|
||||
(defconst backtrace--flags-width 2
|
||||
"Width in characters of the flags for a backtrace frame.")
|
||||
|
||||
;;; Navigation and Text Properties
|
||||
|
||||
;; This mode uses the following text properties:
|
||||
;; backtrace-index: The index into the buffer-local variable
|
||||
;; `backtrace-frames' for the frame at point, or nil if outside of a
|
||||
;; frame (in the buffer header).
|
||||
;; backtrace-view: A plist describing how the frame is printed. See
|
||||
;; the docstring for the buffer-local variable `backtrace-view.
|
||||
;; backtrace-section: The part of a frame which point is in. Either
|
||||
;; `func' or `locals'. At the moment just used to show and hide the
|
||||
;; local variables. Derived modes which do additional printing
|
||||
;; could define their own frame sections.
|
||||
;; backtrace-form: A value applied to each printed representation of a
|
||||
;; top-level s-expression, which needs to be different for sexps
|
||||
;; printed adjacent to each other, so the limits can be quickly
|
||||
;; found for pretty-printing.
|
||||
|
||||
(defsubst backtrace-get-index (&optional pos)
|
||||
"Return the index of the backtrace frame at POS.
|
||||
The value is an index into `backtrace-frames', or nil.
|
||||
POS, if omitted or nil, defaults to point."
|
||||
(get-text-property (or pos (point)) 'backtrace-index))
|
||||
|
||||
(defsubst backtrace-get-section (&optional pos)
|
||||
"Return the section of a backtrace frame at POS.
|
||||
POS, if omitted or nil, defaults to point."
|
||||
(get-text-property (or pos (point)) 'backtrace-section))
|
||||
|
||||
(defsubst backtrace-get-view (&optional pos)
|
||||
"Return the view plist of the backtrace frame at POS.
|
||||
POS, if omitted or nil, defaults to point."
|
||||
(get-text-property (or pos (point)) 'backtrace-view))
|
||||
|
||||
(defsubst backtrace-get-form (&optional pos)
|
||||
"Return the backtrace form data for the form printed at POS.
|
||||
POS, if omitted or nil, defaults to point."
|
||||
(get-text-property (or pos (point)) 'backtrace-form))
|
||||
|
||||
(defun backtrace-get-frame-start (&optional pos)
|
||||
"Return the beginning position of the frame at POS in the buffer.
|
||||
POS, if omitted or nil, defaults to point."
|
||||
(let ((posn (or pos (point))))
|
||||
(if (or (= (point-min) posn)
|
||||
(not (eq (backtrace-get-index posn)
|
||||
(backtrace-get-index (1- posn)))))
|
||||
posn
|
||||
(previous-single-property-change posn 'backtrace-index nil (point-min)))))
|
||||
|
||||
(defun backtrace-get-frame-end (&optional pos)
|
||||
"Return the position of the end of the frame at POS in the buffer.
|
||||
POS, if omitted or nil, defaults to point."
|
||||
(next-single-property-change (or pos (point))
|
||||
'backtrace-index nil (point-max)))
|
||||
|
||||
(defun backtrace-forward-frame ()
|
||||
"Move forward to the beginning of the next frame."
|
||||
(interactive)
|
||||
(let ((max (backtrace-get-frame-end)))
|
||||
(when (= max (point-max))
|
||||
(user-error "No next stack frame"))
|
||||
(goto-char max)))
|
||||
|
||||
(defun backtrace-backward-frame ()
|
||||
"Move backward to the start of a stack frame."
|
||||
(interactive)
|
||||
(let ((current-index (backtrace-get-index))
|
||||
(min (backtrace-get-frame-start)))
|
||||
(if (or (and (/= (point) (point-max)) (null current-index))
|
||||
(= min (point-min))
|
||||
(and (= min (point))
|
||||
(null (backtrace-get-index (1- min)))))
|
||||
(user-error "No previous stack frame"))
|
||||
(if (= min (point))
|
||||
(goto-char (backtrace-get-frame-start (1- min)))
|
||||
(goto-char min))))
|
||||
|
||||
;; Other Backtrace mode commands
|
||||
|
||||
(defun backtrace-revert (&rest _ignored)
|
||||
"The `revert-buffer-function' for `backtrace-mode'.
|
||||
It runs `backtrace-revert-hook', then calls `backtrace-print'."
|
||||
(interactive)
|
||||
(unless (derived-mode-p 'backtrace-mode)
|
||||
(error "The current buffer is not in Backtrace mode"))
|
||||
(run-hooks 'backtrace-revert-hook)
|
||||
(backtrace-print t))
|
||||
|
||||
(defmacro backtrace--with-output-variables (view &rest body)
|
||||
"Bind output variables according to VIEW and execute BODY."
|
||||
(declare (indent 1))
|
||||
`(let ((print-escape-control-characters t)
|
||||
(print-escape-newlines t)
|
||||
(print-circle (plist-get ,view :print-circle))
|
||||
(standard-output (current-buffer)))
|
||||
,@body))
|
||||
|
||||
(defun backtrace-toggle-locals (&optional all)
|
||||
"Toggle the display of local variables for the backtrace frame at point.
|
||||
With prefix argument ALL, toggle the value of :show-locals in
|
||||
`backtrace-view', which affects all of the backtrace frames in
|
||||
the buffer."
|
||||
(interactive "P")
|
||||
(if all
|
||||
(let ((pos (make-marker))
|
||||
(visible (not (plist-get backtrace-view :show-locals))))
|
||||
(setq backtrace-view (plist-put backtrace-view :show-locals visible))
|
||||
(set-marker-insertion-type pos t)
|
||||
(set-marker pos (point))
|
||||
(goto-char (point-min))
|
||||
;; Skip the header.
|
||||
(unless (backtrace-get-index)
|
||||
(goto-char (backtrace-get-frame-end)))
|
||||
(while (< (point) (point-max))
|
||||
(backtrace--set-frame-locals-visible visible)
|
||||
(goto-char (backtrace-get-frame-end)))
|
||||
(goto-char pos)
|
||||
(when (invisible-p pos)
|
||||
(goto-char (backtrace-get-frame-start))))
|
||||
(let ((index (backtrace-get-index)))
|
||||
(unless index
|
||||
(user-error "Not in a stack frame"))
|
||||
(backtrace--set-frame-locals-visible
|
||||
(not (plist-get (backtrace-get-view) :show-locals))))))
|
||||
|
||||
(defun backtrace--set-frame-locals-visible (visible)
|
||||
"Set the visibility of the local vars for the frame at point to VISIBLE."
|
||||
(let ((pos (point))
|
||||
(index (backtrace-get-index))
|
||||
(start (backtrace-get-frame-start))
|
||||
(end (backtrace-get-frame-end))
|
||||
(view (copy-sequence (backtrace-get-view)))
|
||||
(inhibit-read-only t))
|
||||
(setq view (plist-put view :show-locals visible))
|
||||
(goto-char (backtrace-get-frame-start))
|
||||
(while (not (or (= (point) end)
|
||||
(eq (backtrace-get-section) 'locals)))
|
||||
(goto-char (next-single-property-change (point)
|
||||
'backtrace-section nil end)))
|
||||
(cond
|
||||
((and (= (point) end) visible)
|
||||
;; The locals section doesn't exist so create it.
|
||||
(let ((standard-output (current-buffer)))
|
||||
(backtrace--with-output-variables view
|
||||
(backtrace--print-locals
|
||||
(nth index backtrace-frames) view))
|
||||
(add-text-properties end (point) `(backtrace-index ,index))
|
||||
(goto-char pos)))
|
||||
((/= (point) end)
|
||||
;; The locals section does exist, so add or remove the overlay.
|
||||
(backtrace--set-locals-visible-overlay (point) end visible)
|
||||
(goto-char (if (invisible-p pos) start pos))))
|
||||
(add-text-properties start (backtrace-get-frame-end)
|
||||
`(backtrace-view ,view))))
|
||||
|
||||
(defun backtrace--set-locals-visible-overlay (beg end visible)
|
||||
(backtrace--change-button-skip beg end (not visible))
|
||||
(if visible
|
||||
(remove-overlays beg end 'invisible t)
|
||||
(let ((o (make-overlay beg end)))
|
||||
(overlay-put o 'invisible t)
|
||||
(overlay-put o 'evaporate t))))
|
||||
|
||||
(defun backtrace--change-button-skip (beg end value)
|
||||
"Change the skip property on all buttons between BEG and END.
|
||||
Set it to VALUE unless the button is a `backtrace-ellipsis' button."
|
||||
(let ((inhibit-read-only t))
|
||||
(setq beg (next-button beg))
|
||||
(while (and beg (< beg end))
|
||||
(unless (eq (button-type beg) 'backtrace-ellipsis)
|
||||
(button-put beg 'skip value))
|
||||
(setq beg (next-button beg)))))
|
||||
|
||||
(defun backtrace-toggle-print-circle (&optional all)
|
||||
"Toggle `print-circle' for the backtrace frame at point.
|
||||
With prefix argument ALL, toggle the value of :print-circle in
|
||||
`backtrace-view', which affects all of the backtrace frames in
|
||||
the buffer."
|
||||
(interactive "P")
|
||||
(backtrace--toggle-feature :print-circle all))
|
||||
|
||||
(defun backtrace--toggle-feature (feature all)
|
||||
"Toggle FEATURE for the current backtrace frame or for the buffer.
|
||||
FEATURE should be one of the options in `backtrace-view'. If ALL
|
||||
is non-nil, toggle FEATURE for all frames in the buffer. After
|
||||
toggling the feature, reprint the affected frame(s). Afterwards
|
||||
position point at the start of the frame it was in before."
|
||||
(if all
|
||||
(let ((index (backtrace-get-index))
|
||||
(pos (point))
|
||||
(at-end (= (point) (point-max)))
|
||||
(value (not (plist-get backtrace-view feature))))
|
||||
(setq backtrace-view (plist-put backtrace-view feature value))
|
||||
(goto-char (point-min))
|
||||
;; Skip the header.
|
||||
(unless (backtrace-get-index)
|
||||
(goto-char (backtrace-get-frame-end)))
|
||||
(while (< (point) (point-max))
|
||||
(backtrace--set-feature feature value)
|
||||
(goto-char (backtrace-get-frame-end)))
|
||||
(if (not index)
|
||||
(goto-char (if at-end (point-max) pos))
|
||||
(goto-char (point-min))
|
||||
(while (and (not (eql index (backtrace-get-index)))
|
||||
(< (point) (point-max)))
|
||||
(goto-char (backtrace-get-frame-end)))))
|
||||
(let ((index (backtrace-get-index)))
|
||||
(unless index
|
||||
(user-error "Not in a stack frame"))
|
||||
(backtrace--set-feature feature
|
||||
(not (plist-get (backtrace-get-view) feature))))))
|
||||
|
||||
(defun backtrace--set-feature (feature value)
|
||||
"Set FEATURE in the view plist of the frame at point to VALUE.
|
||||
Reprint the frame with the new view plist."
|
||||
(let ((inhibit-read-only t)
|
||||
(view (copy-sequence (backtrace-get-view)))
|
||||
(index (backtrace-get-index))
|
||||
(min (backtrace-get-frame-start))
|
||||
(max (backtrace-get-frame-end)))
|
||||
(setq view (plist-put view feature value))
|
||||
(delete-region min max)
|
||||
(goto-char min)
|
||||
(backtrace-print-frame (nth index backtrace-frames) view)
|
||||
(add-text-properties min (point)
|
||||
`(backtrace-index ,index backtrace-view ,view))
|
||||
(goto-char min)))
|
||||
|
||||
(defun backtrace-expand-ellipsis (button)
|
||||
"Expand display of the elided form at BUTTON."
|
||||
(interactive)
|
||||
(goto-char (button-start button))
|
||||
(unless (get-text-property (point) 'cl-print-ellipsis)
|
||||
(if (and (> (point) (point-min))
|
||||
(get-text-property (1- (point)) 'cl-print-ellipsis))
|
||||
(backward-char)
|
||||
(user-error "No ellipsis to expand here")))
|
||||
(let* ((end (next-single-property-change (point) 'cl-print-ellipsis))
|
||||
(begin (previous-single-property-change end 'cl-print-ellipsis))
|
||||
(value (get-text-property begin 'cl-print-ellipsis))
|
||||
(props (backtrace-get-text-properties begin))
|
||||
(inhibit-read-only t))
|
||||
(backtrace--with-output-variables (backtrace-get-view)
|
||||
(delete-region begin end)
|
||||
(insert (cl-print-to-string-with-limit #'cl-print-expand-ellipsis value
|
||||
backtrace-line-length))
|
||||
(setq end (point))
|
||||
(goto-char begin)
|
||||
(while (< (point) end)
|
||||
(let ((next (next-single-property-change (point) 'cl-print-ellipsis
|
||||
nil end)))
|
||||
(when (get-text-property (point) 'cl-print-ellipsis)
|
||||
(make-text-button (point) next :type 'backtrace-ellipsis))
|
||||
(goto-char next)))
|
||||
(goto-char begin)
|
||||
(add-text-properties begin end props))))
|
||||
|
||||
(defun backtrace-expand-ellipses (&optional no-limit)
|
||||
"Expand display of all \"...\"s in the backtrace frame at point.
|
||||
\\<backtrace-mode-map>
|
||||
Each ellipsis will be limited to `backtrace-line-length'
|
||||
characters in its expansion. With optional prefix argument
|
||||
NO-LIMIT, do not limit the number of characters. Note that with
|
||||
or without the argument, using this command can result in very
|
||||
long lines and very poor display performance. If this happens
|
||||
and is a problem, use `\\[revert-buffer]' to return to the
|
||||
initial state of the Backtrace buffer."
|
||||
(interactive "P")
|
||||
(save-excursion
|
||||
(let ((start (backtrace-get-frame-start))
|
||||
(end (backtrace-get-frame-end))
|
||||
(backtrace-line-length (unless no-limit backtrace-line-length)))
|
||||
(goto-char end)
|
||||
(while (> (point) start)
|
||||
(let ((next (previous-single-property-change (point) 'cl-print-ellipsis
|
||||
nil start)))
|
||||
(when (get-text-property (point) 'cl-print-ellipsis)
|
||||
(push-button (point)))
|
||||
(goto-char next))))))
|
||||
|
||||
(defun backtrace-multi-line ()
|
||||
"Show the top level s-expression at point on multiple lines with indentation."
|
||||
(interactive)
|
||||
(backtrace--reformat-sexp #'backtrace--multi-line))
|
||||
|
||||
(defun backtrace--multi-line ()
|
||||
"Pretty print the current buffer, then remove the trailing newline."
|
||||
(set-syntax-table emacs-lisp-mode-syntax-table)
|
||||
(pp-buffer)
|
||||
(goto-char (1- (point-max)))
|
||||
(delete-char 1))
|
||||
|
||||
(defun backtrace-single-line ()
|
||||
"Show the top level s-expression at point on one line."
|
||||
(interactive)
|
||||
(backtrace--reformat-sexp #'backtrace--single-line))
|
||||
|
||||
(defun backtrace--single-line ()
|
||||
"Replace line breaks and following indentation with spaces.
|
||||
Works on the current buffer."
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\n[[:blank:]]*" nil t)
|
||||
(replace-match " ")))
|
||||
|
||||
(defun backtrace--reformat-sexp (format-function)
|
||||
"Reformat the top level sexp at point.
|
||||
Locate the top level sexp at or following point on the same line,
|
||||
and reformat it with FORMAT-FUNCTION, preserving the location of
|
||||
point within the sexp. If no sexp is found before the end of
|
||||
the line or buffer, signal an error.
|
||||
|
||||
FORMAT-FUNCTION will be called without arguments, with the
|
||||
current buffer set to a temporary buffer containing only the
|
||||
content of the sexp."
|
||||
(let* ((orig-pos (point))
|
||||
(pos (point))
|
||||
(tag (backtrace-get-form pos))
|
||||
(end (next-single-property-change pos 'backtrace-form))
|
||||
(begin (previous-single-property-change end 'backtrace-form
|
||||
nil (point-min))))
|
||||
(unless tag
|
||||
(when (or (= end (point-max)) (> end (point-at-eol)))
|
||||
(user-error "No form here to reformat"))
|
||||
(goto-char end)
|
||||
(setq pos end
|
||||
end (next-single-property-change pos 'backtrace-form)
|
||||
begin (previous-single-property-change end 'backtrace-form
|
||||
nil (point-min))))
|
||||
(let* ((offset (when (>= orig-pos begin) (- orig-pos begin)))
|
||||
(offset-marker (when offset (make-marker)))
|
||||
(content (buffer-substring begin end))
|
||||
(props (backtrace-get-text-properties begin))
|
||||
(inhibit-read-only t))
|
||||
(delete-region begin end)
|
||||
(insert (with-temp-buffer
|
||||
(insert content)
|
||||
(when offset
|
||||
(set-marker-insertion-type offset-marker t)
|
||||
(set-marker offset-marker (+ (point-min) offset)))
|
||||
(funcall format-function)
|
||||
(when offset
|
||||
(setq offset (- (marker-position offset-marker) (point-min))))
|
||||
(buffer-string)))
|
||||
(when offset
|
||||
(set-marker offset-marker (+ begin offset)))
|
||||
(save-excursion
|
||||
(goto-char begin)
|
||||
(indent-sexp))
|
||||
(add-text-properties begin (point) props)
|
||||
(if offset
|
||||
(goto-char (marker-position offset-marker))
|
||||
(goto-char orig-pos)))))
|
||||
|
||||
(defun backtrace-get-text-properties (pos)
|
||||
"Return a plist of backtrace-mode's text properties at POS."
|
||||
(apply #'append
|
||||
(mapcar (lambda (prop)
|
||||
(list prop (get-text-property pos prop)))
|
||||
'(backtrace-section backtrace-index backtrace-view
|
||||
backtrace-form))))
|
||||
|
||||
(defun backtrace-goto-source ()
|
||||
"If its location is known, jump to the source code for the frame at point."
|
||||
(interactive)
|
||||
(let* ((index (or (backtrace-get-index) (user-error "Not in a stack frame")))
|
||||
(frame (nth index backtrace-frames))
|
||||
(source-available (plist-get (backtrace-frame-flags frame)
|
||||
:source-available)))
|
||||
(unless (and source-available
|
||||
(catch 'done
|
||||
(dolist (func backtrace-goto-source-functions)
|
||||
(when (funcall func)
|
||||
(throw 'done t)))))
|
||||
(user-error "Source code location not known"))))
|
||||
|
||||
(defun backtrace-help-follow-symbol (&optional pos)
|
||||
"Follow cross-reference at POS, defaulting to point.
|
||||
For the cross-reference format, see `help-make-xrefs'."
|
||||
(interactive "d")
|
||||
(unless pos
|
||||
(setq pos (point)))
|
||||
(unless (push-button pos)
|
||||
;; Check if the symbol under point is a function or variable.
|
||||
(let ((sym
|
||||
(intern
|
||||
(save-excursion
|
||||
(goto-char pos) (skip-syntax-backward "w_")
|
||||
(buffer-substring (point)
|
||||
(progn (skip-syntax-forward "w_")
|
||||
(point)))))))
|
||||
(when (or (boundp sym) (fboundp sym) (facep sym))
|
||||
(describe-symbol sym)))))
|
||||
|
||||
;; Print backtrace frames
|
||||
|
||||
(defun backtrace-print (&optional remember-pos)
|
||||
"Populate the current Backtrace mode buffer.
|
||||
This erases the buffer and inserts printed representations of the
|
||||
frames. Optional argument REMEMBER-POS, if non-nil, means to
|
||||
move point to the entry with the same ID element as the current
|
||||
line and recenter window line accordingly."
|
||||
(let ((inhibit-read-only t)
|
||||
entry-index saved-pt window-line)
|
||||
(and remember-pos
|
||||
(setq entry-index (backtrace-get-index))
|
||||
(when (eq (window-buffer) (current-buffer))
|
||||
(setq window-line
|
||||
(count-screen-lines (window-start) (point)))))
|
||||
(erase-buffer)
|
||||
(when backtrace-insert-header-function
|
||||
(funcall backtrace-insert-header-function))
|
||||
(dotimes (idx (length backtrace-frames))
|
||||
(let ((beg (point))
|
||||
(elt (nth idx backtrace-frames)))
|
||||
(and entry-index
|
||||
(equal entry-index idx)
|
||||
(setq entry-index nil
|
||||
saved-pt (point)))
|
||||
(backtrace-print-frame elt backtrace-view)
|
||||
(add-text-properties
|
||||
beg (point)
|
||||
`(backtrace-index ,idx backtrace-view ,backtrace-view))))
|
||||
(set-buffer-modified-p nil)
|
||||
;; If REMEMBER-POS was specified, move to the "old" location.
|
||||
(if saved-pt
|
||||
(progn (goto-char saved-pt)
|
||||
(when window-line
|
||||
(recenter window-line)))
|
||||
(goto-char (point-min)))))
|
||||
|
||||
;; Define button type used for ...'s.
|
||||
;; Set skip property so you don't have to TAB through 100 of them to
|
||||
;; get to the next function name.
|
||||
(define-button-type 'backtrace-ellipsis
|
||||
'skip t 'action #'backtrace-expand-ellipsis
|
||||
'help-echo "mouse-2, RET: expand this ellipsis")
|
||||
|
||||
(defun backtrace-print-to-string (obj &optional limit)
|
||||
"Return a printed representation of OBJ formatted for backtraces.
|
||||
Attempt to get the length of the returned string under LIMIT
|
||||
charcters with appropriate settings of `print-level' and
|
||||
`print-length.' LIMIT defaults to `backtrace-line-length'."
|
||||
(backtrace--with-output-variables backtrace-view
|
||||
(backtrace--print-to-string obj limit)))
|
||||
|
||||
(defun backtrace--print-to-string (sexp &optional limit)
|
||||
;; This is for use by callers who wrap the call with
|
||||
;; backtrace--with-output-variables.
|
||||
(setq limit (or limit backtrace-line-length))
|
||||
(with-temp-buffer
|
||||
(insert (cl-print-to-string-with-limit #'backtrace--print sexp limit))
|
||||
;; Add a unique backtrace-form property.
|
||||
(put-text-property (point-min) (point) 'backtrace-form (gensym))
|
||||
;; Make buttons from all the "..."s. Since there might be many of
|
||||
;; them, use text property buttons.
|
||||
(goto-char (point-min))
|
||||
(while (< (point) (point-max))
|
||||
(let ((end (next-single-property-change (point) 'cl-print-ellipsis
|
||||
nil (point-max))))
|
||||
(when (get-text-property (point) 'cl-print-ellipsis)
|
||||
(make-text-button (point) end :type 'backtrace-ellipsis))
|
||||
(goto-char end)))
|
||||
(buffer-string)))
|
||||
|
||||
(defun backtrace-print-frame (frame view)
|
||||
"Insert a backtrace FRAME at point formatted according to VIEW.
|
||||
Tag the sections of the frame with the `backtrace-section' text
|
||||
property for use by navigation."
|
||||
(backtrace--with-output-variables view
|
||||
(backtrace--print-flags frame view)
|
||||
(backtrace--print-func-and-args frame view)
|
||||
(backtrace--print-locals frame view)))
|
||||
|
||||
(defun backtrace--print-flags (frame view)
|
||||
"Print the flags of a backtrace FRAME if enabled in VIEW."
|
||||
(let ((beg (point))
|
||||
(flag (plist-get (backtrace-frame-flags frame) :debug-on-exit))
|
||||
(source (plist-get (backtrace-frame-flags frame) :source-available)))
|
||||
(when (plist-get view :show-flags)
|
||||
(when source (insert ">"))
|
||||
(when flag (insert "*")))
|
||||
(insert (make-string (- backtrace--flags-width (- (point) beg)) ?\s))
|
||||
(put-text-property beg (point) 'backtrace-section 'func)))
|
||||
|
||||
(defun backtrace--print-func-and-args (frame _view)
|
||||
"Print the function, arguments and buffer position of a backtrace FRAME.
|
||||
Format it according to VIEW."
|
||||
(let* ((beg (point))
|
||||
(evald (backtrace-frame-evald frame))
|
||||
(fun (backtrace-frame-fun frame))
|
||||
(args (backtrace-frame-args frame))
|
||||
(def (and (symbolp fun) (fboundp fun) (symbol-function fun)))
|
||||
(fun-file (or (symbol-file fun 'defun)
|
||||
(and (subrp def)
|
||||
(not (eq 'unevalled (cdr (subr-arity def))))
|
||||
(find-lisp-object-file-name fun def))))
|
||||
(fun-pt (point)))
|
||||
(cond
|
||||
((and evald (not debugger-stack-frame-as-list))
|
||||
(if (atom fun)
|
||||
(funcall backtrace-print-function fun)
|
||||
(insert
|
||||
(backtrace--print-to-string fun (when args (/ backtrace-line-length 2)))))
|
||||
(if args
|
||||
(insert (backtrace--print-to-string
|
||||
args (max (truncate (/ backtrace-line-length 5))
|
||||
(- backtrace-line-length (- (point) beg)))))
|
||||
;; The backtrace-form property is so that backtrace-multi-line
|
||||
;; will find it. backtrace-multi-line doesn't do anything
|
||||
;; useful with it, just being consistent.
|
||||
(let ((start (point)))
|
||||
(insert "()")
|
||||
(put-text-property start (point) 'backtrace-form t))))
|
||||
(t
|
||||
(let ((fun-and-args (cons fun args)))
|
||||
(insert (backtrace--print-to-string fun-and-args)))
|
||||
(cl-incf fun-pt)))
|
||||
(when fun-file
|
||||
(make-text-button fun-pt (+ fun-pt (length (symbol-name fun)))
|
||||
:type 'help-function-def
|
||||
'help-args (list fun fun-file)))
|
||||
;; After any frame that uses eval-buffer, insert a comment that
|
||||
;; states the buffer position it's reading at.
|
||||
(when (backtrace-frame-pos frame)
|
||||
(insert " ; Reading at ")
|
||||
(let ((pos (point)))
|
||||
(insert (format "buffer position %d" (backtrace-frame-pos frame)))
|
||||
(make-button pos (point) :type 'backtrace-buffer-pos
|
||||
'backtrace-buffer (backtrace-frame-buffer frame)
|
||||
'backtrace-pos (backtrace-frame-pos frame))))
|
||||
(insert "\n")
|
||||
(put-text-property beg (point) 'backtrace-section 'func)))
|
||||
|
||||
(defun backtrace--print-locals (frame view)
|
||||
"Print a backtrace FRAME's local variables according to VIEW.
|
||||
Print them only if :show-locals is non-nil in the VIEW plist."
|
||||
(when (plist-get view :show-locals)
|
||||
(let* ((beg (point))
|
||||
(locals (backtrace-frame-locals frame)))
|
||||
(if (null locals)
|
||||
(insert " [no locals]\n")
|
||||
(pcase-dolist (`(,symbol . ,value) locals)
|
||||
(insert " ")
|
||||
(backtrace--print symbol)
|
||||
(insert " = ")
|
||||
(insert (backtrace--print-to-string value))
|
||||
(insert "\n")))
|
||||
(put-text-property beg (point) 'backtrace-section 'locals))))
|
||||
|
||||
(defun backtrace--print (obj &optional stream)
|
||||
"Attempt to print OBJ to STREAM using `backtrace-print-function'.
|
||||
Fall back to `prin1' if there is an error."
|
||||
(condition-case err
|
||||
(funcall backtrace-print-function obj stream)
|
||||
(error
|
||||
(message "Error in backtrace printer: %S" err)
|
||||
(prin1 obj stream))))
|
||||
|
||||
(defun backtrace-update-flags ()
|
||||
"Update the display of the flags in the backtrace frame at point."
|
||||
(let ((view (backtrace-get-view))
|
||||
(begin (backtrace-get-frame-start)))
|
||||
(when (plist-get view :show-flags)
|
||||
(save-excursion
|
||||
(goto-char begin)
|
||||
(let ((props (backtrace-get-text-properties begin))
|
||||
(inhibit-read-only t)
|
||||
(standard-output (current-buffer)))
|
||||
(delete-char backtrace--flags-width)
|
||||
(backtrace--print-flags (nth (backtrace-get-index) backtrace-frames)
|
||||
view)
|
||||
(add-text-properties begin (point) props))))))
|
||||
|
||||
(defun backtrace--filter-visible (beg end &optional _delete)
|
||||
"Return the visible text between BEG and END."
|
||||
(let ((result ""))
|
||||
(while (< beg end)
|
||||
(let ((next (next-single-char-property-change beg 'invisible)))
|
||||
(unless (get-char-property beg 'invisible)
|
||||
(setq result (concat result (buffer-substring beg (min end next)))))
|
||||
(setq beg next)))
|
||||
result))
|
||||
|
||||
;;; The mode definition
|
||||
|
||||
(define-derived-mode backtrace-mode special-mode "Backtrace"
|
||||
"Generic major mode for examining an Elisp stack backtrace.
|
||||
This mode can be used directly, or other major modes can be
|
||||
derived from it, using `define-derived-mode'.
|
||||
|
||||
In this major mode, the buffer contains some optional lines of
|
||||
header text followed by backtrace frames, each consisting of one
|
||||
or more whole lines.
|
||||
|
||||
Letters in this mode do not insert themselves; instead they are
|
||||
commands.
|
||||
\\<backtrace-mode-map>
|
||||
\\{backtrace-mode-map}
|
||||
|
||||
A mode which inherits from Backtrace mode, or a command which
|
||||
creates a backtrace-mode buffer, should usually do the following:
|
||||
|
||||
- Set `backtrace-revert-hook', if the buffer contents need
|
||||
to be specially recomputed prior to `revert-buffer'.
|
||||
- Maybe set `backtrace-insert-header-function' to a function to create
|
||||
header text for the buffer.
|
||||
- Set `backtrace-frames' (see below).
|
||||
- Maybe modify `backtrace-view' (see below).
|
||||
- Maybe set `backtrace-print-function'.
|
||||
|
||||
A command which creates or switches to a Backtrace mode buffer,
|
||||
such as `ert-results-pop-to-backtrace-for-test-at-point', should
|
||||
initialize `backtrace-frames' to a list of `backtrace-frame'
|
||||
objects (`backtrace-get-frames' is provided for that purpose, if
|
||||
desired), and may optionally modify `backtrace-view', which is a
|
||||
plist describing the appearance of the backtrace. Finally, it
|
||||
should call `backtrace-print'.
|
||||
|
||||
`backtrace-print' calls `backtrace-insert-header-function'
|
||||
followed by `backtrace-print-frame', once for each stack frame."
|
||||
:syntax-table emacs-lisp-mode-syntax-table
|
||||
(when backtrace-fontify
|
||||
(setq font-lock-defaults
|
||||
'((backtrace-font-lock-keywords
|
||||
backtrace-font-lock-keywords-1
|
||||
backtrace-font-lock-keywords-2)
|
||||
nil nil nil nil
|
||||
(font-lock-syntactic-face-function
|
||||
. lisp-font-lock-syntactic-face-function))))
|
||||
(setq truncate-lines t)
|
||||
(buffer-disable-undo)
|
||||
;; In debug.el, from 1998 to 2009 this was set to nil, reason stated
|
||||
;; was because of bytecode. Since 2009 it's been set to t, but the
|
||||
;; default is t so I think this isn't necessary.
|
||||
;; (set-buffer-multibyte t)
|
||||
(setq-local revert-buffer-function #'backtrace-revert)
|
||||
(setq-local filter-buffer-substring-function #'backtrace--filter-visible)
|
||||
(setq-local indent-line-function 'lisp-indent-line)
|
||||
(setq-local indent-region-function 'lisp-indent-region)
|
||||
(add-hook 'xref-backend-functions #'backtrace--xref-backend nil t))
|
||||
|
||||
(put 'backtrace-mode 'mode-class 'special)
|
||||
|
||||
;;; Backtrace printing
|
||||
|
||||
;;;###autoload
|
||||
(defun backtrace ()
|
||||
"Print a trace of Lisp function calls currently active.
|
||||
Output stream used is value of `standard-output'."
|
||||
(princ (backtrace-to-string (backtrace-get-frames 'backtrace)))
|
||||
nil)
|
||||
|
||||
(defun backtrace-to-string(&optional frames)
|
||||
"Format FRAMES, a list of `backtrace-frame' objects, for output.
|
||||
Return the result as a string. If FRAMES is nil, use all
|
||||
function calls currently active."
|
||||
(unless frames (setq frames (backtrace-get-frames 'backtrace-to-string)))
|
||||
(let ((backtrace-fontify nil))
|
||||
(with-temp-buffer
|
||||
(backtrace-mode)
|
||||
(setq backtrace-view '(:show-flags t)
|
||||
backtrace-frames frames
|
||||
backtrace-print-function #'cl-prin1)
|
||||
(backtrace-print)
|
||||
(substring-no-properties (filter-buffer-substring (point-min)
|
||||
(point-max))))))
|
||||
|
||||
(provide 'backtrace)
|
||||
|
||||
;;; backtrace.el ends here
|
@ -55,10 +55,19 @@ call other entry points instead, such as `cl-prin1'."
|
||||
;; we should only use it for objects which don't have nesting.
|
||||
(prin1 object stream))
|
||||
|
||||
(cl-defgeneric cl-print-object-contents (_object _start _stream)
|
||||
"Dispatcher to print the contents of OBJECT on STREAM.
|
||||
Print the contents starting with the item at START, without
|
||||
delimiters."
|
||||
;; Every cl-print-object method which can print an ellipsis should
|
||||
;; have a matching cl-print-object-contents method to expand an
|
||||
;; ellipsis.
|
||||
(error "Missing cl-print-object-contents method"))
|
||||
|
||||
(cl-defmethod cl-print-object ((object cons) stream)
|
||||
(if (and cl-print--depth (natnump print-level)
|
||||
(> cl-print--depth print-level))
|
||||
(princ "..." stream)
|
||||
(cl-print-insert-ellipsis object 0 stream)
|
||||
(let ((car (pop object))
|
||||
(count 1))
|
||||
(if (and print-quoted
|
||||
@ -84,23 +93,60 @@ call other entry points instead, such as `cl-prin1'."
|
||||
(princ " " stream)
|
||||
(if (or (not (natnump print-length)) (> print-length count))
|
||||
(cl-print-object (pop object) stream)
|
||||
(princ "..." stream)
|
||||
(cl-print-insert-ellipsis object print-length stream)
|
||||
(setq object nil))
|
||||
(cl-incf count))
|
||||
(when object
|
||||
(princ " . " stream) (cl-print-object object stream))
|
||||
(princ ")" stream)))))
|
||||
|
||||
(cl-defmethod cl-print-object-contents ((object cons) _start stream)
|
||||
(let ((count 0))
|
||||
(while (and (consp object)
|
||||
(not (cond
|
||||
(cl-print--number-table
|
||||
(numberp (gethash object cl-print--number-table)))
|
||||
((memq object cl-print--currently-printing))
|
||||
(t (push object cl-print--currently-printing)
|
||||
nil))))
|
||||
(unless (zerop count)
|
||||
(princ " " stream))
|
||||
(if (or (not (natnump print-length)) (> print-length count))
|
||||
(cl-print-object (pop object) stream)
|
||||
(cl-print-insert-ellipsis object print-length stream)
|
||||
(setq object nil))
|
||||
(cl-incf count))
|
||||
(when object
|
||||
(princ " . " stream) (cl-print-object object stream))))
|
||||
|
||||
(cl-defmethod cl-print-object ((object vector) stream)
|
||||
(princ "[" stream)
|
||||
(let ((count (length object)))
|
||||
(dotimes (i (if (natnump print-length)
|
||||
(min print-length count) count))
|
||||
(unless (zerop i) (princ " " stream))
|
||||
(cl-print-object (aref object i) stream))
|
||||
(when (and (natnump print-length) (< print-length count))
|
||||
(princ " ..." stream)))
|
||||
(princ "]" stream))
|
||||
(if (and cl-print--depth (natnump print-level)
|
||||
(> cl-print--depth print-level))
|
||||
(cl-print-insert-ellipsis object 0 stream)
|
||||
(princ "[" stream)
|
||||
(let* ((len (length object))
|
||||
(limit (if (natnump print-length)
|
||||
(min print-length len) len)))
|
||||
(dotimes (i limit)
|
||||
(unless (zerop i) (princ " " stream))
|
||||
(cl-print-object (aref object i) stream))
|
||||
(when (< limit len)
|
||||
(princ " " stream)
|
||||
(cl-print-insert-ellipsis object limit stream)))
|
||||
(princ "]" stream)))
|
||||
|
||||
(cl-defmethod cl-print-object-contents ((object vector) start stream)
|
||||
(let* ((len (length object))
|
||||
(limit (if (natnump print-length)
|
||||
(min (+ start print-length) len) len))
|
||||
(i start))
|
||||
(while (< i limit)
|
||||
(unless (= i start) (princ " " stream))
|
||||
(cl-print-object (aref object i) stream)
|
||||
(cl-incf i))
|
||||
(when (< limit len)
|
||||
(princ " " stream)
|
||||
(cl-print-insert-ellipsis object limit stream))))
|
||||
|
||||
(cl-defmethod cl-print-object ((object hash-table) stream)
|
||||
(princ "#<hash-table " stream)
|
||||
@ -199,21 +245,135 @@ into a button whose action shows the function's disassembly.")
|
||||
(princ ")" stream)))
|
||||
|
||||
(cl-defmethod cl-print-object ((object cl-structure-object) stream)
|
||||
(princ "#s(" stream)
|
||||
(if (and cl-print--depth (natnump print-level)
|
||||
(> cl-print--depth print-level))
|
||||
(cl-print-insert-ellipsis object 0 stream)
|
||||
(princ "#s(" stream)
|
||||
(let* ((class (cl-find-class (type-of object)))
|
||||
(slots (cl--struct-class-slots class))
|
||||
(len (length slots))
|
||||
(limit (if (natnump print-length)
|
||||
(min print-length len) len)))
|
||||
(princ (cl--struct-class-name class) stream)
|
||||
(dotimes (i limit)
|
||||
(let ((slot (aref slots i)))
|
||||
(princ " :" stream)
|
||||
(princ (cl--slot-descriptor-name slot) stream)
|
||||
(princ " " stream)
|
||||
(cl-print-object (aref object (1+ i)) stream)))
|
||||
(when (< limit len)
|
||||
(princ " " stream)
|
||||
(cl-print-insert-ellipsis object limit stream)))
|
||||
(princ ")" stream)))
|
||||
|
||||
(cl-defmethod cl-print-object-contents ((object cl-structure-object) start stream)
|
||||
(let* ((class (cl-find-class (type-of object)))
|
||||
(slots (cl--struct-class-slots class))
|
||||
(count (length slots)))
|
||||
(princ (cl--struct-class-name class) stream)
|
||||
(dotimes (i (if (natnump print-length)
|
||||
(min print-length count) count))
|
||||
(len (length slots))
|
||||
(limit (if (natnump print-length)
|
||||
(min (+ start print-length) len) len))
|
||||
(i start))
|
||||
(while (< i limit)
|
||||
(let ((slot (aref slots i)))
|
||||
(princ " :" stream)
|
||||
(unless (= i start) (princ " " stream))
|
||||
(princ ":" stream)
|
||||
(princ (cl--slot-descriptor-name slot) stream)
|
||||
(princ " " stream)
|
||||
(cl-print-object (aref object (1+ i)) stream)))
|
||||
(when (and (natnump print-length) (< print-length count))
|
||||
(princ " ..." stream)))
|
||||
(princ ")" stream))
|
||||
(cl-print-object (aref object (1+ i)) stream))
|
||||
(cl-incf i))
|
||||
(when (< limit len)
|
||||
(princ " " stream)
|
||||
(cl-print-insert-ellipsis object limit stream))))
|
||||
|
||||
(cl-defmethod cl-print-object ((object string) stream)
|
||||
(unless stream (setq stream standard-output))
|
||||
(let* ((has-properties (or (text-properties-at 0 object)
|
||||
(next-property-change 0 object)))
|
||||
(len (length object))
|
||||
(limit (if (natnump print-length) (min print-length len) len)))
|
||||
(if (and has-properties
|
||||
cl-print--depth
|
||||
(natnump print-level)
|
||||
(> cl-print--depth print-level))
|
||||
(cl-print-insert-ellipsis object 0 stream)
|
||||
;; Print all or part of the string
|
||||
(when has-properties
|
||||
(princ "#(" stream))
|
||||
(if (= limit len)
|
||||
(prin1 (if has-properties (substring-no-properties object) object)
|
||||
stream)
|
||||
(let ((part (concat (substring-no-properties object 0 limit) "...")))
|
||||
(prin1 part stream)
|
||||
(when (bufferp stream)
|
||||
(with-current-buffer stream
|
||||
(cl-print-propertize-ellipsis object limit
|
||||
(- (point) 4)
|
||||
(- (point) 1) stream)))))
|
||||
;; Print the property list.
|
||||
(when has-properties
|
||||
(let* ((interval-limit (and (natnump print-length)
|
||||
(max 1 (/ print-length 3))))
|
||||
(interval-count 0)
|
||||
(start-pos (if (text-properties-at 0 object)
|
||||
0 (next-property-change 0 object)))
|
||||
(end-pos (next-property-change start-pos object len)))
|
||||
(while (and (or (null interval-limit)
|
||||
(< interval-count interval-limit))
|
||||
(< start-pos len))
|
||||
(let ((props (text-properties-at start-pos object)))
|
||||
(when props
|
||||
(princ " " stream) (princ start-pos stream)
|
||||
(princ " " stream) (princ end-pos stream)
|
||||
(princ " " stream) (cl-print-object props stream)
|
||||
(cl-incf interval-count))
|
||||
(setq start-pos end-pos
|
||||
end-pos (next-property-change start-pos object len))))
|
||||
(when (< start-pos len)
|
||||
(princ " " stream)
|
||||
(cl-print-insert-ellipsis object (list start-pos) stream)))
|
||||
(princ ")" stream)))))
|
||||
|
||||
(cl-defmethod cl-print-object-contents ((object string) start stream)
|
||||
;; If START is an integer, it is an index into the string, and the
|
||||
;; ellipsis that needs to be expanded is part of the string. If
|
||||
;; START is a cons, its car is an index into the string, and the
|
||||
;; ellipsis that needs to be expanded is in the property list.
|
||||
(let* ((len (length object)))
|
||||
(if (atom start)
|
||||
;; Print part of the string.
|
||||
(let* ((limit (if (natnump print-length)
|
||||
(min (+ start print-length) len) len))
|
||||
(substr (substring-no-properties object start limit))
|
||||
(printed (prin1-to-string substr))
|
||||
(trimmed (substring printed 1 (1- (length printed)))))
|
||||
(princ trimmed)
|
||||
(when (< limit len)
|
||||
(cl-print-insert-ellipsis object limit stream)))
|
||||
|
||||
;; Print part of the property list.
|
||||
(let* ((first t)
|
||||
(interval-limit (and (natnump print-length)
|
||||
(max 1 (/ print-length 3))))
|
||||
(interval-count 0)
|
||||
(start-pos (car start))
|
||||
(end-pos (next-property-change start-pos object len)))
|
||||
(while (and (or (null interval-limit)
|
||||
(< interval-count interval-limit))
|
||||
(< start-pos len))
|
||||
(let ((props (text-properties-at start-pos object)))
|
||||
(when props
|
||||
(if first
|
||||
(setq first nil)
|
||||
(princ " " stream))
|
||||
(princ start-pos stream)
|
||||
(princ " " stream) (princ end-pos stream)
|
||||
(princ " " stream) (cl-print-object props stream)
|
||||
(cl-incf interval-count))
|
||||
(setq start-pos end-pos
|
||||
end-pos (next-property-change start-pos object len))))
|
||||
(when (< start-pos len)
|
||||
(princ " " stream)
|
||||
(cl-print-insert-ellipsis object (list start-pos) stream))))))
|
||||
|
||||
;;; Circularity and sharing.
|
||||
|
||||
@ -275,8 +435,17 @@ into a button whose action shows the function's disassembly.")
|
||||
(push cdr stack)
|
||||
(push car stack))
|
||||
((pred stringp)
|
||||
;; We presumably won't print its text-properties.
|
||||
nil)
|
||||
(let* ((len (length object))
|
||||
(start (if (text-properties-at 0 object)
|
||||
0 (next-property-change 0 object)))
|
||||
(end (and start
|
||||
(next-property-change start object len))))
|
||||
(while (and start (< start len))
|
||||
(let ((props (text-properties-at start object)))
|
||||
(when props
|
||||
(push props stack))
|
||||
(setq start end
|
||||
end (next-property-change start object len))))))
|
||||
((or (pred arrayp) (pred byte-code-function-p))
|
||||
;; FIXME: Inefficient for char-tables!
|
||||
(dotimes (i (length object))
|
||||
@ -291,6 +460,48 @@ into a button whose action shows the function's disassembly.")
|
||||
(cl-print--find-sharing object print-number-table)))
|
||||
print-number-table))
|
||||
|
||||
(defun cl-print-insert-ellipsis (object start stream)
|
||||
"Print \"...\" to STREAM with the `cl-print-ellipsis' text property.
|
||||
Save state in the text property in order to print the elided part
|
||||
of OBJECT later. START should be 0 if the whole OBJECT is being
|
||||
elided, otherwise it should be an index or other pointer into the
|
||||
internals of OBJECT which can be passed to
|
||||
`cl-print-object-contents' at a future time."
|
||||
(unless stream (setq stream standard-output))
|
||||
(let ((ellipsis-start (and (bufferp stream)
|
||||
(with-current-buffer stream (point)))))
|
||||
(princ "..." stream)
|
||||
(when ellipsis-start
|
||||
(with-current-buffer stream
|
||||
(cl-print-propertize-ellipsis object start ellipsis-start (point)
|
||||
stream)))))
|
||||
|
||||
(defun cl-print-propertize-ellipsis (object start beg end stream)
|
||||
"Add the `cl-print-ellipsis' property between BEG and END.
|
||||
STREAM should be a buffer. OBJECT and START are as described in
|
||||
`cl-print-insert-ellipsis'."
|
||||
(let ((value (list object start cl-print--number-table
|
||||
cl-print--currently-printing)))
|
||||
(with-current-buffer stream
|
||||
(put-text-property beg end 'cl-print-ellipsis value stream))))
|
||||
|
||||
;;;###autoload
|
||||
(defun cl-print-expand-ellipsis (value stream)
|
||||
"Print the expansion of an ellipsis to STREAM.
|
||||
VALUE should be the value of the `cl-print-ellipsis' text property
|
||||
which was attached to the ellipsis by `cl-prin1'."
|
||||
(let ((cl-print--depth 1)
|
||||
(object (nth 0 value))
|
||||
(start (nth 1 value))
|
||||
(cl-print--number-table (nth 2 value))
|
||||
(print-number-table (nth 2 value))
|
||||
(cl-print--currently-printing (nth 3 value)))
|
||||
(when (eq object (car cl-print--currently-printing))
|
||||
(pop cl-print--currently-printing))
|
||||
(if (equal start 0)
|
||||
(cl-print-object object stream)
|
||||
(cl-print-object-contents object start stream))))
|
||||
|
||||
;;;###autoload
|
||||
(defun cl-prin1 (object &optional stream)
|
||||
"Print OBJECT on STREAM according to its type.
|
||||
@ -313,5 +524,45 @@ node `(elisp)Output Variables'."
|
||||
(cl-prin1 object (current-buffer))
|
||||
(buffer-string)))
|
||||
|
||||
;;;###autoload
|
||||
(defun cl-print-to-string-with-limit (print-function value limit)
|
||||
"Return a string containing a printed representation of VALUE.
|
||||
Attempt to get the length of the returned string under LIMIT
|
||||
characters with appropriate settings of `print-level' and
|
||||
`print-length.' Use PRINT-FUNCTION to print, which should take
|
||||
the arguments VALUE and STREAM and which should respect
|
||||
`print-length' and `print-level'. LIMIT may be nil or zero in
|
||||
which case PRINT-FUNCTION will be called with `print-level' and
|
||||
`print-length' bound to nil.
|
||||
|
||||
Use this function with `cl-prin1' to print an object,
|
||||
abbreviating it with ellipses to fit within a size limit. Use
|
||||
this function with `cl-prin1-expand-ellipsis' to expand an
|
||||
ellipsis, abbreviating the expansion to stay within a size
|
||||
limit."
|
||||
(setq limit (and (natnump limit)
|
||||
(not (zerop limit))
|
||||
limit))
|
||||
;; Since this is used by the debugger when stack space may be
|
||||
;; limited, if you increase print-level here, add more depth in
|
||||
;; call_debugger (bug#31919).
|
||||
(let* ((print-length (when limit (min limit 50)))
|
||||
(print-level (when limit (min 8 (truncate (log limit)))))
|
||||
(delta (when limit
|
||||
(max 1 (truncate (/ print-length print-level))))))
|
||||
(with-temp-buffer
|
||||
(catch 'done
|
||||
(while t
|
||||
(erase-buffer)
|
||||
(funcall print-function value (current-buffer))
|
||||
;; Stop when either print-level is too low or the value is
|
||||
;; successfully printed in the space allowed.
|
||||
(when (or (not limit)
|
||||
(< (- (point-max) (point-min)) limit)
|
||||
(= print-level 2))
|
||||
(throw 'done (buffer-string)))
|
||||
(cl-decf print-level)
|
||||
(cl-decf print-length delta))))))
|
||||
|
||||
(provide 'cl-print)
|
||||
;;; cl-print.el ends here
|
||||
|
@ -28,6 +28,7 @@
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'backtrace)
|
||||
(require 'button)
|
||||
|
||||
(defgroup debugger nil
|
||||
@ -133,6 +134,25 @@ where CAUSE can be:
|
||||
- exit: called because of exit of a flagged function.
|
||||
- error: called because of `debug-on-error'.")
|
||||
|
||||
(cl-defstruct (debugger--buffer-state
|
||||
(:constructor debugger--save-buffer-state
|
||||
(&aux (mode major-mode)
|
||||
(header backtrace-insert-header-function)
|
||||
(frames backtrace-frames)
|
||||
(content (buffer-string))
|
||||
(pos (point)))))
|
||||
mode header frames content pos)
|
||||
|
||||
(defun debugger--restore-buffer-state (state)
|
||||
(unless (derived-mode-p (debugger--buffer-state-mode state))
|
||||
(funcall (debugger--buffer-state-mode state)))
|
||||
(setq backtrace-insert-header-function (debugger--buffer-state-header state)
|
||||
backtrace-frames (debugger--buffer-state-frames state))
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert (debugger--buffer-state-content state)))
|
||||
(goto-char (debugger--buffer-state-pos state)))
|
||||
|
||||
;;;###autoload
|
||||
(setq debugger 'debug)
|
||||
;;;###autoload
|
||||
@ -174,7 +194,7 @@ first will be printed into the backtrace buffer."
|
||||
(debugger-previous-state
|
||||
(if (get-buffer "*Backtrace*")
|
||||
(with-current-buffer (get-buffer "*Backtrace*")
|
||||
(list major-mode (buffer-string)))))
|
||||
(debugger--save-buffer-state))))
|
||||
(debugger-args args)
|
||||
(debugger-buffer (get-buffer-create "*Backtrace*"))
|
||||
(debugger-old-buffer (current-buffer))
|
||||
@ -236,7 +256,8 @@ first will be printed into the backtrace buffer."
|
||||
(window-total-height debugger-window)))
|
||||
(error nil)))
|
||||
(setq debugger-previous-window debugger-window))
|
||||
(debugger-mode)
|
||||
(unless (derived-mode-p 'debugger-mode)
|
||||
(debugger-mode))
|
||||
(debugger-setup-buffer debugger-args)
|
||||
(when noninteractive
|
||||
;; If the backtrace is long, save the beginning
|
||||
@ -280,15 +301,14 @@ first will be printed into the backtrace buffer."
|
||||
(setq debugger-previous-window nil))
|
||||
;; Restore previous state of debugger-buffer in case we were
|
||||
;; in a recursive invocation of the debugger, otherwise just
|
||||
;; erase the buffer and put it into fundamental mode.
|
||||
;; erase the buffer.
|
||||
(when (buffer-live-p debugger-buffer)
|
||||
(with-current-buffer debugger-buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(if (null debugger-previous-state)
|
||||
(fundamental-mode)
|
||||
(insert (nth 1 debugger-previous-state))
|
||||
(funcall (nth 0 debugger-previous-state))))))
|
||||
(if debugger-previous-state
|
||||
(debugger--restore-buffer-state debugger-previous-state)
|
||||
(setq backtrace-insert-header-function nil)
|
||||
(setq backtrace-frames nil)
|
||||
(backtrace-print))))
|
||||
(with-timeout-unsuspend debugger-with-timeout-suspend)
|
||||
(set-match-data debugger-outer-match-data)))
|
||||
(setq debug-on-next-call debugger-step-after-exit)
|
||||
@ -301,112 +321,80 @@ first will be printed into the backtrace buffer."
|
||||
(message "Error in debug printer: %S" err)
|
||||
(prin1 obj stream))))
|
||||
|
||||
(defun debugger-insert-backtrace (frames do-xrefs)
|
||||
"Format and insert the backtrace FRAMES at point.
|
||||
Make functions into cross-reference buttons if DO-XREFS is non-nil."
|
||||
(let ((standard-output (current-buffer))
|
||||
(eval-buffers eval-buffer-list))
|
||||
(require 'help-mode) ; Define `help-function-def' button type.
|
||||
(pcase-dolist (`(,evald ,fun ,args ,flags) frames)
|
||||
(insert (if (plist-get flags :debug-on-exit)
|
||||
"* " " "))
|
||||
(let ((fun-file (and do-xrefs (symbol-file fun 'defun)))
|
||||
(fun-pt (point)))
|
||||
(cond
|
||||
((and evald (not debugger-stack-frame-as-list))
|
||||
(debugger--print fun)
|
||||
(if args (debugger--print args) (princ "()")))
|
||||
(t
|
||||
(debugger--print (cons fun args))
|
||||
(cl-incf fun-pt)))
|
||||
(when fun-file
|
||||
(make-text-button fun-pt (+ fun-pt (length (symbol-name fun)))
|
||||
:type 'help-function-def
|
||||
'help-args (list fun fun-file))))
|
||||
;; After any frame that uses eval-buffer, insert a line that
|
||||
;; states the buffer position it's reading at.
|
||||
(when (and eval-buffers (memq fun '(eval-buffer eval-region)))
|
||||
(insert (format " ; Reading at buffer position %d"
|
||||
;; This will get the wrong result if there are
|
||||
;; two nested eval-region calls for the same
|
||||
;; buffer. That's not a very useful case.
|
||||
(with-current-buffer (pop eval-buffers)
|
||||
(point)))))
|
||||
(insert "\n"))))
|
||||
|
||||
(defun debugger-setup-buffer (args)
|
||||
"Initialize the `*Backtrace*' buffer for entry to the debugger.
|
||||
That buffer should be current already."
|
||||
(setq buffer-read-only nil)
|
||||
(erase-buffer)
|
||||
(set-buffer-multibyte t) ;Why was it nil ? -stef
|
||||
(setq buffer-undo-list t)
|
||||
That buffer should be current already and in debugger-mode."
|
||||
(setq backtrace-frames (nthcdr
|
||||
;; Remove debug--implement-debug-on-entry and the
|
||||
;; advice's `apply' frame.
|
||||
(if (eq (car args) 'debug) 3 1)
|
||||
(backtrace-get-frames 'debug)))
|
||||
(when (eq (car-safe args) 'exit)
|
||||
(setq debugger-value (nth 1 args))
|
||||
(setf (cl-getf (backtrace-frame-flags (car backtrace-frames))
|
||||
:debug-on-exit)
|
||||
nil))
|
||||
|
||||
(setq backtrace-view (plist-put backtrace-view :show-flags t)
|
||||
backtrace-insert-header-function (lambda ()
|
||||
(debugger--insert-header args))
|
||||
backtrace-print-function debugger-print-function)
|
||||
(backtrace-print)
|
||||
;; Place point on "stack frame 0" (bug#15101).
|
||||
(goto-char (point-min))
|
||||
(search-forward ":" (line-end-position) t)
|
||||
(when (and (< (point) (line-end-position))
|
||||
(= (char-after) ?\s))
|
||||
(forward-char)))
|
||||
|
||||
(defun debugger--insert-header (args)
|
||||
"Insert the header for the debugger's Backtrace buffer.
|
||||
Include the reason for debugger entry from ARGS."
|
||||
(insert "Debugger entered")
|
||||
(let ((frames (nthcdr
|
||||
;; Remove debug--implement-debug-on-entry and the
|
||||
;; advice's `apply' frame.
|
||||
(if (eq (car args) 'debug) 3 1)
|
||||
(backtrace-frames 'debug)))
|
||||
(print-escape-newlines t)
|
||||
(print-escape-control-characters t)
|
||||
;; If you increase print-level, add more depth in call_debugger.
|
||||
(print-level 8)
|
||||
(print-length 50)
|
||||
(pos (point)))
|
||||
(pcase (car args)
|
||||
;; lambda is for debug-on-call when a function call is next.
|
||||
;; debug is for debug-on-entry function called.
|
||||
((or `lambda `debug)
|
||||
(insert "--entering a function:\n")
|
||||
(setq pos (1- (point))))
|
||||
;; Exiting a function.
|
||||
(`exit
|
||||
(insert "--returning value: ")
|
||||
(setq pos (point))
|
||||
(setq debugger-value (nth 1 args))
|
||||
(debugger--print debugger-value (current-buffer))
|
||||
(setf (cl-getf (nth 3 (car frames)) :debug-on-exit) nil)
|
||||
(insert ?\n))
|
||||
;; Watchpoint triggered.
|
||||
((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args)))
|
||||
(insert
|
||||
"--"
|
||||
(pcase details
|
||||
(`(makunbound nil) (format "making %s void" symbol))
|
||||
(`(makunbound ,buffer) (format "killing local value of %s in buffer %s"
|
||||
symbol buffer))
|
||||
(`(defvaralias ,_) (format "aliasing %s to %s" symbol newval))
|
||||
(`(let ,_) (format "let-binding %s to %S" symbol newval))
|
||||
(`(unlet ,_) (format "ending let-binding of %s" symbol))
|
||||
(`(set nil) (format "setting %s to %S" symbol newval))
|
||||
(`(set ,buffer) (format "setting %s in buffer %s to %S"
|
||||
symbol buffer newval))
|
||||
(_ (error "unrecognized watchpoint triggered %S" (cdr args))))
|
||||
": ")
|
||||
(setq pos (point))
|
||||
(insert ?\n))
|
||||
;; Debugger entered for an error.
|
||||
(`error
|
||||
(insert "--Lisp error: ")
|
||||
(setq pos (point))
|
||||
(debugger--print (nth 1 args) (current-buffer))
|
||||
(insert ?\n))
|
||||
;; debug-on-call, when the next thing is an eval.
|
||||
(`t
|
||||
(insert "--beginning evaluation of function call form:\n")
|
||||
(setq pos (1- (point))))
|
||||
;; User calls debug directly.
|
||||
(_
|
||||
(insert ": ")
|
||||
(setq pos (point))
|
||||
(debugger--print
|
||||
(if (eq (car args) 'nil)
|
||||
(cdr args) args)
|
||||
(current-buffer))
|
||||
(insert ?\n)))
|
||||
(debugger-insert-backtrace frames t)
|
||||
;; Place point on "stack frame 0" (bug#15101).
|
||||
(goto-char pos)))
|
||||
(pcase (car args)
|
||||
;; lambda is for debug-on-call when a function call is next.
|
||||
;; debug is for debug-on-entry function called.
|
||||
((or `lambda `debug)
|
||||
(insert "--entering a function:\n"))
|
||||
;; Exiting a function.
|
||||
(`exit
|
||||
(insert "--returning value: ")
|
||||
(insert (backtrace-print-to-string debugger-value))
|
||||
(insert ?\n))
|
||||
;; Watchpoint triggered.
|
||||
((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args)))
|
||||
(insert
|
||||
"--"
|
||||
(pcase details
|
||||
(`(makunbound nil) (format "making %s void" symbol))
|
||||
(`(makunbound ,buffer) (format "killing local value of %s in buffer %s"
|
||||
symbol buffer))
|
||||
(`(defvaralias ,_) (format "aliasing %s to %s" symbol newval))
|
||||
(`(let ,_) (format "let-binding %s to %s" symbol
|
||||
(backtrace-print-to-string newval)))
|
||||
(`(unlet ,_) (format "ending let-binding of %s" symbol))
|
||||
(`(set nil) (format "setting %s to %s" symbol
|
||||
(backtrace-print-to-string newval)))
|
||||
(`(set ,buffer) (format "setting %s in buffer %s to %s"
|
||||
symbol buffer
|
||||
(backtrace-print-to-string newval)))
|
||||
(_ (error "unrecognized watchpoint triggered %S" (cdr args))))
|
||||
": ")
|
||||
(insert ?\n))
|
||||
;; Debugger entered for an error.
|
||||
(`error
|
||||
(insert "--Lisp error: ")
|
||||
(insert (backtrace-print-to-string (nth 1 args)))
|
||||
(insert ?\n))
|
||||
;; debug-on-call, when the next thing is an eval.
|
||||
(`t
|
||||
(insert "--beginning evaluation of function call form:\n"))
|
||||
;; User calls debug directly.
|
||||
(_
|
||||
(insert ": ")
|
||||
(insert (backtrace-print-to-string (if (eq (car args) 'nil)
|
||||
(cdr args) args)))
|
||||
(insert ?\n))))
|
||||
|
||||
|
||||
(defun debugger-step-through ()
|
||||
@ -426,12 +414,12 @@ Enter another debugger on next entry to eval, apply or funcall."
|
||||
(unless debugger-may-continue
|
||||
(error "Cannot continue"))
|
||||
(message "Continuing.")
|
||||
(save-excursion
|
||||
;; Check to see if we've flagged some frame for debug-on-exit, in which
|
||||
;; case we'll probably come back to the debugger soon.
|
||||
(goto-char (point-min))
|
||||
(if (re-search-forward "^\\* " nil t)
|
||||
(setq debugger-will-be-back t)))
|
||||
|
||||
;; Check to see if we've flagged some frame for debug-on-exit, in which
|
||||
;; case we'll probably come back to the debugger soon.
|
||||
(dolist (frame backtrace-frames)
|
||||
(when (plist-get (backtrace-frame-flags frame) :debug-on-exit)
|
||||
(setq debugger-will-be-back t)))
|
||||
(exit-recursive-edit))
|
||||
|
||||
(defun debugger-return-value (val)
|
||||
@ -446,12 +434,11 @@ will be used, such as in a debug on exit from a frame."
|
||||
(setq debugger-value val)
|
||||
(princ "Returning " t)
|
||||
(debugger--print debugger-value)
|
||||
(save-excursion
|
||||
;; Check to see if we've flagged some frame for debug-on-exit, in which
|
||||
;; case we'll probably come back to the debugger soon.
|
||||
(goto-char (point-min))
|
||||
(if (re-search-forward "^\\* " nil t)
|
||||
(setq debugger-will-be-back t)))
|
||||
(dolist (frame backtrace-frames)
|
||||
(when (plist-get (backtrace-frame-flags frame) :debug-on-exit)
|
||||
(setq debugger-will-be-back t)))
|
||||
(exit-recursive-edit))
|
||||
|
||||
(defun debugger-jump ()
|
||||
@ -473,63 +460,40 @@ removes itself from that hook."
|
||||
|
||||
(defun debugger-frame-number (&optional skip-base)
|
||||
"Return number of frames in backtrace before the one point points at."
|
||||
(save-excursion
|
||||
(beginning-of-line)
|
||||
(if (looking-at " *;;;\\|[a-z]")
|
||||
(error "This line is not a function call"))
|
||||
(let ((opoint (point))
|
||||
(count 0))
|
||||
(unless skip-base
|
||||
(let ((index (backtrace-get-index))
|
||||
(count 0))
|
||||
(unless index
|
||||
(error "This line is not a function call"))
|
||||
(unless skip-base
|
||||
(while (not (eq (cadr (backtrace-frame count)) 'debug))
|
||||
(setq count (1+ count)))
|
||||
;; Skip debug--implement-debug-on-entry frame.
|
||||
(when (eq 'debug--implement-debug-on-entry
|
||||
(cadr (backtrace-frame (1+ count))))
|
||||
(setq count (+ 2 count))))
|
||||
(goto-char (point-min))
|
||||
(when (looking-at "Debugger entered--\\(Lisp error\\|returning value\\):")
|
||||
(goto-char (match-end 0))
|
||||
(forward-sexp 1))
|
||||
(forward-line 1)
|
||||
(while (progn
|
||||
(forward-char 2)
|
||||
(cond ((debugger--locals-visible-p)
|
||||
(goto-char (next-single-char-property-change
|
||||
(point) 'locals-visible)))
|
||||
((= (following-char) ?\()
|
||||
(forward-sexp 1))
|
||||
(t
|
||||
(forward-sexp 2)))
|
||||
(forward-line 1)
|
||||
(<= (point) opoint))
|
||||
(if (looking-at " *;;;")
|
||||
(forward-line 1))
|
||||
(setq count (1+ count)))
|
||||
count)))
|
||||
(+ count index)))
|
||||
|
||||
(defun debugger-frame ()
|
||||
"Request entry to debugger when this frame exits.
|
||||
Applies to the frame whose line point is on in the backtrace."
|
||||
(interactive)
|
||||
(backtrace-debug (debugger-frame-number) t)
|
||||
(beginning-of-line)
|
||||
(if (= (following-char) ? )
|
||||
(let ((inhibit-read-only t))
|
||||
(delete-char 1)
|
||||
(insert ?*)))
|
||||
(beginning-of-line))
|
||||
(setf
|
||||
(cl-getf (backtrace-frame-flags (nth (backtrace-get-index) backtrace-frames))
|
||||
:debug-on-exit)
|
||||
t)
|
||||
(backtrace-update-flags))
|
||||
|
||||
(defun debugger-frame-clear ()
|
||||
"Do not enter debugger when this frame exits.
|
||||
Applies to the frame whose line point is on in the backtrace."
|
||||
(interactive)
|
||||
(backtrace-debug (debugger-frame-number) nil)
|
||||
(beginning-of-line)
|
||||
(if (= (following-char) ?*)
|
||||
(let ((inhibit-read-only t))
|
||||
(delete-char 1)
|
||||
(insert ? )))
|
||||
(beginning-of-line))
|
||||
(setf
|
||||
(cl-getf (backtrace-frame-flags (nth (backtrace-get-index) backtrace-frames))
|
||||
:debug-on-exit)
|
||||
nil)
|
||||
(backtrace-update-flags))
|
||||
|
||||
(defmacro debugger-env-macro (&rest body)
|
||||
"Run BODY in original environment."
|
||||
@ -564,69 +528,10 @@ The environment used is the one when entering the activation frame at point."
|
||||
(let ((str (eval-expression-print-format val)))
|
||||
(if str (princ str t))))))))
|
||||
|
||||
(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) 'locals-visible)))
|
||||
|
||||
(defun debugger--insert-locals (locals)
|
||||
"Insert the local variables LOCALS at point."
|
||||
(cond ((null locals)
|
||||
(insert "\n [no locals]"))
|
||||
(t
|
||||
(let ((print-escape-newlines t))
|
||||
(dolist (s+v locals)
|
||||
(let ((symbol (car s+v))
|
||||
(value (cdr s+v)))
|
||||
(insert "\n ")
|
||||
(prin1 symbol (current-buffer))
|
||||
(insert " = ")
|
||||
(debugger--print value (current-buffer))))))))
|
||||
|
||||
(defun debugger--show-locals ()
|
||||
"For the frame at point, insert locals and add text properties."
|
||||
(let* ((nframe (1+ (debugger-frame-number 'skip-base)))
|
||||
(base (debugger--backtrace-base))
|
||||
(locals (backtrace--locals nframe base))
|
||||
(inhibit-read-only t))
|
||||
(save-excursion
|
||||
(let ((start (progn
|
||||
(move-to-column 2)
|
||||
(point))))
|
||||
(end-of-line)
|
||||
(debugger--insert-locals locals)
|
||||
(add-text-properties start (point) '(locals-visible 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) 'locals-visible)))
|
||||
(start (previous-single-char-property-change end 'locals-visible))
|
||||
(inhibit-read-only t))
|
||||
(remove-text-properties start end '(locals-visible))
|
||||
(goto-char start)
|
||||
(end-of-line)
|
||||
(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))
|
||||
(menu-map (make-sparse-keymap)))
|
||||
(set-keymap-parent map button-buffer-map)
|
||||
(suppress-keymap map)
|
||||
(define-key map "-" 'negative-argument)
|
||||
(let ((map (make-keymap)))
|
||||
(set-keymap-parent map backtrace-mode-map)
|
||||
(define-key map "b" 'debugger-frame)
|
||||
(define-key map "c" 'debugger-continue)
|
||||
(define-key map "j" 'debugger-jump)
|
||||
@ -634,63 +539,47 @@ The environment used is the one when entering the activation frame at point."
|
||||
(define-key map "u" 'debugger-frame-clear)
|
||||
(define-key map "d" 'debugger-step-through)
|
||||
(define-key map "l" 'debugger-list-functions)
|
||||
(define-key map "h" 'describe-mode)
|
||||
(define-key map "q" 'top-level)
|
||||
(define-key map "q" 'debugger-quit)
|
||||
(define-key map "e" 'debugger-eval-expression)
|
||||
(define-key map "v" 'debugger-toggle-locals) ; "v" is for "variables".
|
||||
(define-key map " " 'next-line)
|
||||
(define-key map "R" 'debugger-record-expression)
|
||||
(define-key map "\C-m" 'debug-help-follow)
|
||||
(define-key map [mouse-2] 'push-button)
|
||||
(define-key map [menu-bar debugger] (cons "Debugger" menu-map))
|
||||
(define-key menu-map [deb-top]
|
||||
'(menu-item "Quit" top-level
|
||||
:help "Quit debugging and return to top level"))
|
||||
(define-key menu-map [deb-s0] '("--"))
|
||||
(define-key menu-map [deb-descr]
|
||||
'(menu-item "Describe Debugger Mode" describe-mode
|
||||
:help "Display documentation for debugger-mode"))
|
||||
(define-key menu-map [deb-hfol]
|
||||
'(menu-item "Help Follow" debug-help-follow
|
||||
:help "Follow cross-reference"))
|
||||
(define-key menu-map [deb-nxt]
|
||||
'(menu-item "Next Line" next-line
|
||||
:help "Move cursor down"))
|
||||
(define-key menu-map [deb-s1] '("--"))
|
||||
(define-key menu-map [deb-lfunc]
|
||||
'(menu-item "List debug on entry functions" debugger-list-functions
|
||||
:help "Display a list of all the functions now set to debug on entry"))
|
||||
(define-key menu-map [deb-fclear]
|
||||
'(menu-item "Cancel debug frame" debugger-frame-clear
|
||||
:help "Do not enter debugger when this frame exits"))
|
||||
(define-key menu-map [deb-frame]
|
||||
'(menu-item "Debug frame" debugger-frame
|
||||
:help "Request entry to debugger when this frame exits"))
|
||||
(define-key menu-map [deb-s2] '("--"))
|
||||
(define-key menu-map [deb-ret]
|
||||
'(menu-item "Return value..." debugger-return-value
|
||||
:help "Continue, specifying value to return."))
|
||||
(define-key menu-map [deb-rec]
|
||||
'(menu-item "Display and Record Expression" debugger-record-expression
|
||||
:help "Display a variable's value and record it in `*Backtrace-record*' buffer"))
|
||||
(define-key menu-map [deb-eval]
|
||||
'(menu-item "Eval Expression..." debugger-eval-expression
|
||||
:help "Eval an expression, in an environment like that outside the debugger"))
|
||||
(define-key menu-map [deb-jump]
|
||||
'(menu-item "Jump" debugger-jump
|
||||
:help "Continue to exit from this frame, with all debug-on-entry suspended"))
|
||||
(define-key menu-map [deb-cont]
|
||||
'(menu-item "Continue" debugger-continue
|
||||
:help "Continue, evaluating this expression without stopping"))
|
||||
(define-key menu-map [deb-step]
|
||||
'(menu-item "Step through" debugger-step-through
|
||||
:help "Proceed, stepping through subexpressions of this expression"))
|
||||
(easy-menu-define nil map ""
|
||||
'("Debugger"
|
||||
["Step through" debugger-step-through
|
||||
:help "Proceed, stepping through subexpressions of this expression"]
|
||||
["Continue" debugger-continue
|
||||
:help "Continue, evaluating this expression without stopping"]
|
||||
["Jump" debugger-jump
|
||||
:help "Continue to exit from this frame, with all debug-on-entry suspended"]
|
||||
["Eval Expression..." debugger-eval-expression
|
||||
:help "Eval an expression, in an environment like that outside the debugger"]
|
||||
["Display and Record Expression" debugger-record-expression
|
||||
:help "Display a variable's value and record it in `*Backtrace-record*' buffer"]
|
||||
["Return value..." debugger-return-value
|
||||
:help "Continue, specifying value to return."]
|
||||
"--"
|
||||
["Debug frame" debugger-frame
|
||||
:help "Request entry to debugger when this frame exits"]
|
||||
["Cancel debug frame" debugger-frame-clear
|
||||
:help "Do not enter debugger when this frame exits"]
|
||||
["List debug on entry functions" debugger-list-functions
|
||||
:help "Display a list of all the functions now set to debug on entry"]
|
||||
"--"
|
||||
["Next Line" next-line
|
||||
:help "Move cursor down"]
|
||||
["Help for Symbol" backtrace-help-follow-symbol
|
||||
:help "Show help for symbol at point"]
|
||||
["Describe Debugger Mode" describe-mode
|
||||
:help "Display documentation for debugger-mode"]
|
||||
"--"
|
||||
["Quit" debugger-quit
|
||||
:help "Quit debugging and return to top level"]))
|
||||
map))
|
||||
|
||||
(put 'debugger-mode 'mode-class 'special)
|
||||
|
||||
(define-derived-mode debugger-mode fundamental-mode "Debugger"
|
||||
"Mode for backtrace buffers, selected in debugger.
|
||||
(define-derived-mode debugger-mode backtrace-mode "Debugger"
|
||||
"Mode for debugging Emacs Lisp using a backtrace.
|
||||
\\<debugger-mode-map>
|
||||
A line starts with `*' if exiting that frame will call the debugger.
|
||||
Type \\[debugger-frame] or \\[debugger-frame-clear] to set or remove the `*'.
|
||||
@ -704,8 +593,6 @@ which functions will enter the debugger when called.
|
||||
|
||||
Complete list of commands:
|
||||
\\{debugger-mode-map}"
|
||||
(setq truncate-lines t)
|
||||
(set-syntax-table emacs-lisp-mode-syntax-table)
|
||||
(add-hook 'kill-buffer-hook
|
||||
(lambda () (if (> (recursion-depth) 0) (top-level)))
|
||||
nil t)
|
||||
@ -732,27 +619,6 @@ Complete list of commands:
|
||||
(buffer-substring (line-beginning-position 0)
|
||||
(line-end-position 0)))))
|
||||
|
||||
(defun debug-help-follow (&optional pos)
|
||||
"Follow cross-reference at POS, defaulting to point.
|
||||
|
||||
For the cross-reference format, see `help-make-xrefs'."
|
||||
(interactive "d")
|
||||
;; Ideally we'd just do (call-interactively 'help-follow) except that this
|
||||
;; assumes we're already in a *Help* buffer and reuses it, so it ends up
|
||||
;; incorrectly "reusing" the *Backtrace* buffer to show the help info.
|
||||
(unless pos
|
||||
(setq pos (point)))
|
||||
(unless (push-button pos)
|
||||
;; check if the symbol under point is a function or variable
|
||||
(let ((sym
|
||||
(intern
|
||||
(save-excursion
|
||||
(goto-char pos) (skip-syntax-backward "w_")
|
||||
(buffer-substring (point)
|
||||
(progn (skip-syntax-forward "w_")
|
||||
(point)))))))
|
||||
(when (or (boundp sym) (fboundp sym) (facep sym))
|
||||
(describe-symbol sym)))))
|
||||
|
||||
;; When you change this, you may also need to change the number of
|
||||
;; frames that the debugger skips.
|
||||
@ -853,6 +719,13 @@ To specify a nil argument interactively, exit with an empty minibuffer."
|
||||
;;(princ "be set to debug on entry, even if it is in the list.")
|
||||
)))))
|
||||
|
||||
(defun debugger-quit ()
|
||||
"Quit debugging and return to the top level."
|
||||
(interactive)
|
||||
(if (= (recursion-depth) 0)
|
||||
(quit-window)
|
||||
(top-level)))
|
||||
|
||||
(defun debug--implement-debug-watch (symbol newval op where)
|
||||
"Conditionally call the debugger.
|
||||
This function is called when SYMBOL's value is modified."
|
||||
|
@ -52,6 +52,7 @@
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'backtrace)
|
||||
(require 'macroexp)
|
||||
(require 'cl-lib)
|
||||
(eval-when-compile (require 'pcase))
|
||||
@ -206,8 +207,7 @@ Use this with caution since it is not debugged."
|
||||
"Non-nil if Edebug should unwrap results of expressions.
|
||||
That is, Edebug will try to remove its own instrumentation from the result.
|
||||
This is useful when debugging macros where the results of expressions
|
||||
are instrumented expressions. But don't do this when results might be
|
||||
circular or an infinite loop will result."
|
||||
are instrumented expressions."
|
||||
:type 'boolean
|
||||
:group 'edebug)
|
||||
|
||||
@ -1265,25 +1265,59 @@ purpose by adding an entry to this alist, and setting
|
||||
(defun edebug-unwrap (sexp)
|
||||
"Return the unwrapped SEXP or return it as is if it is not wrapped.
|
||||
The SEXP might be the result of wrapping a body, which is a list of
|
||||
expressions; a `progn' form will be returned enclosing these forms."
|
||||
(if (consp sexp)
|
||||
(cond
|
||||
((eq 'edebug-after (car sexp))
|
||||
(nth 3 sexp))
|
||||
((eq 'edebug-enter (car sexp))
|
||||
(macroexp-progn (nthcdr 2 (nth 1 (nth 3 sexp)))))
|
||||
(t sexp);; otherwise it is not wrapped, so just return it.
|
||||
)
|
||||
sexp))
|
||||
expressions; a `progn' form will be returned enclosing these forms.
|
||||
Does not unwrap inside vectors, records, structures, or hash tables."
|
||||
(pcase sexp
|
||||
(`(edebug-after ,_before-form ,_after-index ,form)
|
||||
form)
|
||||
(`(lambda ,args (edebug-enter ',_sym ,_arglist
|
||||
(function (lambda nil . ,body))))
|
||||
`(lambda ,args ,@body))
|
||||
(`(closure ,env ,args (edebug-enter ',_sym ,_arglist
|
||||
(function (lambda nil . ,body))))
|
||||
`(closure ,env ,args ,@body))
|
||||
(`(edebug-enter ',_sym ,_args (function (lambda nil . ,body)))
|
||||
(macroexp-progn body))
|
||||
(_ sexp)))
|
||||
|
||||
(defun edebug-unwrap* (sexp)
|
||||
"Return the SEXP recursively unwrapped."
|
||||
(let ((ht (make-hash-table :test 'eq)))
|
||||
(edebug--unwrap1 sexp ht)))
|
||||
|
||||
(defun edebug--unwrap1 (sexp hash-table)
|
||||
"Unwrap SEXP using HASH-TABLE of things already unwrapped.
|
||||
HASH-TABLE contains the results of unwrapping cons cells within
|
||||
SEXP, which are reused to avoid infinite loops when SEXP is or
|
||||
contains a circular object."
|
||||
(let ((new-sexp (edebug-unwrap sexp)))
|
||||
(while (not (eq sexp new-sexp))
|
||||
(setq sexp new-sexp
|
||||
new-sexp (edebug-unwrap sexp)))
|
||||
(if (consp new-sexp)
|
||||
(mapcar #'edebug-unwrap* new-sexp)
|
||||
(let ((result (gethash new-sexp hash-table nil)))
|
||||
(unless result
|
||||
(let ((remainder new-sexp)
|
||||
current)
|
||||
(setq result (cons nil nil)
|
||||
current result)
|
||||
(while
|
||||
(progn
|
||||
(puthash remainder current hash-table)
|
||||
(setf (car current)
|
||||
(edebug--unwrap1 (car remainder) hash-table))
|
||||
(setq remainder (cdr remainder))
|
||||
(cond
|
||||
((atom remainder)
|
||||
(setf (cdr current)
|
||||
(edebug--unwrap1 remainder hash-table))
|
||||
nil)
|
||||
((gethash remainder hash-table nil)
|
||||
(setf (cdr current) (gethash remainder hash-table nil))
|
||||
nil)
|
||||
(t (setq current
|
||||
(setf (cdr current) (cons nil nil)))))))))
|
||||
result)
|
||||
new-sexp)))
|
||||
|
||||
|
||||
@ -3658,7 +3692,7 @@ be installed in `emacs-lisp-mode-map'.")
|
||||
|
||||
;; misc
|
||||
(define-key map "?" 'edebug-help)
|
||||
(define-key map "d" 'edebug-backtrace)
|
||||
(define-key map "d" 'edebug-pop-to-backtrace)
|
||||
|
||||
(define-key map "-" 'negative-argument)
|
||||
|
||||
@ -3916,8 +3950,10 @@ Global commands prefixed by `global-edebug-prefix':
|
||||
;; (setq debugger 'debug) ; use the standard debugger
|
||||
|
||||
;; Note that debug and its utilities must be byte-compiled to work,
|
||||
;; since they depend on the backtrace looking a certain way. But
|
||||
;; edebug is not dependent on this, yet.
|
||||
;; since they depend on the backtrace looking a certain way. Edebug
|
||||
;; will work if not byte-compiled, but it will not be able correctly
|
||||
;; remove its instrumentation from backtraces unless it is
|
||||
;; byte-compiled.
|
||||
|
||||
(defun edebug (&optional arg-mode &rest args)
|
||||
"Replacement for `debug'.
|
||||
@ -3947,49 +3983,136 @@ Otherwise call `debug' normally."
|
||||
(apply #'debug arg-mode args)
|
||||
))
|
||||
|
||||
;;; Backtrace buffer
|
||||
|
||||
(defun edebug-backtrace ()
|
||||
"Display a non-working backtrace. Better than nothing..."
|
||||
(defvar-local edebug-backtrace-frames nil
|
||||
"Stack frames of the current Edebug Backtrace buffer without instrumentation.
|
||||
This should be a list of `edebug---frame' objects.")
|
||||
(defvar-local edebug-instrumented-backtrace-frames nil
|
||||
"Stack frames of the current Edebug Backtrace buffer with instrumentation.
|
||||
This should be a list of `edebug---frame' objects.")
|
||||
|
||||
;; Data structure for backtrace frames with information
|
||||
;; from Edebug instrumentation found in the backtrace.
|
||||
(cl-defstruct
|
||||
(edebug--frame
|
||||
(:constructor edebug--make-frame)
|
||||
(:include backtrace-frame))
|
||||
def-name before-index after-index)
|
||||
|
||||
(defun edebug-pop-to-backtrace ()
|
||||
"Display the current backtrace in a `backtrace-mode' window."
|
||||
(interactive)
|
||||
(if (or (not edebug-backtrace-buffer)
|
||||
(null (buffer-name edebug-backtrace-buffer)))
|
||||
(setq edebug-backtrace-buffer
|
||||
(generate-new-buffer "*Backtrace*"))
|
||||
(generate-new-buffer "*Edebug Backtrace*"))
|
||||
;; Else, could just display edebug-backtrace-buffer.
|
||||
)
|
||||
(with-output-to-temp-buffer (buffer-name edebug-backtrace-buffer)
|
||||
(setq edebug-backtrace-buffer standard-output)
|
||||
(let ((print-escape-newlines t)
|
||||
(print-length 50) ; FIXME cf edebug-safe-prin1-to-string
|
||||
last-ok-point)
|
||||
(backtrace)
|
||||
(pop-to-buffer edebug-backtrace-buffer)
|
||||
(unless (derived-mode-p 'backtrace-mode)
|
||||
(backtrace-mode)
|
||||
(add-hook 'backtrace-goto-source-functions 'edebug--backtrace-goto-source))
|
||||
(setq edebug-instrumented-backtrace-frames
|
||||
(backtrace-get-frames 'edebug-debugger
|
||||
:constructor #'edebug--make-frame)
|
||||
edebug-backtrace-frames (edebug--strip-instrumentation
|
||||
edebug-instrumented-backtrace-frames)
|
||||
backtrace-frames edebug-backtrace-frames)
|
||||
(backtrace-print)
|
||||
(goto-char (point-min)))
|
||||
|
||||
;; Clean up the backtrace.
|
||||
;; Not quite right for current edebug scheme.
|
||||
(set-buffer edebug-backtrace-buffer)
|
||||
(setq truncate-lines t)
|
||||
(goto-char (point-min))
|
||||
(setq last-ok-point (point))
|
||||
(if t (progn
|
||||
(defun edebug--strip-instrumentation (frames)
|
||||
"Return a new list of backtrace frames with instrumentation removed.
|
||||
Remove frames for Edebug's functions and the lambdas in
|
||||
`edebug-enter' wrappers. Fill in the def-name, before-index
|
||||
and after-index fields in both FRAMES and the returned list
|
||||
of deinstrumented frames, for those frames where the source
|
||||
code location is known."
|
||||
(let (skip-next-lambda def-name before-index after-index results
|
||||
(index (length frames)))
|
||||
(dolist (frame (reverse frames))
|
||||
(let ((new-frame (copy-edebug--frame frame))
|
||||
(fun (edebug--frame-fun frame))
|
||||
(args (edebug--frame-args frame)))
|
||||
(cl-decf index)
|
||||
(pcase fun
|
||||
('edebug-enter
|
||||
(setq skip-next-lambda t
|
||||
def-name (nth 0 args)))
|
||||
('edebug-after
|
||||
(setq before-index (if (consp (nth 0 args))
|
||||
(nth 1 (nth 0 args))
|
||||
(nth 0 args))
|
||||
after-index (nth 1 args)))
|
||||
((pred edebug--symbol-not-prefixed-p)
|
||||
(edebug--unwrap-frame new-frame)
|
||||
(edebug--add-source-info new-frame def-name before-index after-index)
|
||||
(edebug--add-source-info frame def-name before-index after-index)
|
||||
(push new-frame results)
|
||||
(setq before-index nil
|
||||
after-index nil))
|
||||
(`(,(or 'lambda 'closure) . ,_)
|
||||
(unless skip-next-lambda
|
||||
(edebug--unwrap-frame new-frame)
|
||||
(edebug--add-source-info frame def-name before-index after-index)
|
||||
(edebug--add-source-info new-frame def-name before-index after-index)
|
||||
(push new-frame results))
|
||||
(setq before-index nil
|
||||
after-index nil
|
||||
skip-next-lambda nil)))))
|
||||
results))
|
||||
|
||||
;; Delete interspersed edebug internals.
|
||||
(while (re-search-forward "^ (?edebug" nil t)
|
||||
(beginning-of-line)
|
||||
(cond
|
||||
((looking-at "^ (edebug-after")
|
||||
;; Previous lines may contain code, so just delete this line.
|
||||
(setq last-ok-point (point))
|
||||
(forward-line 1)
|
||||
(delete-region last-ok-point (point)))
|
||||
(defun edebug--symbol-not-prefixed-p (sym)
|
||||
"Return non-nil if SYM is a symbol not prefixed by \"edebug-\"."
|
||||
(and (symbolp sym)
|
||||
(not (string-prefix-p "edebug-" (symbol-name sym)))))
|
||||
|
||||
((looking-at (if debugger-stack-frame-as-list
|
||||
"^ (edebug"
|
||||
"^ edebug"))
|
||||
(forward-line 1)
|
||||
(delete-region last-ok-point (point))
|
||||
)))
|
||||
)))))
|
||||
(defun edebug--unwrap-frame (frame)
|
||||
"Remove Edebug's instrumentation from FRAME.
|
||||
Strip it from the function and any unevaluated arguments."
|
||||
(setf (edebug--frame-fun frame) (edebug-unwrap* (edebug--frame-fun frame)))
|
||||
(unless (edebug--frame-evald frame)
|
||||
(let (results)
|
||||
(dolist (arg (edebug--frame-args frame))
|
||||
(push (edebug-unwrap* arg) results))
|
||||
(setf (edebug--frame-args frame) (nreverse results)))))
|
||||
|
||||
(defun edebug--add-source-info (frame def-name before-index after-index)
|
||||
"Update FRAME with the additional info needed by an edebug--frame.
|
||||
Save DEF-NAME, BEFORE-INDEX and AFTER-INDEX in FRAME."
|
||||
(when (and before-index def-name)
|
||||
(setf (edebug--frame-flags frame)
|
||||
(plist-put (copy-sequence (edebug--frame-flags frame))
|
||||
:source-available t)))
|
||||
(setf (edebug--frame-def-name frame) (and before-index def-name))
|
||||
(setf (edebug--frame-before-index frame) before-index)
|
||||
(setf (edebug--frame-after-index frame) after-index))
|
||||
|
||||
(defun edebug--backtrace-goto-source ()
|
||||
(let* ((index (backtrace-get-index))
|
||||
(frame (nth index backtrace-frames)))
|
||||
(when (edebug--frame-def-name frame)
|
||||
(let* ((data (get (edebug--frame-def-name frame) 'edebug))
|
||||
(marker (nth 0 data))
|
||||
(offsets (nth 2 data)))
|
||||
(pop-to-buffer (marker-buffer marker))
|
||||
(goto-char (+ (marker-position marker)
|
||||
(aref offsets (edebug--frame-before-index frame))))))))
|
||||
|
||||
(defun edebug-backtrace-show-instrumentation ()
|
||||
"Show Edebug's instrumentation in an Edebug Backtrace buffer."
|
||||
(interactive)
|
||||
(unless (eq backtrace-frames edebug-instrumented-backtrace-frames)
|
||||
(setq backtrace-frames edebug-instrumented-backtrace-frames)
|
||||
(revert-buffer)))
|
||||
|
||||
(defun edebug-backtrace-hide-instrumentation ()
|
||||
"Hide Edebug's instrumentation in an Edebug Backtrace buffer."
|
||||
(interactive)
|
||||
(unless (eq backtrace-frames edebug-backtrace-frames)
|
||||
(setq backtrace-frames edebug-backtrace-frames)
|
||||
(revert-buffer)))
|
||||
|
||||
;;; Trace display
|
||||
|
||||
@ -4163,7 +4286,7 @@ It is removed when you hit any char."
|
||||
["Bounce to Current Point" edebug-bounce-point t]
|
||||
["View Outside Windows" edebug-view-outside t]
|
||||
["Previous Result" edebug-previous-result t]
|
||||
["Show Backtrace" edebug-backtrace t]
|
||||
["Show Backtrace" edebug-pop-to-backtrace t]
|
||||
["Display Freq Count" edebug-display-freq-count t])
|
||||
|
||||
("Eval"
|
||||
|
@ -60,6 +60,7 @@
|
||||
(require 'cl-lib)
|
||||
(require 'button)
|
||||
(require 'debug)
|
||||
(require 'backtrace)
|
||||
(require 'easymenu)
|
||||
(require 'ewoc)
|
||||
(require 'find-func)
|
||||
@ -677,13 +678,6 @@ and is displayed in front of the value of MESSAGE-FORM."
|
||||
(cl-defstruct (ert-test-aborted-with-non-local-exit
|
||||
(:include ert-test-result)))
|
||||
|
||||
(defun ert--print-backtrace (backtrace do-xrefs)
|
||||
"Format the backtrace BACKTRACE to the current buffer."
|
||||
(let ((print-escape-newlines t)
|
||||
(print-level 8)
|
||||
(print-length 50))
|
||||
(debugger-insert-backtrace backtrace do-xrefs)))
|
||||
|
||||
;; A container for the state of the execution of a single test and
|
||||
;; environment data needed during its execution.
|
||||
(cl-defstruct ert--test-execution-info
|
||||
@ -732,7 +726,7 @@ run. ARGS are the arguments to `debugger'."
|
||||
;; use.
|
||||
;;
|
||||
;; Grab the frames above the debugger.
|
||||
(backtrace (cdr (backtrace-frames debugger)))
|
||||
(backtrace (cdr (backtrace-get-frames debugger)))
|
||||
(infos (reverse ert--infos)))
|
||||
(setf (ert--test-execution-info-result info)
|
||||
(cl-ecase type
|
||||
@ -1406,9 +1400,8 @@ Returns the stats object."
|
||||
(ert-test-result-with-condition
|
||||
(message "Test %S backtrace:" (ert-test-name test))
|
||||
(with-temp-buffer
|
||||
(ert--print-backtrace
|
||||
(ert-test-result-with-condition-backtrace result)
|
||||
nil)
|
||||
(insert (backtrace-to-string
|
||||
(ert-test-result-with-condition-backtrace result)))
|
||||
(if (not ert-batch-backtrace-right-margin)
|
||||
(message "%s"
|
||||
(buffer-substring-no-properties (point-min)
|
||||
@ -2450,20 +2443,20 @@ To be used in the ERT results buffer."
|
||||
(cl-etypecase result
|
||||
(ert-test-passed (error "Test passed, no backtrace available"))
|
||||
(ert-test-result-with-condition
|
||||
(let ((backtrace (ert-test-result-with-condition-backtrace result))
|
||||
(buffer (get-buffer-create "*ERT Backtrace*")))
|
||||
(let ((buffer (get-buffer-create "*ERT Backtrace*")))
|
||||
(pop-to-buffer buffer)
|
||||
(let ((inhibit-read-only t))
|
||||
(buffer-disable-undo)
|
||||
(erase-buffer)
|
||||
(ert-simple-view-mode)
|
||||
(set-buffer-multibyte t) ; mimic debugger-setup-buffer
|
||||
(setq truncate-lines t)
|
||||
(ert--print-backtrace backtrace t)
|
||||
(goto-char (point-min))
|
||||
(insert (substitute-command-keys "Backtrace for test `"))
|
||||
(ert-insert-test-name-button (ert-test-name test))
|
||||
(insert (substitute-command-keys "':\n"))))))))
|
||||
(unless (derived-mode-p 'backtrace-mode)
|
||||
(backtrace-mode))
|
||||
(setq backtrace-insert-header-function
|
||||
(lambda () (ert--insert-backtrace-header (ert-test-name test)))
|
||||
backtrace-frames (ert-test-result-with-condition-backtrace result))
|
||||
(backtrace-print)
|
||||
(goto-char (point-min)))))))
|
||||
|
||||
(defun ert--insert-backtrace-header (name)
|
||||
(insert (substitute-command-keys "Backtrace for test `"))
|
||||
(ert-insert-test-name-button name)
|
||||
(insert (substitute-command-keys "':\n")))
|
||||
|
||||
(defun ert-results-pop-to-messages-for-test-at-point ()
|
||||
"Display the part of the *Messages* buffer generated during the test at point.
|
||||
|
@ -517,6 +517,16 @@ This will generate compile-time constants from BINDINGS."
|
||||
(defvar lisp-cl-font-lock-keywords lisp-cl-font-lock-keywords-1
|
||||
"Default expressions to highlight in Lisp modes.")
|
||||
|
||||
;; Support backtrace mode.
|
||||
(defconst lisp-el-font-lock-keywords-for-backtraces lisp-el-font-lock-keywords
|
||||
"Default highlighting from Emacs Lisp mod used in Backtrace mode.")
|
||||
(defconst lisp-el-font-lock-keywords-for-backtraces-1 lisp-el-font-lock-keywords-1
|
||||
"Subdued highlighting from Emacs Lisp mode used in Backtrace mode.")
|
||||
(defconst lisp-el-font-lock-keywords-for-backtraces-2
|
||||
(remove (assoc 'lisp--match-hidden-arg lisp-el-font-lock-keywords-2)
|
||||
lisp-el-font-lock-keywords-2)
|
||||
"Gaudy highlighting from Emacs Lisp mode used in Backtrace mode.")
|
||||
|
||||
(defun lisp-string-in-doc-position-p (listbeg startpos)
|
||||
"Return true if a doc string may occur at STARTPOS inside a list.
|
||||
LISTBEG is the position of the start of the innermost list
|
||||
|
19
lisp/subr.el
19
lisp/subr.el
@ -4687,25 +4687,6 @@ The properties used on SYMBOL are `composefunc', `sendfunc',
|
||||
(put symbol 'hookvar (or hookvar 'mail-send-hook)))
|
||||
|
||||
|
||||
(defun backtrace--print-frame (evald func args flags)
|
||||
"Print a trace of a single stack frame to `standard-output'.
|
||||
EVALD, FUNC, ARGS, FLAGS are as in `mapbacktrace'."
|
||||
(princ (if (plist-get flags :debug-on-exit) "* " " "))
|
||||
(cond
|
||||
((and evald (not debugger-stack-frame-as-list))
|
||||
(cl-prin1 func)
|
||||
(if args (cl-prin1 args) (princ "()")))
|
||||
(t
|
||||
(cl-prin1 (cons func args))))
|
||||
(princ "\n"))
|
||||
|
||||
(defun backtrace ()
|
||||
"Print a trace of Lisp function calls currently active.
|
||||
Output stream used is value of `standard-output'."
|
||||
(let ((print-level (or print-level 8))
|
||||
(print-escape-control-characters t))
|
||||
(mapbacktrace #'backtrace--print-frame 'backtrace)))
|
||||
|
||||
(defun backtrace-frames (&optional base)
|
||||
"Collect all frames of current backtrace into a list.
|
||||
If non-nil, BASE should be a function, and frames before its
|
||||
|
436
test/lisp/emacs-lisp/backtrace-tests.el
Normal file
436
test/lisp/emacs-lisp/backtrace-tests.el
Normal file
@ -0,0 +1,436 @@
|
||||
;;; backtrace-tests.el --- Tests for backtraces -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2018 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Gemini Lasswell
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'backtrace)
|
||||
(require 'ert)
|
||||
(require 'ert-x)
|
||||
(require 'seq)
|
||||
|
||||
;; Delay evaluation of the backtrace-creating functions until
|
||||
;; load so that the backtraces are the same whether this file
|
||||
;; is compiled or not.
|
||||
|
||||
(eval-and-compile
|
||||
(defconst backtrace-tests--uncompiled-functions
|
||||
'(progn
|
||||
(defun backtrace-tests--make-backtrace (arg)
|
||||
(backtrace-tests--setup-buffer))
|
||||
|
||||
(defun backtrace-tests--setup-buffer ()
|
||||
"Set up the current buffer in backtrace mode."
|
||||
(backtrace-mode)
|
||||
(setq backtrace-frames (backtrace-get-frames))
|
||||
(let ((this-index))
|
||||
;; Discard all past `backtrace-tests-make-backtrace'.
|
||||
(dotimes (index (length backtrace-frames))
|
||||
(when (eq (backtrace-frame-fun (nth index backtrace-frames))
|
||||
'backtrace-tests--make-backtrace)
|
||||
(setq this-index index)))
|
||||
(setq backtrace-frames (seq-subseq backtrace-frames 0 (1+ this-index))))
|
||||
(backtrace-print))))
|
||||
|
||||
(eval backtrace-tests--uncompiled-functions))
|
||||
|
||||
(defun backtrace-tests--backtrace-lines ()
|
||||
(if debugger-stack-frame-as-list
|
||||
'(" (backtrace-get-frames)\n"
|
||||
" (setq backtrace-frames (backtrace-get-frames))\n"
|
||||
" (backtrace-tests--setup-buffer)\n"
|
||||
" (backtrace-tests--make-backtrace %s)\n")
|
||||
'(" backtrace-get-frames()\n"
|
||||
" (setq backtrace-frames (backtrace-get-frames))\n"
|
||||
" backtrace-tests--setup-buffer()\n"
|
||||
" backtrace-tests--make-backtrace(%s)\n")))
|
||||
|
||||
(defconst backtrace-tests--line-count (length (backtrace-tests--backtrace-lines)))
|
||||
|
||||
(defun backtrace-tests--backtrace-lines-with-locals ()
|
||||
(let ((lines (backtrace-tests--backtrace-lines))
|
||||
(locals '(" [no locals]\n"
|
||||
" [no locals]\n"
|
||||
" [no locals]\n"
|
||||
" arg = %s\n")))
|
||||
(apply #'append (cl-mapcar #'list lines locals))))
|
||||
|
||||
(defun backtrace-tests--result (value)
|
||||
(format (apply #'concat (backtrace-tests--backtrace-lines))
|
||||
(cl-prin1-to-string value)))
|
||||
|
||||
(defun backtrace-tests--result-with-locals (value)
|
||||
(let ((str (cl-prin1-to-string value)))
|
||||
(format (apply #'concat (backtrace-tests--backtrace-lines-with-locals))
|
||||
str str)))
|
||||
|
||||
;; TODO check that debugger-batch-max-lines still works
|
||||
|
||||
(defconst backtrace-tests--header "Test header\n")
|
||||
(defun backtrace-tests--insert-header ()
|
||||
(insert backtrace-tests--header))
|
||||
|
||||
;;; Tests
|
||||
|
||||
(ert-deftest backtrace-tests--variables ()
|
||||
"Backtrace buffers can show and hide local variables."
|
||||
(ert-with-test-buffer (:name "variables")
|
||||
(let ((results (concat backtrace-tests--header
|
||||
(backtrace-tests--result 'value)))
|
||||
(last-frame (format (nth (1- backtrace-tests--line-count)
|
||||
(backtrace-tests--backtrace-lines)) 'value))
|
||||
(last-frame-with-locals
|
||||
(format (apply #'concat (nthcdr (* 2 (1- backtrace-tests--line-count))
|
||||
(backtrace-tests--backtrace-lines-with-locals)))
|
||||
'value 'value)))
|
||||
(backtrace-tests--make-backtrace 'value)
|
||||
(setq backtrace-insert-header-function #'backtrace-tests--insert-header)
|
||||
(backtrace-print)
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results))
|
||||
;; Go to the last frame.
|
||||
(goto-char (point-max))
|
||||
(forward-line -1)
|
||||
;; Turn on locals for that frame.
|
||||
(backtrace-toggle-locals)
|
||||
(should (string= (backtrace-tests--get-substring (point) (point-max))
|
||||
last-frame-with-locals))
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
(concat results
|
||||
(format (car (last (backtrace-tests--backtrace-lines-with-locals)))
|
||||
'value))))
|
||||
;; Turn off locals for that frame.
|
||||
(backtrace-toggle-locals)
|
||||
(should (string= (backtrace-tests--get-substring (point) (point-max))
|
||||
last-frame))
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results))
|
||||
;; Turn all locals on.
|
||||
(backtrace-toggle-locals '(4))
|
||||
(should (string= (backtrace-tests--get-substring (point) (point-max))
|
||||
last-frame-with-locals))
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
(concat backtrace-tests--header
|
||||
(backtrace-tests--result-with-locals 'value))))
|
||||
;; Turn all locals off.
|
||||
(backtrace-toggle-locals '(4))
|
||||
(should (string= (backtrace-tests--get-substring
|
||||
(point) (+ (point) (length last-frame)))
|
||||
last-frame))
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results)))))
|
||||
|
||||
(ert-deftest backtrace-tests--backward-frame ()
|
||||
"`backtrace-backward-frame' moves backward to the start of a frame."
|
||||
(ert-with-test-buffer (:name "backward")
|
||||
(let ((results (concat backtrace-tests--header
|
||||
(backtrace-tests--result nil))))
|
||||
(backtrace-tests--make-backtrace nil)
|
||||
(setq backtrace-insert-header-function #'backtrace-tests--insert-header)
|
||||
(backtrace-print)
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results))
|
||||
|
||||
;; Try to move backward from header.
|
||||
(goto-char (+ (point-min) (/ (length backtrace-tests--header) 2)))
|
||||
(let ((pos (point)))
|
||||
(should-error (backtrace-backward-frame))
|
||||
(should (= pos (point))))
|
||||
|
||||
;; Try to move backward from start of first line.
|
||||
(forward-line)
|
||||
(let ((pos (point)))
|
||||
(should-error (backtrace-backward-frame))
|
||||
(should (= pos (point))))
|
||||
|
||||
;; Move backward from middle of line.
|
||||
(let ((start (point)))
|
||||
(forward-char (/ (length (nth 0 (backtrace-tests--backtrace-lines))) 2))
|
||||
(backtrace-backward-frame)
|
||||
(should (= start (point))))
|
||||
|
||||
;; Move backward from end of buffer.
|
||||
(goto-char (point-max))
|
||||
(backtrace-backward-frame)
|
||||
(let* ((last (format (car (last (backtrace-tests--backtrace-lines))) nil))
|
||||
(len (length last)))
|
||||
(should (string= (buffer-substring-no-properties (point) (+ (point) len))
|
||||
last)))
|
||||
|
||||
;; Move backward from start of line.
|
||||
(backtrace-backward-frame)
|
||||
(let* ((line (car (last (backtrace-tests--backtrace-lines) 2)))
|
||||
(len (length line)))
|
||||
(should (string= (buffer-substring-no-properties (point) (+ (point) len))
|
||||
line))))))
|
||||
|
||||
(ert-deftest backtrace-tests--forward-frame ()
|
||||
"`backtrace-forward-frame' moves forward to the start of a frame."
|
||||
(ert-with-test-buffer (:name "forward")
|
||||
(let* ((arg '(1 2 3))
|
||||
(results (concat backtrace-tests--header
|
||||
(backtrace-tests--result arg)))
|
||||
(first-line (nth 0 (backtrace-tests--backtrace-lines))))
|
||||
(backtrace-tests--make-backtrace arg)
|
||||
(setq backtrace-insert-header-function #'backtrace-tests--insert-header)
|
||||
(backtrace-print)
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results))
|
||||
;; Move forward from header.
|
||||
(goto-char (+ (point-min) (/ (length backtrace-tests--header) 2)))
|
||||
(backtrace-forward-frame)
|
||||
(should (string= (backtrace-tests--get-substring
|
||||
(point) (+ (point) (length first-line)))
|
||||
first-line))
|
||||
|
||||
(let ((start (point))
|
||||
(offset (/ (length first-line) 2))
|
||||
(second-line (nth 1 (backtrace-tests--backtrace-lines))))
|
||||
;; Move forward from start of first frame.
|
||||
(backtrace-forward-frame)
|
||||
(should (string= (backtrace-tests--get-substring
|
||||
(point) (+ (point) (length second-line)))
|
||||
second-line))
|
||||
;; Move forward from middle of first frame.
|
||||
(goto-char (+ start offset))
|
||||
(backtrace-forward-frame)
|
||||
(should (string= (backtrace-tests--get-substring
|
||||
(point) (+ (point) (length second-line)))
|
||||
second-line)))
|
||||
;; Try to move forward from middle of last frame.
|
||||
(goto-char (- (point-max)
|
||||
(/ 2 (length (car (last (backtrace-tests--backtrace-lines)))))))
|
||||
(should-error (backtrace-forward-frame))
|
||||
;; Try to move forward from end of buffer.
|
||||
(goto-char (point-max))
|
||||
(should-error (backtrace-forward-frame)))))
|
||||
|
||||
(ert-deftest backtrace-tests--single-and-multi-line ()
|
||||
"Forms in backtrace frames can be on a single line or on multiple lines."
|
||||
(ert-with-test-buffer (:name "single-multi-line")
|
||||
(let* ((arg '(lambda (x) ; Quote this so it isn't made into a closure.
|
||||
(let ((number (1+ x)))
|
||||
(+ x number))))
|
||||
(header-string "Test header: ")
|
||||
(header (format "%s%s\n" header-string arg))
|
||||
(insert-header-function (lambda ()
|
||||
(insert header-string)
|
||||
(insert (backtrace-print-to-string arg))
|
||||
(insert "\n")))
|
||||
(results (concat header (backtrace-tests--result arg)))
|
||||
(last-line (format (nth (1- backtrace-tests--line-count)
|
||||
(backtrace-tests--backtrace-lines))
|
||||
arg))
|
||||
(last-line-locals (format (nth (1- (* 2 backtrace-tests--line-count))
|
||||
(backtrace-tests--backtrace-lines-with-locals))
|
||||
arg)))
|
||||
|
||||
(backtrace-tests--make-backtrace arg)
|
||||
(setq backtrace-insert-header-function insert-header-function)
|
||||
(backtrace-print)
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results))
|
||||
;; Check pp and collapse for the form in the header.
|
||||
(goto-char (point-min))
|
||||
(backtrace-tests--verify-single-and-multi-line header)
|
||||
;; Check pp and collapse for the last frame.
|
||||
(goto-char (point-max))
|
||||
(backtrace-backward-frame)
|
||||
(backtrace-tests--verify-single-and-multi-line last-line)
|
||||
;; Check pp and collapse for local variables in the last line.
|
||||
(goto-char (point-max))
|
||||
(backtrace-backward-frame)
|
||||
(backtrace-toggle-locals)
|
||||
(forward-line)
|
||||
(backtrace-tests--verify-single-and-multi-line last-line-locals))))
|
||||
|
||||
(defun backtrace-tests--verify-single-and-multi-line (line)
|
||||
"Verify that `backtrace-single-line' and `backtrace-multi-line' work at point.
|
||||
Point should be at the beginning of a line, and LINE should be a
|
||||
string containing the text of the line at point. Assume that the
|
||||
line contains the strings \"lambda\" and \"number\"."
|
||||
(let ((pos (point)))
|
||||
(backtrace-multi-line)
|
||||
;; Verify point is still at the start of the line.
|
||||
(should (= pos (point))))
|
||||
|
||||
;; Verify the form now spans multiple lines.
|
||||
(let ((pos (point)))
|
||||
(search-forward "number")
|
||||
(should-not (= pos (point-at-bol))))
|
||||
;; Collapse the form.
|
||||
(backtrace-single-line)
|
||||
;; Verify that the form is now back on one line,
|
||||
;; and that point is at the same place.
|
||||
(should (string= (backtrace-tests--get-substring
|
||||
(- (point) 6) (point)) "number"))
|
||||
(should-not (= (point) (point-at-bol)))
|
||||
(should (string= (backtrace-tests--get-substring
|
||||
(point-at-bol) (1+ (point-at-eol)))
|
||||
line)))
|
||||
|
||||
(ert-deftest backtrace-tests--print-circle ()
|
||||
"Backtrace buffers can toggle `print-circle' syntax."
|
||||
(ert-with-test-buffer (:name "print-circle")
|
||||
(let* ((print-circle nil)
|
||||
(arg (let ((val (make-list 5 'a))) (nconc val val) val))
|
||||
(results (backtrace-tests--make-regexp
|
||||
(backtrace-tests--result arg)))
|
||||
(results-circle (regexp-quote (let ((print-circle t))
|
||||
(backtrace-tests--result arg))))
|
||||
(last-frame (backtrace-tests--make-regexp
|
||||
(format (nth (1- backtrace-tests--line-count)
|
||||
(backtrace-tests--backtrace-lines))
|
||||
arg)))
|
||||
(last-frame-circle (regexp-quote
|
||||
(let ((print-circle t))
|
||||
(format (nth (1- backtrace-tests--line-count)
|
||||
(backtrace-tests--backtrace-lines))
|
||||
arg)))))
|
||||
(backtrace-tests--make-backtrace arg)
|
||||
(backtrace-print)
|
||||
(should (string-match-p results
|
||||
(backtrace-tests--get-substring (point-min) (point-max))))
|
||||
;; Go to the last frame.
|
||||
(goto-char (point-max))
|
||||
(forward-line -1)
|
||||
;; Turn on print-circle for that frame.
|
||||
(backtrace-toggle-print-circle)
|
||||
(should (string-match-p last-frame-circle
|
||||
(backtrace-tests--get-substring (point) (point-max))))
|
||||
;; Turn off print-circle for the frame.
|
||||
(backtrace-toggle-print-circle)
|
||||
(should (string-match-p last-frame
|
||||
(backtrace-tests--get-substring (point) (point-max))))
|
||||
(should (string-match-p results
|
||||
(backtrace-tests--get-substring (point-min) (point-max))))
|
||||
;; Turn print-circle on for the buffer.
|
||||
(backtrace-toggle-print-circle '(4))
|
||||
(should (string-match-p last-frame-circle
|
||||
(backtrace-tests--get-substring (point) (point-max))))
|
||||
(should (string-match-p results-circle
|
||||
(backtrace-tests--get-substring (point-min) (point-max))))
|
||||
;; Turn print-circle off.
|
||||
(backtrace-toggle-print-circle '(4))
|
||||
(should (string-match-p last-frame
|
||||
(backtrace-tests--get-substring
|
||||
(point) (+ (point) (length last-frame)))))
|
||||
(should (string-match-p results
|
||||
(backtrace-tests--get-substring (point-min) (point-max)))))))
|
||||
|
||||
(defun backtrace-tests--make-regexp (str)
|
||||
"Make regexp from STR for `backtrace-tests--print-circle'.
|
||||
Used for results of printing circular objects without
|
||||
`print-circle' on. Look for #n in string STR where n is any
|
||||
digit and replace with #[0-9]."
|
||||
(let ((regexp (regexp-quote str)))
|
||||
(with-temp-buffer
|
||||
(insert regexp)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "#[0-9]" nil t)
|
||||
(replace-match "#[0-9]")))
|
||||
(buffer-string)))
|
||||
|
||||
(ert-deftest backtrace-tests--expand-ellipsis ()
|
||||
"Backtrace buffers ellipsify large forms as buttons which expand the ellipses."
|
||||
;; make a backtrace with an ellipsis
|
||||
;; expand the ellipsis
|
||||
(ert-with-test-buffer (:name "variables")
|
||||
(let* ((print-level nil)
|
||||
(print-length nil)
|
||||
(backtrace-line-length 300)
|
||||
(arg (make-list 40 (make-string 10 ?a)))
|
||||
(results (backtrace-tests--result arg)))
|
||||
(backtrace-tests--make-backtrace arg)
|
||||
(backtrace-print)
|
||||
|
||||
;; There should be an ellipsis. Find and expand it.
|
||||
(goto-char (point-min))
|
||||
(search-forward "...")
|
||||
(backward-char)
|
||||
(push-button)
|
||||
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results)))))
|
||||
|
||||
(ert-deftest backtrace-tests--expand-ellipses ()
|
||||
"Backtrace buffers ellipsify large forms and can expand the ellipses."
|
||||
(ert-with-test-buffer (:name "variables")
|
||||
(let* ((print-level nil)
|
||||
(print-length nil)
|
||||
(backtrace-line-length 300)
|
||||
(arg (let ((outer (make-list 40 (make-string 10 ?a)))
|
||||
(nested (make-list 40 (make-string 10 ?b))))
|
||||
(setf (nth 39 nested) (make-list 40 (make-string 10 ?c)))
|
||||
(setf (nth 39 outer) nested)
|
||||
outer))
|
||||
(results (backtrace-tests--result-with-locals arg)))
|
||||
|
||||
;; Make a backtrace with local variables visible.
|
||||
(backtrace-tests--make-backtrace arg)
|
||||
(backtrace-print)
|
||||
(backtrace-toggle-locals '(4))
|
||||
|
||||
;; There should be two ellipses.
|
||||
(goto-char (point-min))
|
||||
(should (search-forward "..."))
|
||||
(should (search-forward "..."))
|
||||
(should-error (search-forward "..."))
|
||||
|
||||
;; Expanding the last frame without argument should expand both
|
||||
;; ellipses, but the expansions will contain one ellipsis each.
|
||||
(let ((buffer-len (- (point-max) (point-min))))
|
||||
(goto-char (point-max))
|
||||
(backtrace-backward-frame)
|
||||
(backtrace-expand-ellipses)
|
||||
(should (> (- (point-max) (point-min)) buffer-len))
|
||||
(goto-char (point-min))
|
||||
(should (search-forward "..."))
|
||||
(should (search-forward "..."))
|
||||
(should-error (search-forward "...")))
|
||||
|
||||
;; Expanding with argument should remove all ellipses.
|
||||
(goto-char (point-max))
|
||||
(backtrace-backward-frame)
|
||||
(backtrace-expand-ellipses '(4))
|
||||
(goto-char (point-min))
|
||||
|
||||
(should-error (search-forward "..."))
|
||||
(should (string= (backtrace-tests--get-substring (point-min) (point-max))
|
||||
results)))))
|
||||
|
||||
|
||||
(ert-deftest backtrace-tests--to-string ()
|
||||
"Backtraces can be produced as strings."
|
||||
(let ((frames (ert-with-test-buffer (:name nil)
|
||||
(backtrace-tests--make-backtrace "string")
|
||||
backtrace-frames)))
|
||||
(should (string= (backtrace-to-string frames)
|
||||
(backtrace-tests--result "string")))))
|
||||
|
||||
(defun backtrace-tests--get-substring (beg end)
|
||||
"Return the visible text between BEG and END.
|
||||
Strip the string properties because it makes failed test results
|
||||
easier to read."
|
||||
(substring-no-properties (filter-buffer-substring beg end)))
|
||||
|
||||
(provide 'backtrace-tests)
|
||||
|
||||
;;; backtrace-tests.el ends here
|
@ -56,19 +56,30 @@
|
||||
(let ((long-list (make-list 5 'a))
|
||||
(long-vec (make-vector 5 'b))
|
||||
(long-struct (cl-print-tests-con))
|
||||
(long-string (make-string 5 ?a))
|
||||
(print-length 4))
|
||||
(should (equal "(a a a a ...)" (cl-prin1-to-string long-list)))
|
||||
(should (equal "[b b b b ...]" (cl-prin1-to-string long-vec)))
|
||||
(should (equal "#s(cl-print-tests-struct :a nil :b nil :c nil :d nil ...)"
|
||||
(cl-prin1-to-string long-struct)))))
|
||||
(cl-prin1-to-string long-struct)))
|
||||
(should (equal "\"aaaa...\"" (cl-prin1-to-string long-string)))))
|
||||
|
||||
(ert-deftest cl-print-tests-4 ()
|
||||
"CL printing observes `print-level'."
|
||||
(let ((deep-list '(a (b (c (d (e))))))
|
||||
(deep-struct (cl-print-tests-con))
|
||||
(print-level 4))
|
||||
(let* ((deep-list '(a (b (c (d (e))))))
|
||||
(buried-vector '(a (b (c (d [e])))))
|
||||
(deep-struct (cl-print-tests-con))
|
||||
(buried-struct `(a (b (c (d ,deep-struct)))))
|
||||
(buried-string '(a (b (c (d #("hello" 0 5 (cl-print-test t)))))))
|
||||
(buried-simple-string '(a (b (c (d "hello")))))
|
||||
(print-level 4))
|
||||
(setf (cl-print-tests-struct-a deep-struct) deep-list)
|
||||
(should (equal "(a (b (c (d ...))))" (cl-prin1-to-string deep-list)))
|
||||
(should (equal "(a (b (c (d ...))))" (cl-prin1-to-string buried-vector)))
|
||||
(should (equal "(a (b (c (d ...))))" (cl-prin1-to-string buried-struct)))
|
||||
(should (equal "(a (b (c (d ...))))" (cl-prin1-to-string buried-string)))
|
||||
(should (equal "(a (b (c (d \"hello\"))))"
|
||||
(cl-prin1-to-string buried-simple-string)))
|
||||
(should (equal "#s(cl-print-tests-struct :a (a (b (c ...))) :b nil :c nil :d nil :e nil)"
|
||||
(cl-prin1-to-string deep-struct)))))
|
||||
|
||||
@ -82,6 +93,129 @@
|
||||
(should (equal "((quote a) (function b) (\\` ((\\, c) (\\,@ d))))"
|
||||
(cl-prin1-to-string quoted-stuff))))))
|
||||
|
||||
(ert-deftest cl-print-tests-strings ()
|
||||
"CL printing prints strings and propertized strings."
|
||||
(let* ((str1 "abcdefghij")
|
||||
(str2 #("abcdefghij" 3 6 (bold t) 7 9 (italic t)))
|
||||
(str3 #("abcdefghij" 0 10 (test t)))
|
||||
(obj '(a b))
|
||||
;; Since the byte compiler reuses string literals,
|
||||
;; and the put-text-property call is destructive, use
|
||||
;; copy-sequence to make a new string.
|
||||
(str4 (copy-sequence "abcdefghij")))
|
||||
(put-text-property 0 5 'test obj str4)
|
||||
(put-text-property 7 10 'test obj str4)
|
||||
|
||||
(should (equal "\"abcdefghij\"" (cl-prin1-to-string str1)))
|
||||
(should (equal "#(\"abcdefghij\" 3 6 (bold t) 7 9 (italic t))"
|
||||
(cl-prin1-to-string str2)))
|
||||
(should (equal "#(\"abcdefghij\" 0 10 (test t))"
|
||||
(cl-prin1-to-string str3)))
|
||||
(let ((print-circle nil))
|
||||
(should
|
||||
(equal
|
||||
"#(\"abcdefghij\" 0 5 (test (a b)) 7 10 (test (a b)))"
|
||||
(cl-prin1-to-string str4))))
|
||||
(let ((print-circle t))
|
||||
(should
|
||||
(equal
|
||||
"#(\"abcdefghij\" 0 5 (test #1=(a b)) 7 10 (test #1#))"
|
||||
(cl-prin1-to-string str4))))))
|
||||
|
||||
(ert-deftest cl-print-tests-ellipsis-cons ()
|
||||
"Ellipsis expansion works in conses."
|
||||
(let ((print-length 4)
|
||||
(print-level 3))
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
'(0 1 2 3 4 5) "(0 1 2 3 ...)" "4 5")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
'(0 1 2 3 4 5 6 7 8 9) "(0 1 2 3 ...)" "4 5 6 7 ...")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
'(a (b (c (d (e))))) "(a (b (c ...)))" "(d (e))")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
(let ((x (make-list 6 'b)))
|
||||
(setf (nthcdr 6 x) 'c)
|
||||
x)
|
||||
"(b b b b ...)" "b b . c")))
|
||||
|
||||
(ert-deftest cl-print-tests-ellipsis-vector ()
|
||||
"Ellipsis expansion works in vectors."
|
||||
(let ((print-length 4)
|
||||
(print-level 3))
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
[0 1 2 3 4 5] "[0 1 2 3 ...]" "4 5")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
[0 1 2 3 4 5 6 7 8 9] "[0 1 2 3 ...]" "4 5 6 7 ...")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
[a [b [c [d [e]]]]] "[a [b [c ...]]]" "[d [e]]")))
|
||||
|
||||
(ert-deftest cl-print-tests-ellipsis-string ()
|
||||
"Ellipsis expansion works in strings."
|
||||
(let ((print-length 4)
|
||||
(print-level 3))
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
"abcdefg" "\"abcd...\"" "efg")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
"abcdefghijk" "\"abcd...\"" "efgh...")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
'(1 (2 (3 #("abcde" 0 5 (test t)))))
|
||||
"(1 (2 (3 ...)))" "#(\"abcd...\" 0 5 (test t))")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
#("abcd" 0 1 (bold t) 1 2 (invisible t) 3 4 (italic t))
|
||||
"#(\"abcd\" 0 1 (bold t) ...)" "1 2 (invisible t) ...")))
|
||||
|
||||
(ert-deftest cl-print-tests-ellipsis-struct ()
|
||||
"Ellipsis expansion works in structures."
|
||||
(let ((print-length 4)
|
||||
(print-level 3)
|
||||
(struct (cl-print-tests-con)))
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
struct "#s(cl-print-tests-struct :a nil :b nil :c nil :d nil ...)" ":e nil")
|
||||
(let ((print-length 2))
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
struct "#s(cl-print-tests-struct :a nil :b nil ...)" ":c nil :d nil ..."))
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
`(a (b (c ,struct)))
|
||||
"(a (b (c ...)))"
|
||||
"#s(cl-print-tests-struct :a nil :b nil :c nil :d nil ...)")))
|
||||
|
||||
(ert-deftest cl-print-tests-ellipsis-circular ()
|
||||
"Ellipsis expansion works with circular objects."
|
||||
(let ((wide-obj (list 0 1 2 3 4))
|
||||
(deep-obj `(0 (1 (2 (3 (4))))))
|
||||
(print-length 4)
|
||||
(print-level 3))
|
||||
(setf (nth 4 wide-obj) wide-obj)
|
||||
(setf (car (cadadr (cadadr deep-obj))) deep-obj)
|
||||
(let ((print-circle nil))
|
||||
(cl-print-tests-check-ellipsis-expansion-rx
|
||||
wide-obj (regexp-quote "(0 1 2 3 ...)") "\\`#[0-9]\\'")
|
||||
(cl-print-tests-check-ellipsis-expansion-rx
|
||||
deep-obj (regexp-quote "(0 (1 (2 ...)))") "\\`(3 (#[0-9]))\\'"))
|
||||
(let ((print-circle t))
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
wide-obj "#1=(0 1 2 3 ...)" "#1#")
|
||||
(cl-print-tests-check-ellipsis-expansion
|
||||
deep-obj "#1=(0 (1 (2 ...)))" "(3 (#1#))"))))
|
||||
|
||||
(defun cl-print-tests-check-ellipsis-expansion (obj expected expanded)
|
||||
(let* ((result (cl-prin1-to-string obj))
|
||||
(pos (next-single-property-change 0 'cl-print-ellipsis result))
|
||||
value)
|
||||
(should pos)
|
||||
(setq value (get-text-property pos 'cl-print-ellipsis result))
|
||||
(should (equal expected result))
|
||||
(should (equal expanded (with-output-to-string (cl-print-expand-ellipsis
|
||||
value nil))))))
|
||||
|
||||
(defun cl-print-tests-check-ellipsis-expansion-rx (obj expected expanded)
|
||||
(let* ((result (cl-prin1-to-string obj))
|
||||
(pos (next-single-property-change 0 'cl-print-ellipsis result))
|
||||
(value (get-text-property pos 'cl-print-ellipsis result)))
|
||||
(should (string-match expected result))
|
||||
(should (string-match expanded (with-output-to-string
|
||||
(cl-print-expand-ellipsis value nil))))))
|
||||
|
||||
(ert-deftest cl-print-circle ()
|
||||
(let ((x '(#1=(a . #1#) #1#)))
|
||||
(let ((print-circle nil))
|
||||
@ -99,5 +233,41 @@
|
||||
(let ((print-circle t))
|
||||
(should (equal "(0 . #1=(0 . #1#))" (cl-prin1-to-string x))))))
|
||||
|
||||
(ert-deftest cl-print-tests-print-to-string-with-limit ()
|
||||
(let* ((thing10 (make-list 10 'a))
|
||||
(thing100 (make-list 100 'a))
|
||||
(thing10x10 (make-list 10 thing10))
|
||||
(nested-thing (let ((val 'a))
|
||||
(dotimes (_i 20)
|
||||
(setq val (list val)))
|
||||
val))
|
||||
;; Make a consistent environment for this test.
|
||||
(print-circle nil)
|
||||
(print-level nil)
|
||||
(print-length nil))
|
||||
|
||||
;; Print something that fits in the space given.
|
||||
(should (string= (cl-prin1-to-string thing10)
|
||||
(cl-print-to-string-with-limit #'cl-prin1 thing10 100)))
|
||||
|
||||
;; Print something which needs to be abbreviated and which can be.
|
||||
(should (< (length (cl-print-to-string-with-limit #'cl-prin1 thing100 100))
|
||||
100
|
||||
(length (cl-prin1-to-string thing100))))
|
||||
|
||||
;; Print something resistant to easy abbreviation.
|
||||
(should (string= (cl-prin1-to-string thing10x10)
|
||||
(cl-print-to-string-with-limit #'cl-prin1 thing10x10 100)))
|
||||
|
||||
;; Print something which should be abbreviated even if the limit is large.
|
||||
(should (< (length (cl-print-to-string-with-limit #'cl-prin1 nested-thing 1000))
|
||||
(length (cl-prin1-to-string nested-thing))))
|
||||
|
||||
;; Print with no limits.
|
||||
(dolist (thing (list thing10 thing100 thing10x10 nested-thing))
|
||||
(let ((rep (cl-prin1-to-string thing)))
|
||||
(should (string= rep (cl-print-to-string-with-limit #'cl-prin1 thing 0)))
|
||||
(should (string= rep (cl-print-to-string-with-limit #'cl-prin1 thing nil)))))))
|
||||
|
||||
|
||||
;;; cl-print-tests.el ends here.
|
||||
|
@ -41,7 +41,7 @@
|
||||
(defun edebug-test-code-range (num)
|
||||
!start!(let ((index 0)
|
||||
(result nil))
|
||||
(while (< index num)!test!
|
||||
(while !lt!(< index num)!test!
|
||||
(push index result)!loop!
|
||||
(cl-incf index))!end-loop!
|
||||
(nreverse result)))
|
||||
|
@ -432,9 +432,11 @@ test and possibly others should be updated."
|
||||
(verify-keybinding "P" 'edebug-view-outside) ;; same as v
|
||||
(verify-keybinding "W" 'edebug-toggle-save-windows)
|
||||
(verify-keybinding "?" 'edebug-help)
|
||||
(verify-keybinding "d" 'edebug-backtrace)
|
||||
(verify-keybinding "d" 'edebug-pop-to-backtrace)
|
||||
(verify-keybinding "-" 'negative-argument)
|
||||
(verify-keybinding "=" 'edebug-temp-display-freq-count)))
|
||||
(verify-keybinding "=" 'edebug-temp-display-freq-count)
|
||||
(should (eq (lookup-key backtrace-mode-map "n") 'backtrace-forward-frame))
|
||||
(should (eq (lookup-key backtrace-mode-map "s") 'backtrace-goto-source))))
|
||||
|
||||
(ert-deftest edebug-tests-stop-point-at-start-of-first-instrumented-function ()
|
||||
"Edebug stops at the beginning of an instrumented function."
|
||||
@ -924,5 +926,17 @@ test and possibly others should be updated."
|
||||
"g"
|
||||
(should (equal edebug-tests-@-result "The result of applying + to (1 x) is 11")))))
|
||||
|
||||
(ert-deftest edebug-tests-backtrace-goto-source ()
|
||||
"Edebug can jump to instrumented source from its *Edebug-Backtrace* buffer."
|
||||
(edebug-tests-with-normal-env
|
||||
(edebug-tests-setup-@ "range" '(2) t)
|
||||
(edebug-tests-run-kbd-macro
|
||||
"@ SPC SPC"
|
||||
(edebug-tests-should-be-at "range" "lt")
|
||||
"dns" ; Pop to backtrace, next frame, goto source.
|
||||
(edebug-tests-should-be-at "range" "start")
|
||||
"g"
|
||||
(should (equal edebug-tests-@-result '(0 1))))))
|
||||
|
||||
(provide 'edebug-tests)
|
||||
;;; edebug-tests.el ends here
|
||||
|
@ -376,7 +376,7 @@ This macro is used to test if macroexpansion in `should' works."
|
||||
(test (make-ert-test :body test-body))
|
||||
(result (ert-run-test test)))
|
||||
(should (ert-test-failed-p result))
|
||||
(should (eq (nth 1 (car (ert-test-failed-backtrace result)))
|
||||
(should (eq (backtrace-frame-fun (car (ert-test-failed-backtrace result)))
|
||||
'signal))))
|
||||
|
||||
(ert-deftest ert-test-messages ()
|
||||
|
Loading…
Reference in New Issue
Block a user