]> git.eshelyaron.com Git - emacs.git/commitdiff
Add new command to expand all "..."s in a backtrace frame
authorGemini Lasswell <gazally@runbox.com>
Sat, 14 Jul 2018 15:05:51 +0000 (08:05 -0700)
committerGemini Lasswell <gazally@runbox.com>
Fri, 3 Aug 2018 15:53:02 +0000 (08:53 -0700)
* doc/lispref/debugging.texi (Backtraces): Document new keybinding.
* lisp/emacs-lisp/backtrace.el (backtrace-line-length): Add the
option of unlimited line length.
(backtrace--match-ellipsis-in-string): Add a comment to explain
why this function is necessary.
(backtrace-mode-map): Add keybinding for 'backtrace-expand-ellipses'.
(backtrace-expand-ellipsis): Use 'cl-print-to-string-with-limit'.
(backtrace-expand-ellipses): New command.
(backtrace-print-to-string): Use 'cl-print-to-string-with-limit'.
Tag the printed forms with a gensym instead of the values of
print-length and print-level.
(backtrace--print): Add 'stream' argument.
* test/lisp/emacs-lisp/backtrace-tests.el
(backtrace-tests--expand-ellipsis): Make the test less dependent
on the implementation.
(backtrace-tests--expand-ellipses): New test.

Move the fitting of a printed representation into a limited number of
characters using appropriate values of print-level and print-length
from 'backtrace-print-to-string' to cl-print.el for future use by
other parts of Emacs.
* lisp/emacs-lisp/cl-print.el (cl-print-to-string-with-limit): New
function.
* test/lisp/emacs-lisp/cl-print-tests.el
(cl-print-tests-print-to-string-with-limit): New test.

doc/lispref/debugging.texi
lisp/emacs-lisp/backtrace.el
lisp/emacs-lisp/cl-print.el
test/lisp/emacs-lisp/backtrace-tests.el
test/lisp/emacs-lisp/cl-print-tests.el

index 5230854cc7aecf5a6db275b9ed6243d6032d020c..87429a67ba9e016eb191ed7167ca8a1b1a158b68 100644 (file)
@@ -457,6 +457,9 @@ 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
index 779feb43075cece0ecec349ecb40849bbaccb74e..da5a777177d0565d16f1244622922a5db1694cb1 100644 (file)
@@ -55,7 +55,8 @@ order to debug the code that does fontification."
   "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."
+guaranteed.  If set to nil or zero, Backtrace mode will not
+abbreviate the forms it prints."
   :type 'integer
   :group 'backtrace
   :version "27.1")
@@ -146,6 +147,9 @@ fontifies.")
 
 (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)
@@ -187,6 +191,7 @@ This is commonly used to recompute `backtrace-frames'.")
     (define-key map "\C-m" 'backtrace-help-follow-symbol)
     (define-key map "+" 'backtrace-pretty-print)
     (define-key map "-" 'backtrace-collapse)
+    (define-key map "." 'backtrace-expand-ellipses)
     (define-key map [follow-link] 'mouse-face)
     (define-key map [mouse-2] 'mouse-select-window)
     map)
@@ -207,9 +212,7 @@ This is commonly used to recompute `backtrace-frames'.")
 ;; 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.  The value chosen is a list contining
-;;   the values of print-level and print-length used to print the
-;;   sexp, and those values are used when expanding ellipses.
+;;   found for pretty-printing.
 
 (defsubst backtrace-get-index (&optional pos)
   "Return the index of the backtrace frame at POS.
@@ -423,9 +426,6 @@ Reprint the frame with the new view plist."
 
 (defun backtrace-expand-ellipsis (button)
   "Expand display of the elided form at BUTTON."
-  ;; TODO a command to expand all ... in form at point
-  ;; with argument, don't bind print-level, length??
-  ;; Enable undo so there's a way to go back?
   (interactive)
   (goto-char (button-start button))
   (unless (get-text-property (point) 'cl-print-ellipsis)
@@ -437,25 +437,44 @@ Reprint the frame with the new view plist."
          (begin (previous-single-property-change end 'cl-print-ellipsis))
          (value (get-text-property begin 'cl-print-ellipsis))
          (props (backtrace-get-text-properties begin))
-         (tag (backtrace-get-form begin))
-         (length (nth 0 tag))  ; TODO should this work with a target char count
-         (level (nth 1 tag))   ; like backtrace-print-to-string?
          (inhibit-read-only t))
     (backtrace--with-output-variables (backtrace-get-view)
-      (let ((print-level level)
-            (print-length length))
-        (delete-region begin end)
-        (cl-print-expand-ellipsis value (current-buffer))
-        (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)))))
+      (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-pretty-print ()
   "Pretty-print the top level s-expression at point."
@@ -605,8 +624,7 @@ line and recenter window line accordingly."
   "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.'  Attach the settings used with the text property
-`backtrace-form'.  LIMIT defaults to `backtrace-line-length'."
+`print-length.'  LIMIT defaults to `backtrace-line-length'."
   (backtrace--with-output-variables backtrace-view
     (backtrace--print-to-string obj limit)))
 
@@ -614,36 +632,20 @@ charcters with appropriate settings of `print-level' and
   ;; This is for use by callers who wrap the call with
   ;; backtrace--with-output-variables.
   (setq limit (or limit backtrace-line-length))
-  (let* ((length 50)  ; (/ backtrace-line-length 100) ??
-         (level (truncate (log limit)))
-         (delta (truncate (/ length level))))
-    (with-temp-buffer
-       (catch 'done
-         (while t
-           (erase-buffer)
-           (let ((standard-output (current-buffer))
-                 (print-length length)
-                 (print-level level))
-             (backtrace--print sexp))
-           ;; Stop when either the level is too low or the sexp is
-           ;; successfully printed in the space allowed.
-           (when (or (< (- (point-max) (point-min)) limit) (= level 2))
-             (throw 'done nil))
-           (cl-decf level)
-           (cl-decf length delta)))
-       (put-text-property (point-min) (point)
-                          'backtrace-form (list length level))
-       ;; Make buttons from all the "..."s.
-       ;; TODO should this be under control of :do-ellipses in the view
-       ;; plist?
-       (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))))
+  (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.
@@ -727,14 +729,14 @@ Print them only if :show-locals is non-nil in the VIEW plist."
           (insert "\n")))
       (put-text-property beg (point) 'backtrace-section 'locals))))
 
-(defun backtrace--print (obj)
-  "Attempt to print OBJ using `backtrace-print-function'.
+(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)
+      (funcall backtrace-print-function obj stream)
     (error
      (message "Error in backtrace printer: %S" err)
-     (prin1 obj))))
+     (prin1 obj stream))))
 
 (defun backtrace-update-flags ()
   "Update the display of the flags in the backtrace frame at point."
@@ -805,8 +807,6 @@ followed by `backtrace-print-frame', once for each stack frame."
              backtrace-font-lock-keywords-1
              backtrace-font-lock-keywords-2)
             nil nil nil nil
-            ;; TODO This one doesn't look necessary:
-            ;; (font-lock-mark-block-function . mark-defun)
            (font-lock-syntactic-face-function
             . lisp-font-lock-syntactic-face-function))))
   (setq truncate-lines t)
index 337efa465a01b95940bdcdc1cc39924b8a0c3f66..c63f5ac005cb728e7c922df1d5a203a3f75c8673 100644 (file)
@@ -524,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
index ba2d33a9d5c4a0ac52f03f19d0fa5b816c4f683f..ff26112ab9a8767580064beda3a0612783da24dc 100644 (file)
@@ -349,32 +349,74 @@ digit and replace with #[0-9]."
     (buffer-string)))
 
 (ert-deftest backtrace-tests--expand-ellipsis ()
-  "Backtrace buffers ellipsify large forms and can expand the ellipses."
+  "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)
-           (arg (let ((long (make-list 100 'a))
-                      (deep '(0 (1 (2 (3 (4 (5 (6 (7 (8 (9))))))))))))
-                  (setf (nth 1 long) deep)
-                  long))
+           (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 two ellipses. Find and expand them.
+      ;; There should be an ellipsis. Find and expand it.
       (goto-char (point-min))
       (search-forward "...")
       (backward-char)
       (push-button)
-      (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)
index 7594d2466b5607bc1295243747d7d7fb20e3a9d6..a469b5526c097d1bab83cf1fcb9d46f7b445cb36 100644 (file)
     (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.