Skip to content

Commit cd6109e

Browse files
committed
Add :pretty view mode
1 parent be8d402 commit cd6109e

File tree

4 files changed

+246
-25
lines changed

4 files changed

+246
-25
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## master (unreleased)
44

5+
* [#335](https://github.com/clojure-emacs/orchard/pull/335) Add `orchard.pp` and pretty view mode.
6+
57
## 0.33.0 (2025-04-08)
68

79
* [#333](https://github.com/clojure-emacs/orchard/pull/333): Add `orchard.profile`.

src/orchard/inspect.clj

+100-24
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
(list 'get key)))
3535
(conj path '<unknown>)))
3636

37-
(def ^:private supported-view-modes #{:normal :object :table})
37+
(def ^:private supported-view-modes #{:normal :object :table :pretty})
3838

3939
(def ^:private default-inspector-config
4040
"Default configuration values for the inspector."
@@ -51,6 +51,14 @@
5151
(assoc :counter 0, :index [], :indentation 0, :rendered [])
5252
(dissoc :chunk :start-idx :last-page)))
5353

54+
(defn- print-string
55+
"Print or pretty print the string `value`, depending on the view mode
56+
of the inspector."
57+
[{:keys [indentation view-mode]} value]
58+
(if (= :pretty view-mode)
59+
(print/pprint-str value {:indentation (or indentation 0)})
60+
(print/print-str value)))
61+
5462
(defn- array? [obj]
5563
(some-> (class obj) .isArray))
5664

@@ -294,11 +302,19 @@
294302
(render-onto values)
295303
(render '(:newline))))
296304

297-
(defn- indent [inspector]
298-
(update inspector :indentation + 2))
305+
(defn- indent
306+
"Increment the `:indentation` of `inspector` by `n` (or 2) when
307+
the :view-mode is in `modes` or `modes` is nil."
308+
[inspector & [n modes]]
309+
(cond-> inspector
310+
(or (nil? modes) (contains? modes (:view-mode inspector)))
311+
(update :indentation + (or n 2))))
299312

300-
(defn- unindent [inspector]
301-
(update inspector :indentation - 2))
313+
(defn- unindent
314+
"Decrement the `:indentation` of `inspector` by `n` (or 2) when the
315+
`:view-mode` is in `modes` or `modes` is nil."
316+
[inspector & [n modes]]
317+
(indent inspector (- (or n 2)) modes))
302318

303319
(defn- padding [{:keys [indentation]}]
304320
(when (and (number? indentation) (pos? indentation))
@@ -325,7 +341,7 @@
325341
([inspector value] (render-value inspector value nil))
326342
([inspector value {:keys [value-role value-key display-value]}]
327343
(let [{:keys [counter]} inspector
328-
display-value (or display-value (print/print-str value))
344+
display-value (or display-value (print-string inspector value))
329345
expr (list :value display-value counter)]
330346
(-> inspector
331347
(update :index conj {:value value
@@ -356,18 +372,68 @@
356372
(render-ln))
357373
inspector))
358374

375+
(defn- rendered-element-str
376+
"Return the string representation of the rendered `element`."
377+
[element]
378+
(cond (string? element)
379+
element
380+
(and (seq? element) (= :value (first element)))
381+
(second element)
382+
(and (seq? element) (= :newline (first element)))
383+
"\n"
384+
:else
385+
(throw (ex-info "Unsupported element" {:element element}))))
386+
387+
(defn- render-map-key
388+
"Render the key of a map."
389+
[inspector key]
390+
(render-value inspector key))
391+
392+
(defn- long-map-key?
393+
"Returns true of `s` is a long string, more than 20 character or
394+
containing newlines."
395+
[^String s]
396+
(or (.contains s "\n") (> (count s) 20)))
397+
398+
(defn- render-map-separator
399+
"Render the map separator according to `rendered-key`. If
400+
`rendered-key` is long or contains newlines the key and value will
401+
be rendered on separate lines."
402+
[{:keys [view-mode] :as inspector} rendered-key]
403+
(if (and (= :pretty view-mode) (long-map-key? rendered-key))
404+
(-> (render-ln inspector)
405+
(render-indent "=")
406+
(render-ln))
407+
(render inspector " = ")))
408+
409+
(defn- render-map-value
410+
"Render a map value. If `mark-values?` is true, attach the keys to the
411+
values in the index."
412+
[{:keys [view-mode] :as inspector} key val mark-values? rendered-key]
413+
(if (= :pretty view-mode)
414+
(let [indentation (if (long-map-key? rendered-key) 0 (+ 3 (count rendered-key)))]
415+
(-> (indent inspector indentation)
416+
(render (if (zero? indentation) " " ""))
417+
(render-value val
418+
(when mark-values?
419+
{:value-role :map-value, :value-key key}))
420+
(unindent indentation)
421+
((if (long-map-key? rendered-key) render-ln identity))))
422+
(render-value inspector val
423+
(when mark-values?
424+
{:value-role :map-value, :value-key key}))))
425+
359426
(defn- render-map-values
360427
"Render associative key-value pairs. If `mark-values?` is true, attach the keys
361428
to the values in the index."
362429
[inspector mappable mark-values?]
363430
(reduce (fn [ins [key val]]
364-
(-> ins
365-
(render-indent)
366-
(render-value key)
367-
(render " = ")
368-
(render-value val (when mark-values?
369-
{:value-role :map-value, :value-key key}))
370-
(render-ln)))
431+
(let [rendered-key (rendered-element-str (last (:rendered (render-map-key ins key))))]
432+
(-> (render-indent ins)
433+
(render-map-key key)
434+
(render-map-separator rendered-key)
435+
(render-map-value key val mark-values? rendered-key)
436+
(render-ln))))
371437
inspector
372438
mappable))
373439

@@ -433,13 +499,16 @@
433499
idx-fmt (str "%" last-idx-len "s")]
434500
(loop [ins inspector, chunk (seq chunk), idx idx-starts-from]
435501
(if chunk
436-
(recur (-> ins
437-
(render-indent (format idx-fmt idx) ". ")
438-
(render-value (first chunk)
439-
(when mark-values?
440-
{:value-role :seq-item, :value-key idx}))
441-
(render-ln))
442-
(next chunk) (inc idx))
502+
(let [header (str (format idx-fmt idx) ". ")]
503+
(recur (-> ins
504+
(render-indent header)
505+
(indent (count header) #{:pretty})
506+
(render-value (first chunk)
507+
(when mark-values?
508+
{:value-role :seq-item, :value-key idx}))
509+
(unindent (count header) #{:pretty})
510+
(render-ln))
511+
(next chunk) (inc idx)))
443512
ins))))
444513

445514
(declare known-types)
@@ -656,7 +725,7 @@
656725

657726
(defmethod inspect :string [inspector ^java.lang.String obj]
658727
(-> (render-class-name inspector obj)
659-
(render "Value: " (print/print-str obj))
728+
(render "Value: " (print-string inspector obj))
660729
(render-ln)
661730
(render-section-header "Print")
662731
(indent)
@@ -716,7 +785,7 @@
716785
(instance? Field obj)
717786
(shorten-member-string (str obj) (.getDeclaringClass ^Field obj))
718787

719-
:else (print/print-str obj))]
788+
:else (print-string inspector obj))]
720789
(letfn [(render-fields [inspector section-name field-values]
721790
(if (seq field-values)
722791
(-> inspector
@@ -924,12 +993,19 @@
924993
(unindent)))))
925994

926995
(defn inspect-render
927-
([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value]
996+
([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value view-mode]
928997
:as inspector}]
929998
(binding [print/*max-atom-length* max-atom-length
930999
print/*max-total-length* max-value-length
9311000
*print-length* max-coll-size
932-
*print-level* max-nested-depth]
1001+
*print-level* (cond-> max-nested-depth
1002+
;; In pretty mode a higher *print-level*
1003+
;; leads to better results, otherwise we
1004+
;; render a ton of # characters when
1005+
;; there is still enough screen estate
1006+
;; in most cases.
1007+
(and (= :pretty view-mode) (number? max-nested-depth))
1008+
(* 2))]
9331009
(-> inspector
9341010
(reset-render-state)
9351011
(decide-if-paginated)

src/orchard/print.clj

+15-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
Var)
1616
(java.util List Map Map$Entry)
1717
(mx.cider.orchard TruncatingStringWriter
18-
TruncatingStringWriter$TotalLimitExceeded)))
18+
TruncatingStringWriter$TotalLimitExceeded))
19+
(:require [clojure.pprint :as pprint]
20+
[orchard.pp :as pp]
21+
[clojure.string :as str]))
1922

2023
(defmulti print
2124
(fn [x _]
@@ -190,3 +193,14 @@
190193
(try (print x writer)
191194
(catch TruncatingStringWriter$TotalLimitExceeded _))
192195
(.toString writer)))
196+
197+
(defn pprint-str
198+
"Pretty print the object `x` with `orchard.pp/pprint` and return it as
199+
a string. The `:indentation` option is the number of spaces used for
200+
indentation."
201+
[x & [{:keys [indentation]}]]
202+
(let [writer (TruncatingStringWriter. *max-atom-length* *max-total-length*)
203+
indentation-str (apply str (repeat (or indentation 0) " "))]
204+
(try (pp/pprint writer x {:indentation indentation-str})
205+
(catch TruncatingStringWriter$TotalLimitExceeded _))
206+
(str/trimr (.toString writer))))

test/orchard/inspect_test.clj

+129
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,135 @@
15591559
[:newline]]
15601560
(section "Contents" rendered)))))
15611561

1562+
(deftest pretty-view-mode-map-test
1563+
(testing "in :pretty view-mode are pretty printed"
1564+
(let [rendered (-> {:a 0
1565+
:bb "000"
1566+
:ccc []
1567+
:d [{:a 0 :bb "000" :ccc [[]]}
1568+
{:a -1 :bb "111" :ccc [1]}
1569+
{:a 2 :bb "222" :ccc [1 2]}]}
1570+
(inspect/start)
1571+
(inspect/set-view-mode :pretty)
1572+
render)]
1573+
(is+ ["--- Contents:" [:newline] " "
1574+
[:value ":a" 1] " = " [:value "0" 2]
1575+
[:newline] " "
1576+
[:value ":bb" 3] " = " [:value "\"000\"" 4]
1577+
[:newline] " "
1578+
[:value ":ccc" 5] " = " [:value "[]" 6]
1579+
[:newline] " "
1580+
[:value ":d" 7] " = "
1581+
[:value "[{:a 0, :bb \"000\", :ccc [[]]}\n {:a -1, :bb \"111\", :ccc [1]}\n {:a 2, :bb \"222\", :ccc [1 2]}]" 8]
1582+
[:newline] [:newline]]
1583+
(section "Contents" rendered))
1584+
(is+ ["--- View mode:" [:newline] " :pretty"]
1585+
(section "View mode" rendered)))))
1586+
1587+
(deftest pretty-view-mode-seq-of-maps-test
1588+
(testing "in :pretty view-mode maps seqs of maps are pretty printed"
1589+
(let [rendered (-> (for [i (range 2)]
1590+
{:a (- i)
1591+
:bb (str i i i)
1592+
:ccc (range i 0 -1)
1593+
:d (for [i (range 5)]
1594+
{:a (- i)
1595+
:bb (str i i i)
1596+
:ccc (range i 0 -1)})})
1597+
(inspect/start)
1598+
(inspect/set-view-mode :pretty)
1599+
render)]
1600+
(is+ ["--- Contents:" [:newline]
1601+
" 0. "
1602+
[:value (str "{:a 0,\n :bb \"000\",\n :ccc (),\n "
1603+
":d\n ({:a 0, :bb \"000\", :ccc ()}\n "
1604+
"{:a -1, :bb \"111\", :ccc (1)}\n {:a -2, :bb "
1605+
"\"222\", :ccc (2 1)}\n {:a -3, :bb \"333\", "
1606+
":ccc (3 2 1)}\n {:a -4, :bb \"444\", :ccc "
1607+
"(4 3 2 1)})}") 1]
1608+
[:newline]
1609+
" 1. "
1610+
[:value (str "{:a -1,\n :bb \"111\",\n :ccc (1),\n "
1611+
":d\n ({:a 0, :bb \"000\", :ccc ()}\n "
1612+
"{:a -1, :bb \"111\", :ccc (1)}\n {:a -2, :bb "
1613+
"\"222\", :ccc (2 1)}\n {:a -3, :bb \"333\", "
1614+
":ccc (3 2 1)}\n {:a -4, :bb \"444\", "
1615+
":ccc (4 3 2 1)})}") 2]
1616+
[:newline] [:newline]]
1617+
(section "Contents" rendered))
1618+
(is+ ["--- View mode:" [:newline] " :pretty"]
1619+
(section "View mode" rendered)))))
1620+
1621+
(deftest pretty-view-mode-map-as-key-test
1622+
(testing "in :pretty view-mode maps that contain maps as a keys are pretty printed"
1623+
(let [rendered (-> {{:a 0
1624+
:bb "000"
1625+
:ccc []
1626+
:d [{:a 0 :bb "000" :ccc []}
1627+
{:a -1 :bb "111" :ccc [1]}
1628+
{:a -2 :bb "222" :ccc [2 1]}
1629+
{:a -3 :bb "333" :ccc [3 2 1]}
1630+
{:a -4 :bb "444" :ccc [4 3 2 1]}]}
1631+
{:a -1
1632+
:bb "111"
1633+
:ccc [1]
1634+
:d [{:a 0 :bb "000" :ccc []}
1635+
{:a -1 :bb "111" :ccc [1]}
1636+
{:a -2 :bb "222" :ccc [2 1]}
1637+
{:a -3 :bb "333" :ccc [3 2 1]}
1638+
{:a -4 :bb "444" :ccc [4 3 2 1]}]}}
1639+
(inspect/start)
1640+
(inspect/set-view-mode :pretty)
1641+
render)]
1642+
(is+ ["--- Contents:" [:newline] " "
1643+
[:value (str "{:a 0,\n :bb \"000\",\n :ccc [],\n :d\n "
1644+
"[{:a 0, :bb \"000\", :ccc []}\n {:a -1, "
1645+
":bb \"111\", :ccc [1]}\n {:a -2, :bb \"222\", "
1646+
":ccc [2 1]}\n {:a -3, :bb \"333\", :ccc [3 2 1]}"
1647+
"\n {:a -4, :bb \"444\", :ccc [4 3 2 1]}]}") 1]
1648+
[:newline] " =" [:newline] " "
1649+
[:value (str "{:a -1,\n :bb \"111\",\n :ccc [1],\n "
1650+
":d\n [{:a 0, :bb \"000\", :ccc []}\n "
1651+
"{:a -1, :bb \"111\", :ccc [1]}\n {:a -2, "
1652+
":bb \"222\", :ccc [2 1]}\n {:a -3, :bb "
1653+
"\"333\", :ccc [3 2 1]}\n {:a -4, :bb "
1654+
"\"444\", :ccc [4 3 2 1]}]}") 2]
1655+
[:newline] [:newline] [:newline]]
1656+
(section "Contents" rendered))
1657+
(is+ ["--- View mode:" [:newline] " :pretty"]
1658+
(section "View mode" rendered)))))
1659+
1660+
(deftest pretty-view-mode-seq-of-map-as-key-test
1661+
(testing "in :pretty view-mode maps that contain seq of maps as a keys are pretty printed"
1662+
(let [rendered (-> {[{:a 0
1663+
:bb "000"
1664+
:ccc []
1665+
:d [{:a 0 :bb "000" :ccc [[]]}
1666+
{:a -1 :bb "111" :ccc [1]}
1667+
{:a 2 :bb "222" :ccc [1 2]}]}]
1668+
{:a 0
1669+
:bb "000"
1670+
:ccc []
1671+
:d [{:a 0 :bb "000" :ccc [[]]}
1672+
{:a -1 :bb "111" :ccc [1]}
1673+
{:a 2 :bb "222" :ccc [1 2]}]}}
1674+
(inspect/start)
1675+
(inspect/set-view-mode :pretty)
1676+
render)]
1677+
(is+ ["--- Contents:" [:newline] " "
1678+
[:value (str "[{:a 0,\n :bb \"000\",\n :ccc [],\n :d\n "
1679+
"[{:a 0, :bb \"000\", :ccc [[]]}\n {:a -1, :bb \"111\", "
1680+
":ccc [1]}\n {:a 2, :bb \"222\", :ccc [1 2]}]}]") 1]
1681+
[:newline] " =" [:newline] " "
1682+
[:value (str "{:a 0,\n :bb \"000\",\n :ccc [],\n :d\n "
1683+
"[{:a 0, :bb \"000\", :ccc [[]]}\n {:a -1, "
1684+
":bb \"111\", :ccc [1]}\n {:a 2, :bb \"222\", "
1685+
":ccc [1 2]}]}") 2]
1686+
[:newline] [:newline] [:newline]]
1687+
(section "Contents" rendered))
1688+
(is+ ["--- View mode:" [:newline] " :pretty"]
1689+
(section "View mode" rendered)))))
1690+
15621691
(deftest tap-test
15631692
(testing "tap-current-value"
15641693
(let [proof (atom [])

0 commit comments

Comments
 (0)