Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [#348](https://github.com/clojure-emacs/orchard/pull/348): Inspector: display length of inspected strings.
- [#348](https://github.com/clojure-emacs/orchard/pull/348): Inspector: display class flags.
- [#349](https://github.com/clojure-emacs/orchard/pull/349): Inspector: add ability to sort maps by key.
- [#350](https://github.com/clojure-emacs/orchard/pull/350): Inspector: add diff mode and `orchard.inspect/diff`.

## 0.35.0 (2025-05-28)

Expand Down
72 changes: 58 additions & 14 deletions src/orchard/inspect.clj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
[orchard.print :as print])
(:import
(java.lang.reflect Constructor Field Method Modifier)
(java.util Arrays List Map)))
(java.util Arrays List Map)
(orchard.print Diff DiffColl)))

;;
;; Navigating Inspector State
Expand Down Expand Up @@ -47,6 +48,7 @@
:display-analytics-hint nil
:analytics-size-cutoff 100000
:sort-maps false
:only-diff false
:pretty-print false})

(defn- reset-render-state [inspector]
Expand Down Expand Up @@ -1038,6 +1040,27 @@
(unindent)
(unindent))))

(defmethod inspect DiffColl [{:keys [only-diff] :as inspector} ^DiffColl obj]
(let [val (cond-> (.coll obj)
only-diff print/diff-coll-hide-equal-items)]
(-> inspector
(render-class-name val)
(render-counted-length val)
(render-section-header "Diff contents")
(indent)
(render-value-maybe-expand val)
(unindent))))

(defmethod inspect Diff [inspector ^Diff obj]
(let [d1 (.d1 obj), d2 (.d2 obj)]
(-> inspector
(render-class-name obj)
(render-section-header "Diff")
(indent)
(render-labeled-value " Left" d1)
(render-labeled-value "Right" d2)
(unindent))))

(defn ns-refers-by-ns [^clojure.lang.Namespace ns]
(group-by (fn [^clojure.lang.Var v] (.ns v))
(map val (ns-refers ns))))
Expand Down Expand Up @@ -1086,15 +1109,21 @@
(unindent))
inspector)))

(defn render-view-mode [{:keys [value view-mode pretty-print] :as inspector}]
(defn render-view-mode [{:keys [value view-mode pretty-print sort-maps only-diff] :as inspector}]
(if (some? value)
(let [supported (filter #(view-mode-supported? inspector %) view-mode-order)
add-circle #(if %2 (str "●" %1) %1)
diff? (print/diff-result? value)
view-mode-str (str (->> supported
(map #(add-circle (name %) (= % view-mode)))
(str/join " "))
" " (add-circle "pretty" pretty-print))]
(-> (render-section-header inspector "View mode (press 'v' to cycle, 'P' to pretty-print)")
" " (add-circle "pretty" pretty-print)
" " (add-circle "sort-maps" sort-maps)
(when diff?
(str " " (add-circle "only-diff" only-diff))))
caption (format "View mode (press 'v' to cycle, 'P' to pretty-print, 'S' to sort maps%s)"
(if diff? ", 'D' to show only diffs" ""))]
(-> (render-section-header inspector caption)
(indent)
(render-indent view-mode-str)
(unindent)))
Expand All @@ -1104,10 +1133,12 @@
(seq (persistent! rendered)))

(defn inspect-render
([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value pretty-print]
([{:keys [max-atom-length max-value-length max-coll-size max-nested-depth value
pretty-print only-diff]
:as inspector}]
(binding [print/*max-atom-length* max-atom-length
print/*max-total-length* max-value-length
print/*coll-show-only-diff* (boolean only-diff)
*print-length* max-coll-size
*print-level* (cond-> max-nested-depth
;; In pretty mode a higher *print-level*
Expand Down Expand Up @@ -1141,12 +1172,25 @@
(assoc :view-mode (first (supported-view-modes inspector)))
inspect-render))))

(defn ^:deprecated clear
"If necessary, use `(start inspector nil) instead.`"
[inspector]
(start inspector nil))

(defn ^:deprecated fresh
"If necessary, use `(start nil)` instead."
[]
(start nil))
(defn diff
"Perform a recursive diff on two values and return a structure suitable to be
viewed with the inspector."
[d1 d2]
(cond (= d1 d2) d1
(not= (class d1) (class d2)) (print/->Diff d1 d2)

(and (sequential? d1) (sequential? d2))
(let [n (max (count d1) (count d2))]
(->> (mapv #(diff (nth d1 % print/nothing) (nth d2 % print/nothing))
(range n))
print/->DiffColl))

(and (map? d1) (map? d2))
(print/->DiffColl
(->> (concat (keys d1) (keys d2))
distinct
(mapv (fn [k]
[k (diff (get d1 k print/nothing) (get d2 k print/nothing))]))
(into {})))

:else (print/->Diff d1 d2)))
19 changes: 16 additions & 3 deletions src/orchard/pp.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
(:require [clojure.string :as str]
[orchard.print :as print])
(:import (mx.cider.orchard TruncatingStringWriter
TruncatingStringWriter$TotalLimitExceeded)))
TruncatingStringWriter$TotalLimitExceeded)
(orchard.print DiffColl)))

(defn ^:private strip-ns
"Given a (presumably qualified) ident, return an unqualified version
Expand Down Expand Up @@ -317,10 +318,18 @@
(do
(write writer reader-macro)
(-pprint (second this) writer
(update opts :indentation
(fn [indentation] (str indentation " "))))))
(update opts :indentation str " "))))
(-pprint-coll this writer opts)))

(defn ^:private -pprint-diff-coll
[^DiffColl this writer opts]
(if (meets-print-level? (:level opts))
(write writer "#")
(let [coll (cond-> (.coll this)
print/*coll-show-only-diff* print/diff-coll-hide-equal-items)]
(write writer "#≠")
(-pprint coll writer (update opts :indentation str " ")))))

(extend-protocol PrettyPrintable
nil
(-pprint [_ writer _]
Expand Down Expand Up @@ -350,6 +359,10 @@
(-pprint [this writer opts]
(-pprint-coll (or (seq this) ()) writer opts))

DiffColl
(-pprint [this writer opts]
(-pprint-diff-coll this writer opts))

Object
(-pprint [this writer opts]
(if (array? this)
Expand Down
39 changes: 39 additions & 0 deletions src/orchard/print.clj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"Maximum total size of the resulting string."
Integer/MAX_VALUE)

(def ^:dynamic *coll-show-only-diff*
"When displaying collection diffs, whether to hide matching values."
false)

(defn- print-coll-item
"Print an item in the context of a collection. When printing a map, don't print
`[]` characters around map entries."
Expand Down Expand Up @@ -218,6 +222,41 @@
(print (str first-frame) w))
(.write w "]"))

;;;; Diffing support. Used for orchard.inspect/diff.

(deftype Diff [d1 d2])
(deftype DiffColl [coll]) ;; For collections that contain diff elements.
(deftype Nothing []) ;; To represent absent value.
(def nothing (->Nothing))

(defn diff-result?
"Return true if the object represents a diff result."
[x]
(or (instance? Diff x) (instance? DiffColl x)))

(defn diff-coll-hide-equal-items [coll]
(cond (map? coll) (into {} (filter (fn [[_ v]] (diff-result? v))
coll))
(sequential? coll) (mapv #(if (diff-result? %) % nothing)
coll)
:else coll))

(defmethod print DiffColl [^DiffColl x, ^Writer w]
(let [coll (cond-> (.coll x)
*coll-show-only-diff* diff-coll-hide-equal-items)]
(.write w "#≠")
(print coll w)))

(defmethod print Diff [^Diff x, ^Writer w]
(let [d1 (.d1 x), d2 (.d2 x)]
(.write w "#±[")
(print d1 w)
(.write w " ~~ ")
(print d2 w)
(.write w "]")))

(defmethod print Nothing [_ _])

(defmethod print :default [^Object x, ^Writer w]
(print-method x w))

Expand Down
Loading