Skip to content

Commit 46fa804

Browse files
[inspect] Add diff mode
1 parent ad6cbb7 commit 46fa804

File tree

5 files changed

+249
-41
lines changed

5 files changed

+249
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- [#348](https://github.com/clojure-emacs/orchard/pull/348): Inspector: display length of inspected strings.
77
- [#348](https://github.com/clojure-emacs/orchard/pull/348): Inspector: display class flags.
88
- [#349](https://github.com/clojure-emacs/orchard/pull/349): Inspector: add ability to sort maps by key.
9+
- [#350](https://github.com/clojure-emacs/orchard/pull/350): Inspector: add diff mode and `orchard.inspect/diff`.
910

1011
## 0.35.0 (2025-05-28)
1112

src/orchard/inspect.clj

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
[orchard.print :as print])
1919
(:import
2020
(java.lang.reflect Constructor Field Method Modifier)
21-
(java.util Arrays List Map)))
21+
(java.util Arrays List Map)
22+
(orchard.print Diff DiffColl)))
2223

2324
;;
2425
;; Navigating Inspector State
@@ -47,6 +48,7 @@
4748
:display-analytics-hint nil
4849
:analytics-size-cutoff 100000
4950
:sort-maps false
51+
:only-diff false
5052
:pretty-print false})
5153

5254
(defn- reset-render-state [inspector]
@@ -1038,6 +1040,27 @@
10381040
(unindent)
10391041
(unindent))))
10401042

1043+
(defmethod inspect DiffColl [{:keys [only-diff] :as inspector} ^DiffColl obj]
1044+
(let [val (cond-> (.coll obj)
1045+
only-diff print/diff-coll-hide-equal-items)]
1046+
(-> inspector
1047+
(render-class-name val)
1048+
(render-counted-length val)
1049+
(render-section-header "Diff contents")
1050+
(indent)
1051+
(render-value-maybe-expand val)
1052+
(unindent))))
1053+
1054+
(defmethod inspect Diff [inspector ^Diff obj]
1055+
(let [d1 (.d1 obj), d2 (.d2 obj)]
1056+
(-> inspector
1057+
(render-class-name obj)
1058+
(render-section-header "Diff")
1059+
(indent)
1060+
(render-labeled-value " Left" d1)
1061+
(render-labeled-value "Right" d2)
1062+
(unindent))))
1063+
10411064
(defn ns-refers-by-ns [^clojure.lang.Namespace ns]
10421065
(group-by (fn [^clojure.lang.Var v] (.ns v))
10431066
(map val (ns-refers ns))))
@@ -1086,15 +1109,20 @@
10861109
(unindent))
10871110
inspector)))
10881111

1089-
(defn render-view-mode [{:keys [value view-mode pretty-print] :as inspector}]
1112+
(defn render-view-mode [{:keys [value view-mode pretty-print only-diff] :as inspector}]
10901113
(if (some? value)
10911114
(let [supported (filter #(view-mode-supported? inspector %) view-mode-order)
10921115
add-circle #(if %2 (str "" %1) %1)
1116+
diff? (print/diff-result? value)
10931117
view-mode-str (str (->> supported
10941118
(map #(add-circle (name %) (= % view-mode)))
10951119
(str/join " "))
1096-
" " (add-circle "pretty" pretty-print))]
1097-
(-> (render-section-header inspector "View mode (press 'v' to cycle, 'P' to pretty-print)")
1120+
" " (add-circle "pretty" pretty-print)
1121+
(when diff?
1122+
(str " " (add-circle "only-diff" only-diff))))
1123+
caption (format "View mode (press 'v' to cycle, 'P' to pretty-print%s)"
1124+
(if diff? ", 'D' to show only diffs" ""))]
1125+
(-> (render-section-header inspector caption)
10981126
(indent)
10991127
(render-indent view-mode-str)
11001128
(unindent)))
@@ -1104,10 +1132,12 @@
11041132
(seq (persistent! rendered)))
11051133

11061134
(defn inspect-render
1107-
([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value pretty-print]
1135+
([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value
1136+
pretty-print only-diff]
11081137
:as inspector}]
11091138
(binding [print/*max-atom-length* max-atom-length
11101139
print/*max-total-length* max-value-length
1140+
print/*coll-show-only-diff* (boolean only-diff)
11111141
*print-length* max-coll-size
11121142
*print-level* (cond-> max-nested-depth
11131143
;; In pretty mode a higher *print-level*
@@ -1141,12 +1171,25 @@
11411171
(assoc :view-mode (first (supported-view-modes inspector)))
11421172
inspect-render))))
11431173

1144-
(defn ^:deprecated clear
1145-
"If necessary, use `(start inspector nil) instead.`"
1146-
[inspector]
1147-
(start inspector nil))
1148-
1149-
(defn ^:deprecated fresh
1150-
"If necessary, use `(start nil)` instead."
1151-
[]
1152-
(start nil))
1174+
(defn diff
1175+
"Perform a recursive diff on two values and return a structure suitable to be
1176+
viewed with the inspector."
1177+
[d1 d2]
1178+
(cond (= d1 d2) d1
1179+
(not= (class d1) (class d2)) (print/->Diff d1 d2)
1180+
1181+
(and (sequential? d1) (sequential? d2))
1182+
(let [n (max (count d1) (count d2))]
1183+
(->> (mapv #(diff (nth d1 % print/nothing) (nth d2 % print/nothing))
1184+
(range n))
1185+
print/->DiffColl))
1186+
1187+
(and (map? d1) (map? d2))
1188+
(print/->DiffColl
1189+
(->> (concat (keys d1) (keys d2))
1190+
distinct
1191+
(mapv (fn [k]
1192+
[k (diff (get d1 k print/nothing) (get d2 k print/nothing))]))
1193+
(into {})))
1194+
1195+
:else (print/->Diff d1 d2)))

src/orchard/pp.clj

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
(:require [clojure.string :as str]
1515
[orchard.print :as print])
1616
(:import (mx.cider.orchard TruncatingStringWriter
17-
TruncatingStringWriter$TotalLimitExceeded)))
17+
TruncatingStringWriter$TotalLimitExceeded)
18+
(orchard.print DiffColl)))
1819

1920
(defn ^:private strip-ns
2021
"Given a (presumably qualified) ident, return an unqualified version
@@ -317,10 +318,18 @@
317318
(do
318319
(write writer reader-macro)
319320
(-pprint (second this) writer
320-
(update opts :indentation
321-
(fn [indentation] (str indentation " "))))))
321+
(update opts :indentation str " "))))
322322
(-pprint-coll this writer opts)))
323323

324+
(defn ^:private -pprint-diff-coll
325+
[^DiffColl this writer opts]
326+
(if (meets-print-level? (:level opts))
327+
(write writer "#")
328+
(let [coll (cond-> (.coll this)
329+
print/*coll-show-only-diff* print/diff-coll-hide-equal-items)]
330+
(write writer "#≠")
331+
(-pprint coll writer (update opts :indentation str " ")))))
332+
324333
(extend-protocol PrettyPrintable
325334
nil
326335
(-pprint [_ writer _]
@@ -350,6 +359,10 @@
350359
(-pprint [this writer opts]
351360
(-pprint-coll (or (seq this) ()) writer opts))
352361

362+
DiffColl
363+
(-pprint [this writer opts]
364+
(-pprint-diff-coll this writer opts))
365+
353366
Object
354367
(-pprint [this writer opts]
355368
(if (array? this)

src/orchard/print.clj

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
"Maximum total size of the resulting string."
4949
Integer/MAX_VALUE)
5050

51+
(def ^:dynamic *coll-show-only-diff*
52+
"When displaying collection diffs, whether to hide matching values."
53+
false)
54+
5155
(defn- print-coll-item
5256
"Print an item in the context of a collection. When printing a map, don't print
5357
`[]` characters around map entries."
@@ -218,6 +222,41 @@
218222
(print (str first-frame) w))
219223
(.write w "]"))
220224

225+
;;;; Diffing support. Used for orchard.inspect/diff.
226+
227+
(deftype Diff [d1 d2])
228+
(deftype DiffColl [coll]) ;; For collections that contain diff elements.
229+
(deftype Nothing []) ;; To represent absent value.
230+
(def nothing (->Nothing))
231+
232+
(defn diff-result?
233+
"Return true if the object represents a diff result."
234+
[x]
235+
(or (instance? Diff x) (instance? DiffColl x)))
236+
237+
(defn diff-coll-hide-equal-items [coll]
238+
(cond (map? coll) (into {} (filter (fn [[_ v]] (diff-result? v))
239+
coll))
240+
(sequential? coll) (mapv #(if (diff-result? %) % nothing)
241+
coll)
242+
:else coll))
243+
244+
(defmethod print DiffColl [^DiffColl x, ^Writer w]
245+
(let [coll (cond-> (.coll x)
246+
*coll-show-only-diff* diff-coll-hide-equal-items)]
247+
(.write w "#≠")
248+
(print coll w)))
249+
250+
(defmethod print Diff [^Diff x, ^Writer w]
251+
(let [d1 (.d1 x), d2 (.d2 x)]
252+
(.write w "#±[")
253+
(print d1 w)
254+
(.write w " ~~ ")
255+
(print d2 w)
256+
(.write w "]")))
257+
258+
(defmethod print Nothing [_ _])
259+
221260
(defmethod print :default [^Object x, ^Writer w]
222261
(print-method x w))
223262

0 commit comments

Comments
 (0)