aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGemini Lasswell2018-07-14 08:05:51 -0700
committerGemini Lasswell2018-08-03 08:53:02 -0700
commita3ba34aeac1b41ca5d12bfe5644d3fdfa894ddda (patch)
tree3a04c22cc9f55cc2c8b629f9b4df9f316c8d2117
parent2ede75c49b62439e15be3ab8be2c14594f846da6 (diff)
downloademacs-a3ba34aeac1b41ca5d12bfe5644d3fdfa894ddda.tar.gz
emacs-a3ba34aeac1b41ca5d12bfe5644d3fdfa894ddda.zip
Add new command to expand all "..."s in a backtrace frame
* 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.
-rw-r--r--doc/lispref/debugging.texi3
-rw-r--r--lisp/emacs-lisp/backtrace.el124
-rw-r--r--lisp/emacs-lisp/cl-print.el40
-rw-r--r--test/lisp/emacs-lisp/backtrace-tests.el60
-rw-r--r--test/lisp/emacs-lisp/cl-print-tests.el36
5 files changed, 192 insertions, 71 deletions
diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi
index 5230854cc7a..87429a67ba9 100644
--- a/doc/lispref/debugging.texi
+++ b/doc/lispref/debugging.texi
@@ -457,6 +457,9 @@ Collapse the top-level Lisp form at point back to a single line.
457@item # 457@item #
458Toggle @code{print-circle} for the frame at point. 458Toggle @code{print-circle} for the frame at point.
459 459
460@item .
461Expand all the forms abbreviated with ``...'' in the frame at point.
462
460@end table 463@end table
461 464
462@node Debugger Commands 465@node Debugger Commands
diff --git a/lisp/emacs-lisp/backtrace.el b/lisp/emacs-lisp/backtrace.el
index 779feb43075..da5a777177d 100644
--- a/lisp/emacs-lisp/backtrace.el
+++ b/lisp/emacs-lisp/backtrace.el
@@ -55,7 +55,8 @@ order to debug the code that does fontification."
55 "Target length for lines in Backtrace buffers. 55 "Target length for lines in Backtrace buffers.
56Backtrace mode will attempt to abbreviate printing of backtrace 56Backtrace mode will attempt to abbreviate printing of backtrace
57frames to make them shorter than this, but success is not 57frames to make them shorter than this, but success is not
58guaranteed." 58guaranteed. If set to nil or zero, Backtrace mode will not
59abbreviate the forms it prints."
59 :type 'integer 60 :type 'integer
60 :group 'backtrace 61 :group 'backtrace
61 :version "27.1") 62 :version "27.1")
@@ -146,6 +147,9 @@ fontifies.")
146 147
147(defun backtrace--match-ellipsis-in-string (bound) 148(defun backtrace--match-ellipsis-in-string (bound)
148 ;; Fontify ellipses within strings as buttons. 149 ;; Fontify ellipses within strings as buttons.
150 ;; This is necessary because ellipses are text property buttons
151 ;; instead of overlay buttons, which is done because there could
152 ;; be a large number of them.
149 (when (re-search-forward "\\(\\.\\.\\.\\)\"" bound t) 153 (when (re-search-forward "\\(\\.\\.\\.\\)\"" bound t)
150 (and (get-text-property (- (point) 2) 'cl-print-ellipsis) 154 (and (get-text-property (- (point) 2) 'cl-print-ellipsis)
151 (get-text-property (- (point) 3) 'cl-print-ellipsis) 155 (get-text-property (- (point) 3) 'cl-print-ellipsis)
@@ -187,6 +191,7 @@ This is commonly used to recompute `backtrace-frames'.")
187 (define-key map "\C-m" 'backtrace-help-follow-symbol) 191 (define-key map "\C-m" 'backtrace-help-follow-symbol)
188 (define-key map "+" 'backtrace-pretty-print) 192 (define-key map "+" 'backtrace-pretty-print)
189 (define-key map "-" 'backtrace-collapse) 193 (define-key map "-" 'backtrace-collapse)
194 (define-key map "." 'backtrace-expand-ellipses)
190 (define-key map [follow-link] 'mouse-face) 195 (define-key map [follow-link] 'mouse-face)
191 (define-key map [mouse-2] 'mouse-select-window) 196 (define-key map [mouse-2] 'mouse-select-window)
192 map) 197 map)
@@ -207,9 +212,7 @@ This is commonly used to recompute `backtrace-frames'.")
207;; backtrace-form: A value applied to each printed representation of a 212;; backtrace-form: A value applied to each printed representation of a
208;; top-level s-expression, which needs to be different for sexps 213;; top-level s-expression, which needs to be different for sexps
209;; printed adjacent to each other, so the limits can be quickly 214;; printed adjacent to each other, so the limits can be quickly
210;; found for pretty-printing. The value chosen is a list contining 215;; found for pretty-printing.
211;; the values of print-level and print-length used to print the
212;; sexp, and those values are used when expanding ellipses.
213 216
214(defsubst backtrace-get-index (&optional pos) 217(defsubst backtrace-get-index (&optional pos)
215 "Return the index of the backtrace frame at POS. 218 "Return the index of the backtrace frame at POS.
@@ -423,9 +426,6 @@ Reprint the frame with the new view plist."
423 426
424(defun backtrace-expand-ellipsis (button) 427(defun backtrace-expand-ellipsis (button)
425 "Expand display of the elided form at BUTTON." 428 "Expand display of the elided form at BUTTON."
426 ;; TODO a command to expand all ... in form at point
427 ;; with argument, don't bind print-level, length??
428 ;; Enable undo so there's a way to go back?
429 (interactive) 429 (interactive)
430 (goto-char (button-start button)) 430 (goto-char (button-start button))
431 (unless (get-text-property (point) 'cl-print-ellipsis) 431 (unless (get-text-property (point) 'cl-print-ellipsis)
@@ -437,25 +437,44 @@ Reprint the frame with the new view plist."
437 (begin (previous-single-property-change end 'cl-print-ellipsis)) 437 (begin (previous-single-property-change end 'cl-print-ellipsis))
438 (value (get-text-property begin 'cl-print-ellipsis)) 438 (value (get-text-property begin 'cl-print-ellipsis))
439 (props (backtrace-get-text-properties begin)) 439 (props (backtrace-get-text-properties begin))
440 (tag (backtrace-get-form begin))
441 (length (nth 0 tag)) ; TODO should this work with a target char count
442 (level (nth 1 tag)) ; like backtrace-print-to-string?
443 (inhibit-read-only t)) 440 (inhibit-read-only t))
444 (backtrace--with-output-variables (backtrace-get-view) 441 (backtrace--with-output-variables (backtrace-get-view)
445 (let ((print-level level) 442 (delete-region begin end)
446 (print-length length)) 443 (insert (cl-print-to-string-with-limit #'cl-print-expand-ellipsis value
447 (delete-region begin end) 444 backtrace-line-length))
448 (cl-print-expand-ellipsis value (current-buffer)) 445 (setq end (point))
449 (setq end (point)) 446 (goto-char begin)
450 (goto-char begin) 447 (while (< (point) end)
451 (while (< (point) end) 448 (let ((next (next-single-property-change (point) 'cl-print-ellipsis
452 (let ((next (next-single-property-change (point) 'cl-print-ellipsis 449 nil end)))
453 nil end))) 450 (when (get-text-property (point) 'cl-print-ellipsis)
454 (when (get-text-property (point) 'cl-print-ellipsis) 451 (make-text-button (point) next :type 'backtrace-ellipsis))
455 (make-text-button (point) next :type 'backtrace-ellipsis)) 452 (goto-char next)))
456 (goto-char next))) 453 (goto-char begin)
457 (goto-char begin) 454 (add-text-properties begin end props))))
458 (add-text-properties begin end props))))) 455
456(defun backtrace-expand-ellipses (&optional no-limit)
457 "Expand display of all \"...\"s in the backtrace frame at point.
458\\<backtrace-mode-map>
459Each ellipsis will be limited to `backtrace-line-length'
460characters in its expansion. With optional prefix argument
461NO-LIMIT, do not limit the number of characters. Note that with
462or without the argument, using this command can result in very
463long lines and very poor display performance. If this happens
464and is a problem, use `\\[revert-buffer]' to return to the
465initial state of the Backtrace buffer."
466 (interactive "P")
467 (save-excursion
468 (let ((start (backtrace-get-frame-start))
469 (end (backtrace-get-frame-end))
470 (backtrace-line-length (unless no-limit backtrace-line-length)))
471 (goto-char end)
472 (while (> (point) start)
473 (let ((next (previous-single-property-change (point) 'cl-print-ellipsis
474 nil start)))
475 (when (get-text-property (point) 'cl-print-ellipsis)
476 (push-button (point)))
477 (goto-char next))))))
459 478
460(defun backtrace-pretty-print () 479(defun backtrace-pretty-print ()
461 "Pretty-print the top level s-expression at point." 480 "Pretty-print the top level s-expression at point."
@@ -605,8 +624,7 @@ line and recenter window line accordingly."
605 "Return a printed representation of OBJ formatted for backtraces. 624 "Return a printed representation of OBJ formatted for backtraces.
606Attempt to get the length of the returned string under LIMIT 625Attempt to get the length of the returned string under LIMIT
607charcters with appropriate settings of `print-level' and 626charcters with appropriate settings of `print-level' and
608`print-length.' Attach the settings used with the text property 627`print-length.' LIMIT defaults to `backtrace-line-length'."
609`backtrace-form'. LIMIT defaults to `backtrace-line-length'."
610 (backtrace--with-output-variables backtrace-view 628 (backtrace--with-output-variables backtrace-view
611 (backtrace--print-to-string obj limit))) 629 (backtrace--print-to-string obj limit)))
612 630
@@ -614,36 +632,20 @@ charcters with appropriate settings of `print-level' and
614 ;; This is for use by callers who wrap the call with 632 ;; This is for use by callers who wrap the call with
615 ;; backtrace--with-output-variables. 633 ;; backtrace--with-output-variables.
616 (setq limit (or limit backtrace-line-length)) 634 (setq limit (or limit backtrace-line-length))
617 (let* ((length 50) ; (/ backtrace-line-length 100) ?? 635 (with-temp-buffer
618 (level (truncate (log limit))) 636 (insert (cl-print-to-string-with-limit #'backtrace--print sexp limit))
619 (delta (truncate (/ length level)))) 637 ;; Add a unique backtrace-form property.
620 (with-temp-buffer 638 (put-text-property (point-min) (point) 'backtrace-form (gensym))
621 (catch 'done 639 ;; Make buttons from all the "..."s. Since there might be many of
622 (while t 640 ;; them, use text property buttons.
623 (erase-buffer) 641 (goto-char (point-min))
624 (let ((standard-output (current-buffer)) 642 (while (< (point) (point-max))
625 (print-length length) 643 (let ((end (next-single-property-change (point) 'cl-print-ellipsis
626 (print-level level)) 644 nil (point-max))))
627 (backtrace--print sexp)) 645 (when (get-text-property (point) 'cl-print-ellipsis)
628 ;; Stop when either the level is too low or the sexp is 646 (make-text-button (point) end :type 'backtrace-ellipsis))
629 ;; successfully printed in the space allowed. 647 (goto-char end)))
630 (when (or (< (- (point-max) (point-min)) limit) (= level 2)) 648 (buffer-string)))
631 (throw 'done nil))
632 (cl-decf level)
633 (cl-decf length delta)))
634 (put-text-property (point-min) (point)
635 'backtrace-form (list length level))
636 ;; Make buttons from all the "..."s.
637 ;; TODO should this be under control of :do-ellipses in the view
638 ;; plist?
639 (goto-char (point-min))
640 (while (< (point) (point-max))
641 (let ((end (next-single-property-change (point) 'cl-print-ellipsis
642 nil (point-max))))
643 (when (get-text-property (point) 'cl-print-ellipsis)
644 (make-text-button (point) end :type 'backtrace-ellipsis))
645 (goto-char end)))
646 (buffer-string))))
647 649
648(defun backtrace-print-frame (frame view) 650(defun backtrace-print-frame (frame view)
649 "Insert a backtrace FRAME at point formatted according to VIEW. 651 "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."
727 (insert "\n"))) 729 (insert "\n")))
728 (put-text-property beg (point) 'backtrace-section 'locals)))) 730 (put-text-property beg (point) 'backtrace-section 'locals))))
729 731
730(defun backtrace--print (obj) 732(defun backtrace--print (obj &optional stream)
731 "Attempt to print OBJ using `backtrace-print-function'. 733 "Attempt to print OBJ to STREAM using `backtrace-print-function'.
732Fall back to `prin1' if there is an error." 734Fall back to `prin1' if there is an error."
733 (condition-case err 735 (condition-case err
734 (funcall backtrace-print-function obj) 736 (funcall backtrace-print-function obj stream)
735 (error 737 (error
736 (message "Error in backtrace printer: %S" err) 738 (message "Error in backtrace printer: %S" err)
737 (prin1 obj)))) 739 (prin1 obj stream))))
738 740
739(defun backtrace-update-flags () 741(defun backtrace-update-flags ()
740 "Update the display of the flags in the backtrace frame at point." 742 "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."
805 backtrace-font-lock-keywords-1 807 backtrace-font-lock-keywords-1
806 backtrace-font-lock-keywords-2) 808 backtrace-font-lock-keywords-2)
807 nil nil nil nil 809 nil nil nil nil
808 ;; TODO This one doesn't look necessary:
809 ;; (font-lock-mark-block-function . mark-defun)
810 (font-lock-syntactic-face-function 810 (font-lock-syntactic-face-function
811 . lisp-font-lock-syntactic-face-function)))) 811 . lisp-font-lock-syntactic-face-function))))
812 (setq truncate-lines t) 812 (setq truncate-lines t)
diff --git a/lisp/emacs-lisp/cl-print.el b/lisp/emacs-lisp/cl-print.el
index 337efa465a0..c63f5ac005c 100644
--- a/lisp/emacs-lisp/cl-print.el
+++ b/lisp/emacs-lisp/cl-print.el
@@ -524,5 +524,45 @@ node `(elisp)Output Variables'."
524 (cl-prin1 object (current-buffer)) 524 (cl-prin1 object (current-buffer))
525 (buffer-string))) 525 (buffer-string)))
526 526
527;;;###autoload
528(defun cl-print-to-string-with-limit (print-function value limit)
529 "Return a string containing a printed representation of VALUE.
530Attempt to get the length of the returned string under LIMIT
531characters with appropriate settings of `print-level' and
532`print-length.' Use PRINT-FUNCTION to print, which should take
533the arguments VALUE and STREAM and which should respect
534`print-length' and `print-level'. LIMIT may be nil or zero in
535which case PRINT-FUNCTION will be called with `print-level' and
536`print-length' bound to nil.
537
538Use this function with `cl-prin1' to print an object,
539abbreviating it with ellipses to fit within a size limit. Use
540this function with `cl-prin1-expand-ellipsis' to expand an
541ellipsis, abbreviating the expansion to stay within a size
542limit."
543 (setq limit (and (natnump limit)
544 (not (zerop limit))
545 limit))
546 ;; Since this is used by the debugger when stack space may be
547 ;; limited, if you increase print-level here, add more depth in
548 ;; call_debugger (bug#31919).
549 (let* ((print-length (when limit (min limit 50)))
550 (print-level (when limit (min 8 (truncate (log limit)))))
551 (delta (when limit
552 (max 1 (truncate (/ print-length print-level))))))
553 (with-temp-buffer
554 (catch 'done
555 (while t
556 (erase-buffer)
557 (funcall print-function value (current-buffer))
558 ;; Stop when either print-level is too low or the value is
559 ;; successfully printed in the space allowed.
560 (when (or (not limit)
561 (< (- (point-max) (point-min)) limit)
562 (= print-level 2))
563 (throw 'done (buffer-string)))
564 (cl-decf print-level)
565 (cl-decf print-length delta))))))
566
527(provide 'cl-print) 567(provide 'cl-print)
528;;; cl-print.el ends here 568;;; cl-print.el ends here
diff --git a/test/lisp/emacs-lisp/backtrace-tests.el b/test/lisp/emacs-lisp/backtrace-tests.el
index ba2d33a9d5c..ff26112ab9a 100644
--- a/test/lisp/emacs-lisp/backtrace-tests.el
+++ b/test/lisp/emacs-lisp/backtrace-tests.el
@@ -349,32 +349,74 @@ digit and replace with #[0-9]."
349 (buffer-string))) 349 (buffer-string)))
350 350
351(ert-deftest backtrace-tests--expand-ellipsis () 351(ert-deftest backtrace-tests--expand-ellipsis ()
352 "Backtrace buffers ellipsify large forms and can expand the ellipses." 352 "Backtrace buffers ellipsify large forms as buttons which expand the ellipses."
353 ;; make a backtrace with an ellipsis 353 ;; make a backtrace with an ellipsis
354 ;; expand the ellipsis 354 ;; expand the ellipsis
355 (ert-with-test-buffer (:name "variables") 355 (ert-with-test-buffer (:name "variables")
356 (let* ((print-level nil) 356 (let* ((print-level nil)
357 (print-length nil) 357 (print-length nil)
358 (arg (let ((long (make-list 100 'a)) 358 (backtrace-line-length 300)
359 (deep '(0 (1 (2 (3 (4 (5 (6 (7 (8 (9)))))))))))) 359 (arg (make-list 40 (make-string 10 ?a)))
360 (setf (nth 1 long) deep)
361 long))
362 (results (backtrace-tests--result arg))) 360 (results (backtrace-tests--result arg)))
363 (backtrace-tests--make-backtrace arg) 361 (backtrace-tests--make-backtrace arg)
364 (backtrace-print) 362 (backtrace-print)
365 363
366 ;; There should be two ellipses. Find and expand them. 364 ;; There should be an ellipsis. Find and expand it.
367 (goto-char (point-min)) 365 (goto-char (point-min))
368 (search-forward "...") 366 (search-forward "...")
369 (backward-char) 367 (backward-char)
370 (push-button) 368 (push-button)
371 (search-forward "...")
372 (backward-char)
373 (push-button)
374 369
375 (should (string= (backtrace-tests--get-substring (point-min) (point-max)) 370 (should (string= (backtrace-tests--get-substring (point-min) (point-max))
376 results))))) 371 results)))))
377 372
373(ert-deftest backtrace-tests--expand-ellipses ()
374 "Backtrace buffers ellipsify large forms and can expand the ellipses."
375 (ert-with-test-buffer (:name "variables")
376 (let* ((print-level nil)
377 (print-length nil)
378 (backtrace-line-length 300)
379 (arg (let ((outer (make-list 40 (make-string 10 ?a)))
380 (nested (make-list 40 (make-string 10 ?b))))
381 (setf (nth 39 nested) (make-list 40 (make-string 10 ?c)))
382 (setf (nth 39 outer) nested)
383 outer))
384 (results (backtrace-tests--result-with-locals arg)))
385
386 ;; Make a backtrace with local variables visible.
387 (backtrace-tests--make-backtrace arg)
388 (backtrace-print)
389 (backtrace-toggle-locals '(4))
390
391 ;; There should be two ellipses.
392 (goto-char (point-min))
393 (should (search-forward "..."))
394 (should (search-forward "..."))
395 (should-error (search-forward "..."))
396
397 ;; Expanding the last frame without argument should expand both
398 ;; ellipses, but the expansions will contain one ellipsis each.
399 (let ((buffer-len (- (point-max) (point-min))))
400 (goto-char (point-max))
401 (backtrace-backward-frame)
402 (backtrace-expand-ellipses)
403 (should (> (- (point-max) (point-min)) buffer-len))
404 (goto-char (point-min))
405 (should (search-forward "..."))
406 (should (search-forward "..."))
407 (should-error (search-forward "...")))
408
409 ;; Expanding with argument should remove all ellipses.
410 (goto-char (point-max))
411 (backtrace-backward-frame)
412 (backtrace-expand-ellipses '(4))
413 (goto-char (point-min))
414
415 (should-error (search-forward "..."))
416 (should (string= (backtrace-tests--get-substring (point-min) (point-max))
417 results)))))
418
419
378(ert-deftest backtrace-tests--to-string () 420(ert-deftest backtrace-tests--to-string ()
379 "Backtraces can be produced as strings." 421 "Backtraces can be produced as strings."
380 (let ((frames (ert-with-test-buffer (:name nil) 422 (let ((frames (ert-with-test-buffer (:name nil)
diff --git a/test/lisp/emacs-lisp/cl-print-tests.el b/test/lisp/emacs-lisp/cl-print-tests.el
index 7594d2466b5..a469b5526c0 100644
--- a/test/lisp/emacs-lisp/cl-print-tests.el
+++ b/test/lisp/emacs-lisp/cl-print-tests.el
@@ -233,5 +233,41 @@
233 (let ((print-circle t)) 233 (let ((print-circle t))
234 (should (equal "(0 . #1=(0 . #1#))" (cl-prin1-to-string x)))))) 234 (should (equal "(0 . #1=(0 . #1#))" (cl-prin1-to-string x))))))
235 235
236(ert-deftest cl-print-tests-print-to-string-with-limit ()
237 (let* ((thing10 (make-list 10 'a))
238 (thing100 (make-list 100 'a))
239 (thing10x10 (make-list 10 thing10))
240 (nested-thing (let ((val 'a))
241 (dotimes (_i 20)
242 (setq val (list val)))
243 val))
244 ;; Make a consistent environment for this test.
245 (print-circle nil)
246 (print-level nil)
247 (print-length nil))
248
249 ;; Print something that fits in the space given.
250 (should (string= (cl-prin1-to-string thing10)
251 (cl-print-to-string-with-limit #'cl-prin1 thing10 100)))
252
253 ;; Print something which needs to be abbreviated and which can be.
254 (should (< (length (cl-print-to-string-with-limit #'cl-prin1 thing100 100))
255 100
256 (length (cl-prin1-to-string thing100))))
257
258 ;; Print something resistant to easy abbreviation.
259 (should (string= (cl-prin1-to-string thing10x10)
260 (cl-print-to-string-with-limit #'cl-prin1 thing10x10 100)))
261
262 ;; Print something which should be abbreviated even if the limit is large.
263 (should (< (length (cl-print-to-string-with-limit #'cl-prin1 nested-thing 1000))
264 (length (cl-prin1-to-string nested-thing))))
265
266 ;; Print with no limits.
267 (dolist (thing (list thing10 thing100 thing10x10 nested-thing))
268 (let ((rep (cl-prin1-to-string thing)))
269 (should (string= rep (cl-print-to-string-with-limit #'cl-prin1 thing 0)))
270 (should (string= rep (cl-print-to-string-with-limit #'cl-prin1 thing nil)))))))
271
236 272
237;;; cl-print-tests.el ends here. 273;;; cl-print-tests.el ends here.