diff options
| author | Gemini Lasswell | 2018-06-15 10:23:58 -0700 |
|---|---|---|
| committer | Gemini Lasswell | 2018-08-03 08:53:01 -0700 |
| commit | eba16e5e5829c244d313101a769d4988946387d9 (patch) | |
| tree | 33b098f6324ce3f5feefa5213403789b29527943 | |
| parent | e65ec81fc3e556719fae8d8b4b42f571c7e9f4fc (diff) | |
| download | emacs-eba16e5e5829c244d313101a769d4988946387d9.tar.gz emacs-eba16e5e5829c244d313101a769d4988946387d9.zip | |
Support ellipsis expansion in cl-print
* lisp/emacs-lisp/cl-print.el (cl-print-object-contents): New
generic method.
(cl-print-object-contents) <cons, vector,cl-structure-object>: New
methods.
(cl-print-object) <cons>: Use cl-print-insert-ellipsis.
(cl-print-object) <vector, cl-structure-object>: Elide whole object if
print-level exceeded. Use cl-print-insert-ellipsis.
(cl-print-insert-ellipsis, cl-print-propertize-ellipsis)
(cl-print-expand-ellipsis): New functions.
* test/lisp/emacs-lisp/cl-print-tests.el (cl-print-tests-4): Test
printing of objects nested in other objects.
(cl-print-tests-strings, cl-print-tests-ellipsis-cons)
(cl-print-tests-ellipsis-vector, cl-print-tests-ellipsis-struct)
(cl-print-tests-ellipsis-circular): New tests.
(cl-print-tests-check-ellipsis-expansion)
(cl-print-tests-check-ellipsis-expansion-rx): New functions.
| -rw-r--r-- | lisp/emacs-lisp/cl-print.el | 155 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/cl-print-tests.el | 89 |
2 files changed, 220 insertions, 24 deletions
diff --git a/lisp/emacs-lisp/cl-print.el b/lisp/emacs-lisp/cl-print.el index bf5b1e878d5..e638e58275a 100644 --- a/lisp/emacs-lisp/cl-print.el +++ b/lisp/emacs-lisp/cl-print.el | |||
| @@ -55,10 +55,19 @@ call other entry points instead, such as `cl-prin1'." | |||
| 55 | ;; we should only use it for objects which don't have nesting. | 55 | ;; we should only use it for objects which don't have nesting. |
| 56 | (prin1 object stream)) | 56 | (prin1 object stream)) |
| 57 | 57 | ||
| 58 | (cl-defgeneric cl-print-object-contents (_object _start _stream) | ||
| 59 | "Dispatcher to print the contents of OBJECT on STREAM. | ||
| 60 | Print the contents starting with the item at START, without | ||
| 61 | delimiters." | ||
| 62 | ;; Every cl-print-object method which can print an ellipsis should | ||
| 63 | ;; have a matching cl-print-object-contents method to expand an | ||
| 64 | ;; ellipsis. | ||
| 65 | (error "Missing cl-print-object-contents method")) | ||
| 66 | |||
| 58 | (cl-defmethod cl-print-object ((object cons) stream) | 67 | (cl-defmethod cl-print-object ((object cons) stream) |
| 59 | (if (and cl-print--depth (natnump print-level) | 68 | (if (and cl-print--depth (natnump print-level) |
| 60 | (> cl-print--depth print-level)) | 69 | (> cl-print--depth print-level)) |
| 61 | (princ "..." stream) | 70 | (cl-print-insert-ellipsis object 0 stream) |
| 62 | (let ((car (pop object)) | 71 | (let ((car (pop object)) |
| 63 | (count 1)) | 72 | (count 1)) |
| 64 | (if (and print-quoted | 73 | (if (and print-quoted |
| @@ -84,23 +93,60 @@ call other entry points instead, such as `cl-prin1'." | |||
| 84 | (princ " " stream) | 93 | (princ " " stream) |
| 85 | (if (or (not (natnump print-length)) (> print-length count)) | 94 | (if (or (not (natnump print-length)) (> print-length count)) |
| 86 | (cl-print-object (pop object) stream) | 95 | (cl-print-object (pop object) stream) |
| 87 | (princ "..." stream) | 96 | (cl-print-insert-ellipsis object print-length stream) |
| 88 | (setq object nil)) | 97 | (setq object nil)) |
| 89 | (cl-incf count)) | 98 | (cl-incf count)) |
| 90 | (when object | 99 | (when object |
| 91 | (princ " . " stream) (cl-print-object object stream)) | 100 | (princ " . " stream) (cl-print-object object stream)) |
| 92 | (princ ")" stream))))) | 101 | (princ ")" stream))))) |
| 93 | 102 | ||
| 103 | (cl-defmethod cl-print-object-contents ((object cons) _start stream) | ||
| 104 | (let ((count 0)) | ||
| 105 | (while (and (consp object) | ||
| 106 | (not (cond | ||
| 107 | (cl-print--number-table | ||
| 108 | (numberp (gethash object cl-print--number-table))) | ||
| 109 | ((memq object cl-print--currently-printing)) | ||
| 110 | (t (push object cl-print--currently-printing) | ||
| 111 | nil)))) | ||
| 112 | (unless (zerop count) | ||
| 113 | (princ " " stream)) | ||
| 114 | (if (or (not (natnump print-length)) (> print-length count)) | ||
| 115 | (cl-print-object (pop object) stream) | ||
| 116 | (cl-print-insert-ellipsis object print-length stream) | ||
| 117 | (setq object nil)) | ||
| 118 | (cl-incf count)) | ||
| 119 | (when object | ||
| 120 | (princ " . " stream) (cl-print-object object stream)))) | ||
| 121 | |||
| 94 | (cl-defmethod cl-print-object ((object vector) stream) | 122 | (cl-defmethod cl-print-object ((object vector) stream) |
| 95 | (princ "[" stream) | 123 | (if (and cl-print--depth (natnump print-level) |
| 96 | (let ((count (length object))) | 124 | (> cl-print--depth print-level)) |
| 97 | (dotimes (i (if (natnump print-length) | 125 | (cl-print-insert-ellipsis object 0 stream) |
| 98 | (min print-length count) count)) | 126 | (princ "[" stream) |
| 99 | (unless (zerop i) (princ " " stream)) | 127 | (let* ((len (length object)) |
| 100 | (cl-print-object (aref object i) stream)) | 128 | (limit (if (natnump print-length) |
| 101 | (when (and (natnump print-length) (< print-length count)) | 129 | (min print-length len) len))) |
| 102 | (princ " ..." stream))) | 130 | (dotimes (i limit) |
| 103 | (princ "]" stream)) | 131 | (unless (zerop i) (princ " " stream)) |
| 132 | (cl-print-object (aref object i) stream)) | ||
| 133 | (when (< limit len) | ||
| 134 | (princ " " stream) | ||
| 135 | (cl-print-insert-ellipsis object limit stream))) | ||
| 136 | (princ "]" stream))) | ||
| 137 | |||
| 138 | (cl-defmethod cl-print-object-contents ((object vector) start stream) | ||
| 139 | (let* ((len (length object)) | ||
| 140 | (limit (if (natnump print-length) | ||
| 141 | (min (+ start print-length) len) len)) | ||
| 142 | (i start)) | ||
| 143 | (while (< i limit) | ||
| 144 | (unless (= i start) (princ " " stream)) | ||
| 145 | (cl-print-object (aref object i) stream) | ||
| 146 | (cl-incf i)) | ||
| 147 | (when (< limit len) | ||
| 148 | (princ " " stream) | ||
| 149 | (cl-print-insert-ellipsis object limit stream)))) | ||
| 104 | 150 | ||
| 105 | (cl-defmethod cl-print-object ((object hash-table) stream) | 151 | (cl-defmethod cl-print-object ((object hash-table) stream) |
| 106 | (princ "#<hash-table " stream) | 152 | (princ "#<hash-table " stream) |
| @@ -199,21 +245,46 @@ into a button whose action shows the function's disassembly.") | |||
| 199 | (princ ")" stream))) | 245 | (princ ")" stream))) |
| 200 | 246 | ||
| 201 | (cl-defmethod cl-print-object ((object cl-structure-object) stream) | 247 | (cl-defmethod cl-print-object ((object cl-structure-object) stream) |
| 202 | (princ "#s(" stream) | 248 | (if (and cl-print--depth (natnump print-level) |
| 249 | (> cl-print--depth print-level)) | ||
| 250 | (cl-print-insert-ellipsis object 0 stream) | ||
| 251 | (princ "#s(" stream) | ||
| 252 | (let* ((class (cl-find-class (type-of object))) | ||
| 253 | (slots (cl--struct-class-slots class)) | ||
| 254 | (len (length slots)) | ||
| 255 | (limit (if (natnump print-length) | ||
| 256 | (min print-length len) len))) | ||
| 257 | (princ (cl--struct-class-name class) stream) | ||
| 258 | (dotimes (i limit) | ||
| 259 | (let ((slot (aref slots i))) | ||
| 260 | (princ " :" stream) | ||
| 261 | (princ (cl--slot-descriptor-name slot) stream) | ||
| 262 | (princ " " stream) | ||
| 263 | (cl-print-object (aref object (1+ i)) stream))) | ||
| 264 | (when (< limit len) | ||
| 265 | (princ " " stream) | ||
| 266 | (cl-print-insert-ellipsis object limit stream))) | ||
| 267 | (princ ")" stream))) | ||
| 268 | |||
| 269 | (cl-defmethod cl-print-object-contents ((object cl-structure-object) start stream) | ||
| 203 | (let* ((class (cl-find-class (type-of object))) | 270 | (let* ((class (cl-find-class (type-of object))) |
| 204 | (slots (cl--struct-class-slots class)) | 271 | (slots (cl--struct-class-slots class)) |
| 205 | (count (length slots))) | 272 | (len (length slots)) |
| 206 | (princ (cl--struct-class-name class) stream) | 273 | (limit (if (natnump print-length) |
| 207 | (dotimes (i (if (natnump print-length) | 274 | (min (+ start print-length) len) len)) |
| 208 | (min print-length count) count)) | 275 | (i start)) |
| 276 | (while (< i limit) | ||
| 209 | (let ((slot (aref slots i))) | 277 | (let ((slot (aref slots i))) |
| 210 | (princ " :" stream) | 278 | (unless (= i start) (princ " " stream)) |
| 279 | (princ ":" stream) | ||
| 211 | (princ (cl--slot-descriptor-name slot) stream) | 280 | (princ (cl--slot-descriptor-name slot) stream) |
| 212 | (princ " " stream) | 281 | (princ " " stream) |
| 213 | (cl-print-object (aref object (1+ i)) stream))) | 282 | (cl-print-object (aref object (1+ i)) stream)) |
| 214 | (when (and (natnump print-length) (< print-length count)) | 283 | (cl-incf i)) |
| 215 | (princ " ..." stream))) | 284 | (when (< limit len) |
| 216 | (princ ")" stream)) | 285 | (princ " " stream) |
| 286 | (cl-print-insert-ellipsis object limit stream)))) | ||
| 287 | |||
| 217 | 288 | ||
| 218 | ;;; Circularity and sharing. | 289 | ;;; Circularity and sharing. |
| 219 | 290 | ||
| @@ -291,6 +362,48 @@ into a button whose action shows the function's disassembly.") | |||
| 291 | (cl-print--find-sharing object print-number-table))) | 362 | (cl-print--find-sharing object print-number-table))) |
| 292 | print-number-table)) | 363 | print-number-table)) |
| 293 | 364 | ||
| 365 | (defun cl-print-insert-ellipsis (object start stream) | ||
| 366 | "Print \"...\" to STREAM with the `cl-print-ellipsis' text property. | ||
| 367 | Save state in the text property in order to print the elided part | ||
| 368 | of OBJECT later. START should be 0 if the whole OBJECT is being | ||
| 369 | elided, otherwise it should be an index or other pointer into the | ||
| 370 | internals of OBJECT which can be passed to | ||
| 371 | `cl-print-object-contents' at a future time." | ||
| 372 | (unless stream (setq stream standard-output)) | ||
| 373 | (let ((ellipsis-start (and (bufferp stream) | ||
| 374 | (with-current-buffer stream (point))))) | ||
| 375 | (princ "..." stream) | ||
| 376 | (when ellipsis-start | ||
| 377 | (with-current-buffer stream | ||
| 378 | (cl-print-propertize-ellipsis object start ellipsis-start (point) | ||
| 379 | stream))))) | ||
| 380 | |||
| 381 | (defun cl-print-propertize-ellipsis (object start beg end stream) | ||
| 382 | "Add the `cl-print-ellipsis' property between BEG and END. | ||
| 383 | STREAM should be a buffer. OBJECT and START are as described in | ||
| 384 | `cl-print-insert-ellipsis'." | ||
| 385 | (let ((value (list object start cl-print--number-table | ||
| 386 | cl-print--currently-printing))) | ||
| 387 | (with-current-buffer stream | ||
| 388 | (put-text-property beg end 'cl-print-ellipsis value stream)))) | ||
| 389 | |||
| 390 | ;;;###autoload | ||
| 391 | (defun cl-print-expand-ellipsis (value stream) | ||
| 392 | "Print the expansion of an ellipsis to STREAM. | ||
| 393 | VALUE should be the value of the `cl-print-ellipsis' text property | ||
| 394 | which was attached to the ellipsis by `cl-prin1'." | ||
| 395 | (let ((cl-print--depth 1) | ||
| 396 | (object (nth 0 value)) | ||
| 397 | (start (nth 1 value)) | ||
| 398 | (cl-print--number-table (nth 2 value)) | ||
| 399 | (print-number-table (nth 2 value)) | ||
| 400 | (cl-print--currently-printing (nth 3 value))) | ||
| 401 | (when (eq object (car cl-print--currently-printing)) | ||
| 402 | (pop cl-print--currently-printing)) | ||
| 403 | (if (equal start 0) | ||
| 404 | (cl-print-object object stream) | ||
| 405 | (cl-print-object-contents object start stream)))) | ||
| 406 | |||
| 294 | ;;;###autoload | 407 | ;;;###autoload |
| 295 | (defun cl-prin1 (object &optional stream) | 408 | (defun cl-prin1 (object &optional stream) |
| 296 | "Print OBJECT on STREAM according to its type. | 409 | "Print OBJECT on STREAM according to its type. |
diff --git a/test/lisp/emacs-lisp/cl-print-tests.el b/test/lisp/emacs-lisp/cl-print-tests.el index 404d323d0c1..2b5eb3402bf 100644 --- a/test/lisp/emacs-lisp/cl-print-tests.el +++ b/test/lisp/emacs-lisp/cl-print-tests.el | |||
| @@ -64,11 +64,15 @@ | |||
| 64 | 64 | ||
| 65 | (ert-deftest cl-print-tests-4 () | 65 | (ert-deftest cl-print-tests-4 () |
| 66 | "CL printing observes `print-level'." | 66 | "CL printing observes `print-level'." |
| 67 | (let ((deep-list '(a (b (c (d (e)))))) | 67 | (let* ((deep-list '(a (b (c (d (e)))))) |
| 68 | (deep-struct (cl-print-tests-con)) | 68 | (buried-vector '(a (b (c (d [e]))))) |
| 69 | (print-level 4)) | 69 | (deep-struct (cl-print-tests-con)) |
| 70 | (buried-struct `(a (b (c (d ,deep-struct))))) | ||
| 71 | (print-level 4)) | ||
| 70 | (setf (cl-print-tests-struct-a deep-struct) deep-list) | 72 | (setf (cl-print-tests-struct-a deep-struct) deep-list) |
| 71 | (should (equal "(a (b (c (d ...))))" (cl-prin1-to-string deep-list))) | 73 | (should (equal "(a (b (c (d ...))))" (cl-prin1-to-string deep-list))) |
| 74 | (should (equal "(a (b (c (d ...))))" (cl-prin1-to-string buried-vector))) | ||
| 75 | (should (equal "(a (b (c (d ...))))" (cl-prin1-to-string buried-struct))) | ||
| 72 | (should (equal "#s(cl-print-tests-struct :a (a (b (c ...))) :b nil :c nil :d nil :e nil)" | 76 | (should (equal "#s(cl-print-tests-struct :a (a (b (c ...))) :b nil :c nil :d nil :e nil)" |
| 73 | (cl-prin1-to-string deep-struct))))) | 77 | (cl-prin1-to-string deep-struct))))) |
| 74 | 78 | ||
| @@ -82,6 +86,85 @@ | |||
| 82 | (should (equal "((quote a) (function b) (\\` ((\\, c) (\\,@ d))))" | 86 | (should (equal "((quote a) (function b) (\\` ((\\, c) (\\,@ d))))" |
| 83 | (cl-prin1-to-string quoted-stuff)))))) | 87 | (cl-prin1-to-string quoted-stuff)))))) |
| 84 | 88 | ||
| 89 | (ert-deftest cl-print-tests-ellipsis-cons () | ||
| 90 | "Ellipsis expansion works in conses." | ||
| 91 | (let ((print-length 4) | ||
| 92 | (print-level 3)) | ||
| 93 | (cl-print-tests-check-ellipsis-expansion | ||
| 94 | '(0 1 2 3 4 5) "(0 1 2 3 ...)" "4 5") | ||
| 95 | (cl-print-tests-check-ellipsis-expansion | ||
| 96 | '(0 1 2 3 4 5 6 7 8 9) "(0 1 2 3 ...)" "4 5 6 7 ...") | ||
| 97 | (cl-print-tests-check-ellipsis-expansion | ||
| 98 | '(a (b (c (d (e))))) "(a (b (c ...)))" "(d (e))") | ||
| 99 | (cl-print-tests-check-ellipsis-expansion | ||
| 100 | (let ((x (make-list 6 'b))) | ||
| 101 | (setf (nthcdr 6 x) 'c) | ||
| 102 | x) | ||
| 103 | "(b b b b ...)" "b b . c"))) | ||
| 104 | |||
| 105 | (ert-deftest cl-print-tests-ellipsis-vector () | ||
| 106 | "Ellipsis expansion works in vectors." | ||
| 107 | (let ((print-length 4) | ||
| 108 | (print-level 3)) | ||
| 109 | (cl-print-tests-check-ellipsis-expansion | ||
| 110 | [0 1 2 3 4 5] "[0 1 2 3 ...]" "4 5") | ||
| 111 | (cl-print-tests-check-ellipsis-expansion | ||
| 112 | [0 1 2 3 4 5 6 7 8 9] "[0 1 2 3 ...]" "4 5 6 7 ...") | ||
| 113 | (cl-print-tests-check-ellipsis-expansion | ||
| 114 | [a [b [c [d [e]]]]] "[a [b [c ...]]]" "[d [e]]"))) | ||
| 115 | |||
| 116 | (ert-deftest cl-print-tests-ellipsis-struct () | ||
| 117 | "Ellipsis expansion works in structures." | ||
| 118 | (let ((print-length 4) | ||
| 119 | (print-level 3) | ||
| 120 | (struct (cl-print-tests-con))) | ||
| 121 | (cl-print-tests-check-ellipsis-expansion | ||
| 122 | struct "#s(cl-print-tests-struct :a nil :b nil :c nil :d nil ...)" ":e nil") | ||
| 123 | (let ((print-length 2)) | ||
| 124 | (cl-print-tests-check-ellipsis-expansion | ||
| 125 | struct "#s(cl-print-tests-struct :a nil :b nil ...)" ":c nil :d nil ...")) | ||
| 126 | (cl-print-tests-check-ellipsis-expansion | ||
| 127 | `(a (b (c ,struct))) | ||
| 128 | "(a (b (c ...)))" | ||
| 129 | "#s(cl-print-tests-struct :a nil :b nil :c nil :d nil ...)"))) | ||
| 130 | |||
| 131 | (ert-deftest cl-print-tests-ellipsis-circular () | ||
| 132 | "Ellipsis expansion works with circular objects." | ||
| 133 | (let ((wide-obj (list 0 1 2 3 4)) | ||
| 134 | (deep-obj `(0 (1 (2 (3 (4)))))) | ||
| 135 | (print-length 4) | ||
| 136 | (print-level 3)) | ||
| 137 | (setf (nth 4 wide-obj) wide-obj) | ||
| 138 | (setf (car (cadadr (cadadr deep-obj))) deep-obj) | ||
| 139 | (let ((print-circle nil)) | ||
| 140 | (cl-print-tests-check-ellipsis-expansion-rx | ||
| 141 | wide-obj (regexp-quote "(0 1 2 3 ...)") "\\`#[0-9]\\'") | ||
| 142 | (cl-print-tests-check-ellipsis-expansion-rx | ||
| 143 | deep-obj (regexp-quote "(0 (1 (2 ...)))") "\\`(3 (#[0-9]))\\'")) | ||
| 144 | (let ((print-circle t)) | ||
| 145 | (cl-print-tests-check-ellipsis-expansion | ||
| 146 | wide-obj "#1=(0 1 2 3 ...)" "#1#") | ||
| 147 | (cl-print-tests-check-ellipsis-expansion | ||
| 148 | deep-obj "#1=(0 (1 (2 ...)))" "(3 (#1#))")))) | ||
| 149 | |||
| 150 | (defun cl-print-tests-check-ellipsis-expansion (obj expected expanded) | ||
| 151 | (let* ((result (cl-prin1-to-string obj)) | ||
| 152 | (pos (next-single-property-change 0 'cl-print-ellipsis result)) | ||
| 153 | value) | ||
| 154 | (should pos) | ||
| 155 | (setq value (get-text-property pos 'cl-print-ellipsis result)) | ||
| 156 | (should (equal expected result)) | ||
| 157 | (should (equal expanded (with-output-to-string (cl-print-expand-ellipsis | ||
| 158 | value nil)))))) | ||
| 159 | |||
| 160 | (defun cl-print-tests-check-ellipsis-expansion-rx (obj expected expanded) | ||
| 161 | (let* ((result (cl-prin1-to-string obj)) | ||
| 162 | (pos (next-single-property-change 0 'cl-print-ellipsis result)) | ||
| 163 | (value (get-text-property pos 'cl-print-ellipsis result))) | ||
| 164 | (should (string-match expected result)) | ||
| 165 | (should (string-match expanded (with-output-to-string | ||
| 166 | (cl-print-expand-ellipsis value nil)))))) | ||
| 167 | |||
| 85 | (ert-deftest cl-print-circle () | 168 | (ert-deftest cl-print-circle () |
| 86 | (let ((x '(#1=(a . #1#) #1#))) | 169 | (let ((x '(#1=(a . #1#) #1#))) |
| 87 | (let ((print-circle nil)) | 170 | (let ((print-circle nil)) |