|
48 | 48 |
|
49 | 49 | (defn- reset-render-state [inspector]
|
50 | 50 | (-> inspector
|
51 |
| - (assoc :counter 0, :index [], :indentation 0, :rendered []) |
| 51 | + (assoc :index [], :indentation 0, :rendered (transient [])) |
52 | 52 | (dissoc :chunk :start-idx :last-page)))
|
53 | 53 |
|
54 | 54 | (defn- print-string
|
|
83 | 83 | `(when-not ~x
|
84 | 84 | (throw (ex-info (str "Precondition failed: " (pr-str '~x)) {}))))
|
85 | 85 |
|
86 |
| -(defn- counted-length [obj] |
| 86 | +(defn- pageable? [obj] |
| 87 | + (contains? #{:list :map :set :array} (object-type obj))) |
| 88 | + |
| 89 | +(defn- counted-length [{:keys [page-size]} obj] |
87 | 90 | (cond (instance? clojure.lang.Counted obj) (count obj)
|
88 | 91 | (instance? Map obj) (.size ^Map obj)
|
89 | 92 | (array? obj) (java.lang.reflect.Array/getLength obj)
|
90 |
| - ;; Count small lazy collections <= 10 elements (arbitrary). |
91 |
| - (sequential? obj) (let [bc (bounded-count 11 obj)] |
92 |
| - (when (<= bc 10) |
93 |
| - bc)))) |
| 93 | + ;; Count small lazy collections (<= page-size). |
| 94 | + (pageable? obj) (let [bc (bounded-count (inc page-size) obj)] |
| 95 | + (when (<= bc page-size) |
| 96 | + bc)))) |
94 | 97 |
|
95 | 98 | (defn- pagination-info
|
96 | 99 | "Calculate if the object should be paginated given the page size. Return a map
|
97 | 100 | with pagination info, or nil if object fits in a single page."
|
98 |
| - [obj page-size current-page] |
99 |
| - (let [clength (counted-length obj) |
| 101 | + [{:keys [page-size current-page view-mode value] :as inspector}] |
| 102 | + (let [page-size (if (= view-mode :hex) |
| 103 | + (* page-size 16) ;; In hex view mode, each row is 16 bytes. |
| 104 | + page-size) |
100 | 105 | start-idx (* current-page page-size)
|
101 | 106 | ;; Try grab a chunk that is one element longer than asked in
|
102 | 107 | ;; page-size. This is how we know there are elements beyond the
|
103 | 108 | ;; current page.
|
104 |
| - chunk+1 (->> obj |
105 |
| - (drop start-idx) |
106 |
| - (take (inc page-size))) |
| 109 | + chunk+1 (persistent! (transduce (comp (drop start-idx) |
| 110 | + (take (inc page-size))) |
| 111 | + conj! (transient []) value)) |
107 | 112 | count+1 (count chunk+1)
|
108 | 113 | paginate? (or (> current-page 0) ;; In non-paginated it's always 0.
|
109 | 114 | (> count+1 page-size))
|
110 |
| - last-page (cond clength (quot (dec clength) page-size) |
111 |
| - (<= count+1 page-size) current-page |
112 |
| - ;; Possibly infinite |
113 |
| - :else Integer/MAX_VALUE)] |
| 115 | + clength (or (counted-length inspector value) |
| 116 | + (when (<= count+1 page-size) |
| 117 | + (+ (* page-size current-page) count+1))) |
| 118 | + last-page (if clength |
| 119 | + (quot (dec clength) page-size) |
| 120 | + ;; Possibly infinite |
| 121 | + Integer/MAX_VALUE)] |
114 | 122 | (when paginate?
|
115 |
| - {:chunk (take page-size chunk+1) |
| 123 | + {:chunk (cond-> chunk+1 |
| 124 | + (> count+1 page-size) pop) |
116 | 125 | :start-idx start-idx
|
117 | 126 | :last-page last-page})))
|
118 | 127 |
|
119 | 128 | (defn- decide-if-paginated
|
120 | 129 | "Make early decision if the inspected object should be paginated. If so,
|
121 | 130 | assoc the `:chunk` to be displayed to `inspector`."
|
122 |
| - [{:keys [value current-page page-size view-mode] :as inspector}] |
123 |
| - (let [pageable? (boolean (#{:list :map :set :array} (object-type value))) |
124 |
| - page-size (if (= view-mode :hex) |
125 |
| - (* page-size 16) ;; In hex view mode, each row is 16 bytes. |
126 |
| - page-size)] |
127 |
| - (cond-> (assoc inspector :pageable pageable?) |
128 |
| - pageable? (merge (pagination-info value page-size current-page))))) |
| 131 | + [{:keys [value] :as inspector}] |
| 132 | + (cond-> inspector |
| 133 | + (pageable? value) (merge (pagination-info inspector)))) |
129 | 134 |
|
130 | 135 | (defn next-page
|
131 | 136 | "Jump to the next page when inspecting a paginated sequence/map. Does nothing
|
|
323 | 328 |
|
324 | 329 | (defn render
|
325 | 330 | ([{:keys [rendered] :as inspector} value]
|
326 |
| - ;; Special case: fuse two last strings together. |
327 |
| - (let [lst (peek (or rendered []))] |
328 |
| - (assoc inspector :rendered (if (and (string? lst) (string? value)) |
329 |
| - (conj (pop rendered) (.concat ^String lst value)) |
330 |
| - (conj rendered value))))) |
| 331 | + (assoc inspector :rendered (conj! rendered value))) |
331 | 332 | ([inspector value & values]
|
332 | 333 | (reduce render (render inspector value) values)))
|
333 | 334 |
|
334 | 335 | (defn render-onto [inspector coll]
|
335 | 336 | (reduce render inspector coll))
|
336 | 337 |
|
337 |
| -(defn render-ln [inspector & values] |
338 |
| - (-> inspector |
339 |
| - (render-onto values) |
340 |
| - (render '(:newline)))) |
| 338 | +(defn render-ln [inspector] |
| 339 | + (render inspector '(:newline))) |
341 | 340 |
|
342 | 341 | (defn- indent
|
343 | 342 | "Increment the `:indentation` of `inspector` by `n` or 2."
|
344 |
| - [inspector & [n]] |
345 |
| - (update inspector :indentation + (or n 2))) |
| 343 | + ([inspector] (update inspector :indentation + 2)) |
| 344 | + ([inspector n] |
| 345 | + (cond-> inspector |
| 346 | + (pos? n) (update :indentation + n)))) |
346 | 347 |
|
347 | 348 | (defn- unindent
|
348 | 349 | "Decrement the `:indentation` of `inspector` by `n` or 2."
|
349 |
| - [inspector & [n]] |
350 |
| - (indent inspector (- (or n 2)))) |
| 350 | + ([inspector] (update inspector :indentation - 2)) |
| 351 | + ([inspector n] |
| 352 | + (cond-> inspector |
| 353 | + (pos? n) (update :indentation - n)))) |
351 | 354 |
|
352 | 355 | (defn- padding [{:keys [indentation]}]
|
353 | 356 | (when (and (number? indentation) (pos? indentation))
|
354 |
| - (String. (char-array indentation \space)))) |
| 357 | + (if (= indentation 2) " " ;; Fastpath |
| 358 | + (String. (char-array indentation \space))))) |
355 | 359 |
|
356 |
| -(defn- render-indent [inspector & values] |
357 |
| - (let [padding (padding inspector)] |
358 |
| - (cond-> inspector |
359 |
| - padding |
360 |
| - (render padding) |
361 |
| - (seq values) |
362 |
| - (render-onto values)))) |
| 360 | +(defn- render-indent |
| 361 | + ([inspector] |
| 362 | + (if-let [padding (padding inspector)] |
| 363 | + (render inspector padding) |
| 364 | + inspector)) |
| 365 | + ([inspector & values] |
| 366 | + (render-onto (render-indent inspector) values))) |
363 | 367 |
|
364 | 368 | (defn- render-indent-ln [inspector & values]
|
365 | 369 | (let [padding (padding inspector)]
|
|
380 | 384 | `display-value` string can be provided explicitly."
|
381 | 385 | ([inspector value] (render-value inspector value nil))
|
382 | 386 | ([inspector value {:keys [value-role value-key display-value]}]
|
383 |
| - (let [{:keys [counter]} inspector |
| 387 | + (let [{:keys [index]} inspector |
384 | 388 | display-value (or display-value (print-string inspector value))
|
385 |
| - expr (list :value display-value counter)] |
| 389 | + expr (seq [:value display-value (count index)])] |
386 | 390 | (-> inspector
|
387 | 391 | (update :index conj {:value value
|
388 | 392 | :role value-role
|
389 | 393 | :key value-key})
|
390 |
| - (update :counter inc) |
391 |
| - (update :rendered conj expr))))) |
| 394 | + (update :rendered conj! expr))))) |
392 | 395 |
|
393 | 396 | (defn render-indented-value [inspector value & [value-opts]]
|
394 | 397 | (-> inspector
|
|
410 | 413 | (render-labeled-value inspector "Class" (class obj)))
|
411 | 414 |
|
412 | 415 | (defn- render-counted-length [inspector obj]
|
413 |
| - (if-let [clength (counted-length obj)] |
| 416 | + (if-let [clength (counted-length inspector obj)] |
414 | 417 | (render-indent-ln inspector "Count: " (str clength))
|
415 | 418 | inspector))
|
416 | 419 |
|
|
507 | 510 | (reduce render-row ins pr-rows))))
|
508 | 511 |
|
509 | 512 | (defn- leftpad [idx last-idx-len]
|
510 |
| - (let [idx-s (str idx) |
| 513 | + (let [^String idx-s (str idx) |
511 | 514 | idx-len (count idx-s)]
|
512 | 515 | (if (= idx-len last-idx-len)
|
513 |
| - (str idx-s ". ") |
| 516 | + (.concat idx-s ". ") |
514 | 517 | (str (String. (char-array (- last-idx-len idx-len) \space)) idx-s ". "))))
|
515 | 518 |
|
516 | 519 | (defn- render-indexed-chunk
|
|
520 | 523 | [{:keys [pretty-print] :as inspector} chunk {:keys [start-idx mark-values? skip-nils?]}]
|
521 | 524 | (let [start-idx (or start-idx 0)
|
522 | 525 | n (count chunk)
|
| 526 | + idx (volatile! start-idx) |
523 | 527 | last-idx (+ start-idx n -1)
|
524 | 528 | last-idx-len (count (str last-idx))]
|
525 |
| - (loop [ins inspector, chunk (seq chunk), idx start-idx] |
526 |
| - (if chunk |
527 |
| - (let [header (leftpad idx last-idx-len) |
528 |
| - indentation (if pretty-print (count header) 0) |
529 |
| - item (first chunk)] |
530 |
| - (recur (if-not (and (nil? item) skip-nils?) |
531 |
| - (-> ins |
532 |
| - (render-indent header) |
533 |
| - (indent indentation) |
534 |
| - (render-value item |
535 |
| - (when mark-values? |
536 |
| - {:value-role :seq-item, :value-key idx})) |
537 |
| - (unindent indentation) |
538 |
| - (render-ln)) |
539 |
| - ins) |
540 |
| - (next chunk) (inc idx))) |
541 |
| - ins)))) |
| 529 | + (reduce (fn [ins item] |
| 530 | + (let [i @idx |
| 531 | + header (leftpad i last-idx-len) |
| 532 | + indentation (if pretty-print (count header) 0)] |
| 533 | + (vswap! idx inc) |
| 534 | + (if-not (and (nil? item) skip-nils?) |
| 535 | + (-> ins |
| 536 | + (render-indent) |
| 537 | + (render header) |
| 538 | + (indent indentation) |
| 539 | + (render-value item |
| 540 | + (when mark-values? |
| 541 | + {:value-role :seq-item, :value-key i})) |
| 542 | + (unindent indentation) |
| 543 | + (render-ln)) |
| 544 | + ins))) |
| 545 | + inspector chunk))) |
542 | 546 |
|
543 | 547 | (declare known-types)
|
544 | 548 |
|
|
566 | 570 | "If `obj` is a collection smaller than page-size, then render it as a
|
567 | 571 | collection, otherwise as a compact value."
|
568 | 572 | [{:keys [page-size] :as inspector} obj]
|
569 |
| - (if (some-> (counted-length obj) (<= page-size)) |
| 573 | + (if (some-> (counted-length inspector obj) (<= page-size)) |
570 | 574 | (render-items inspector obj {:map? (map? obj), :start-idx 0})
|
571 | 575 | (render-indented-value inspector obj)))
|
572 | 576 |
|
|
656 | 660 | "Datafy either the current value or its paginated view. Return datafied
|
657 | 661 | representation if it differs from value and boolean `mirror?` that tells if
|
658 | 662 | the datafied representation mirrors the structure of the input collection."
|
659 |
| - [{:keys [value chunk pageable]}] |
| 663 | + [{:keys [value chunk]}] |
660 | 664 | (if-let [datafied (datafy-root value)]
|
661 | 665 | ;; If the root value has datafy representation, check if it's a collection.
|
662 | 666 | ;; If so, additionally datafy its items or map values.
|
|
669 | 673 | (when-not (identical? datafied value)
|
670 | 674 | [datafied false]))
|
671 | 675 |
|
672 |
| - (when pageable |
| 676 | + (when (pageable? value) |
673 | 677 | ;; If the value is a type that can be paged, then only datafy the
|
674 | 678 | ;; displayed chunk.
|
675 | 679 | (let [chunk (or chunk value)
|
|
751 | 755 |
|
752 | 756 | (defmethod inspect :nil [inspector _obj]
|
753 | 757 | (-> inspector
|
754 |
| - (render-ln "Value: nil") |
| 758 | + (render "Value: nil") |
| 759 | + (render-ln) |
755 | 760 | (render-section-header "Contents")
|
756 | 761 | (indent)
|
757 | 762 | (render-indent-ln
|
|
898 | 903 | (render-ident-hashcode [inspector]
|
899 | 904 | (let [code (System/identityHashCode obj)]
|
900 | 905 | (-> inspector
|
901 |
| - (render-indent "Identity hash code: " (str code) " " |
902 |
| - (format "(0x%s)" (Integer/toHexString code))) |
| 906 | + (render "Identity hash code: " (str code) " " |
| 907 | + (format "(0x%s)" (Integer/toHexString code))) |
903 | 908 | (render-ln))))]
|
904 | 909 | (cond-> inspector
|
905 | 910 | true (render-labeled-value "Class" (class obj))
|
|
1058 | 1063 |
|
1059 | 1064 | (defmethod inspect :namespace [inspector ^clojure.lang.Namespace obj]
|
1060 | 1065 | (-> (render-class-name inspector obj)
|
1061 |
| - (render-counted-length (ns-map obj)) |
1062 | 1066 | (render-meta-information obj)
|
1063 | 1067 | (render-ns-refers obj)
|
1064 | 1068 | (render-ns-imports obj)
|
|
1088 | 1092 | (unindent)))
|
1089 | 1093 | inspector))
|
1090 | 1094 |
|
| 1095 | +(defn- finalize-rendered [rendered] |
| 1096 | + (seq (persistent! rendered))) |
| 1097 | + |
1091 | 1098 | (defn inspect-render
|
1092 | 1099 | ([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value pretty-print]
|
1093 | 1100 | :as inspector}]
|
|
1108 | 1115 | (inspect value)
|
1109 | 1116 | (render-path)
|
1110 | 1117 | (render-view-mode)
|
1111 |
| - (update :rendered seq))))) |
| 1118 | + (update :rendered finalize-rendered))))) |
1112 | 1119 |
|
1113 | 1120 | ;; Public entrypoints
|
1114 | 1121 |
|
|
1133 | 1140 | "If necessary, use `(start nil)` instead."
|
1134 | 1141 | []
|
1135 | 1142 | (start nil))
|
1136 |
| - |
1137 |
| -(defn inspect-print |
1138 |
| - "Get a human readable printout of rendered sequence." |
1139 |
| - [x] |
1140 |
| - (print |
1141 |
| - (with-out-str |
1142 |
| - (doseq [[type value :as component] (:rendered (start x))] |
1143 |
| - (print (case type |
1144 |
| - :newline \newline |
1145 |
| - :value (str value) |
1146 |
| - component)))))) |
0 commit comments