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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## master (unreleased)

- [#342](https://github.com/clojure-emacs/orchard/pull/342): Inspector: add hexdump view mode.

## 0.34.3 (2025-04-28)

- Inspector: fix multiple frequencies not shown for the same value in analytics.
Expand Down
114 changes: 78 additions & 36 deletions src/orchard/inspect.clj
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
(list 'get key)))
(conj path '<unknown>)))

(def ^:private supported-view-modes #{:normal :object :table})
(def ^:private supported-view-modes #{:normal :object :table :hex})

(def ^:private default-inspector-config
"Default configuration values for the inspector."
Expand Down Expand Up @@ -121,8 +121,11 @@
(defn- decide-if-paginated
"Make early decision if the inspected object should be paginated. If so,
assoc the `:chunk` to be displayed to `inspector`."
[{:keys [value current-page page-size] :as inspector}]
(let [pageable? (boolean (#{:list :map :set :array} (object-type value)))]
[{:keys [value current-page page-size view-mode] :as inspector}]
(let [pageable? (boolean (#{:list :map :set :array} (object-type value)))
page-size (if (= view-mode :hex)
(* page-size 16) ;; In hex view mode, each row is 16 bytes.
page-size)]
(cond-> (assoc inspector :pageable pageable?)
pageable? (merge (pagination-info value page-size current-page)))))

Expand Down Expand Up @@ -327,6 +330,13 @@
(seq values)
(render-onto values))))

(defn- render-indent-ln [inspector & values]
(let [padding (padding inspector)]
(cond-> inspector
padding (render padding)
(seq values) (render-onto values)
true (render '(:newline)))))

(defn- render-section-header [inspector section]
(-> (render-ln inspector)
(render (format "%s--- %s:" (or (padding inspector) "") (name section)))
Expand Down Expand Up @@ -370,9 +380,7 @@

(defn- render-counted-length [inspector obj]
(if-let [clength (counted-length obj)]
(-> inspector
(render-indent "Count: " (str clength))
(render-ln))
(render-indent-ln inspector "Count: " (str clength))
inspector))

(defn- long-map-key?
Expand All @@ -387,9 +395,9 @@
be rendered on separate lines."
[{:keys [pretty-print] :as inspector} long-key?]
(if (and pretty-print long-key?)
(-> (render-ln inspector)
(render-indent "=")
(render-ln))
(-> inspector
(render-ln)
(render-indent-ln "="))
(render inspector " = ")))

(defn- render-map-value
Expand Down Expand Up @@ -471,8 +479,7 @@
(as-> inspector ins
(render-ln ins)
(render-row ins pr-ks)
(render-indent ins)
(render-ln ins divider)
(render-indent-ln ins divider)
(reduce render-row ins pr-rows))))

(defn- render-indexed-chunk
Expand Down Expand Up @@ -506,12 +513,11 @@
(if last-page
(-> (render-section-header inspector "Page Info")
(indent)
(render-indent (format "Page size: %d, showing page: %d of %s"
page-size (inc current-page)
(if (= last-page Integer/MAX_VALUE)
"?" (inc last-page))))
(unindent)
(render-ln))
(render-indent-ln (format "Page size: %d, showing page: %d of %s"
page-size (inc current-page)
(if (= last-page Integer/MAX_VALUE)
"?" (inc last-page))))
(unindent))
inspector))

(defn- render-items [inspector items map? start-idx mark-values?]
Expand All @@ -531,17 +537,13 @@

(defn- render-leading-page-ellipsis [{:keys [current-page] :as inspector}]
(if (> current-page 0)
(-> inspector
(render-indent "...")
(render-ln))
(render-indent-ln inspector "...")
inspector))

(defn- render-trailing-page-ellipsis
[{:keys [current-page last-page] :as inspector}]
(if (some-> last-page (> current-page))
(-> inspector
(render-indent "...")
(render-ln))
(render-indent-ln inspector "...")
inspector))

(defn- render-collection-paged
Expand Down Expand Up @@ -572,9 +574,8 @@
(indent ins)
(if value-analysis
(render-value-maybe-expand ins value-analysis)
(-> ins
(render-indent)
(render-ln "Press 'y' or M-x cider-inspector-display-analytics to analyze this value.")))
(render-indent-ln
ins "Press 'y' or M-x cider-inspector-display-analytics to analyze this value."))
(unindent ins))
inspector))

Expand Down Expand Up @@ -655,6 +656,45 @@
(unindent ins))
inspector))

;; Hex view mode

(defn- byte->ascii [b]
(let [c (bit-and b 0xFF)]
(if (and (>= c 32) (<= c 126))
(char c)
;; Use MIDDLE DOT for non-printed chars as it is distinct from 0x2E.
\·)))

(defn- format-hex-row
"Format 16 bytes as hex values."
[bytes]
(let [hex-strs (mapv #(format "%02x" (bit-and % 0xFF)) bytes)
padded (concat hex-strs (repeat (- 16 (count bytes)) " "))
[left-half right-half] (split-at 8 padded)]
(str (str/join " " left-half) " " (str/join " " right-half))))

(defn format-ascii-row
"Format 16 bytes as ASCII characters."
[bytes]
(str/join (map byte->ascii bytes)))

(defn render-hexdump
"Render the current array or array chunk as a hexdump-style table."
[{:keys [value chunk start-idx] :as inspector}]
(let [start-idx (or start-idx 0)
lines (eduction (comp (partition-all 16)
(map-indexed vector))
(or chunk value))]
(as-> inspector ins
(render-leading-page-ellipsis ins)
(reduce (fn [ins [i line]]
(let [addr (+ (* i 16) start-idx)]
(render-indent-ln
ins (format "0x%08x │ %s │ %s" addr (format-hex-row line)
(format-ascii-row line)))))
ins lines)
(render-trailing-page-ellipsis ins))))

;; Inspector multimethod
(defn- dispatch-inspect [{:keys [view-mode] :as _ins} obj]
(if (= view-mode :object)
Expand Down Expand Up @@ -685,16 +725,18 @@
(defmethod inspect :map [inspector obj] (inspect-coll inspector obj))

(defmethod inspect :array [inspector obj]
(-> (render-class-name inspector obj)
(render-counted-length obj)
(render-labeled-value "Component Type" (.getComponentType (class obj)))
(render-analytics)
(render-section-header "Contents")
(indent)
(render-collection-paged)
(unindent)
(render-datafy)
(render-page-info)))
(as-> (render-class-name inspector obj) ins
(render-counted-length ins obj)
(render-labeled-value ins "Component Type" (.getComponentType (class obj)))
(render-analytics ins)
(render-section-header ins "Contents")
(indent ins)
(if (= (:view-mode inspector) :hex)
(render-hexdump ins)
(render-collection-paged ins))
(unindent ins)
(render-datafy ins)
(render-page-info ins)))

(defn- render-var-value [inspector ^clojure.lang.Var obj]
(if-not (.isBound obj)
Expand Down
Loading