Skip to content

Commit 8ecdab4

Browse files
alexander-yakushevbbatsov
authored andcommitted
[inspect] Extract eval-interceptor-transport to be reusable
1 parent d1e09a7 commit 8ecdab4

File tree

2 files changed

+74
-72
lines changed

2 files changed

+74
-72
lines changed

src/cider/nrepl/middleware/inspect.clj

Lines changed: 43 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
(ns cider.nrepl.middleware.inspect
22
(:require
33
[cider.nrepl.middleware.util.cljs :as cljs]
4-
[cider.nrepl.middleware.util.error-handling :refer [base-error-response with-safe-transport]]
5-
[nrepl.middleware.caught :as caught]
4+
[cider.nrepl.middleware.util.error-handling
5+
:refer [eval-interceptor-transport with-safe-transport]]
66
[nrepl.misc :refer [response-for]]
77
[nrepl.transport :as transport]
88
[orchard.info :as info]
99
[orchard.inspect :as inspect]
10-
[orchard.java])
11-
(:import
12-
(nrepl.transport Transport)))
10+
[orchard.java]))
1311

1412
(defn- update-inspector [inspector f & args]
1513
;; Ensure that there is valid inspector value before passing it to
@@ -24,36 +22,43 @@
2422
(alter-meta! update ::inspector #(apply update-inspector % f args))
2523
(get ::inspector)))
2624

25+
(defn- extract-doc-fragments
26+
"If `value` is either a class, a method, or a field, try to calculate doc
27+
fragments from it and return as a map."
28+
[value]
29+
(let [class-sym (when (class? value)
30+
(-> ^Class value .getCanonicalName symbol))
31+
method-sym (when (instance? java.lang.reflect.Method value)
32+
(symbol (str (-> ^java.lang.reflect.Method value .getDeclaringClass .getCanonicalName)
33+
"/"
34+
(-> ^java.lang.reflect.Method value .getName))))
35+
field-sym (when (instance? java.lang.reflect.Field value)
36+
(symbol (str (-> ^java.lang.reflect.Field value .getDeclaringClass .getCanonicalName)
37+
"/"
38+
(-> ^java.lang.reflect.Field value .getName))))
39+
fragments-sym (or method-sym field-sym class-sym)]
40+
(if fragments-sym
41+
(select-keys (info/info 'user fragments-sym)
42+
[:doc-fragments
43+
:doc-first-sentence-fragments
44+
:doc-block-tags-fragments])
45+
{})))
46+
2747
(defn- inspector-response
2848
([msg inspector]
2949
(inspector-response msg inspector {:status :done}))
3050

31-
([msg {:keys [rendered value path]} resp]
32-
(let [class-sym (when (class? value)
33-
(-> ^Class value .getCanonicalName symbol))
34-
method-sym (when (instance? java.lang.reflect.Method value)
35-
(symbol (str (-> ^java.lang.reflect.Method value .getDeclaringClass .getCanonicalName)
36-
"/"
37-
(-> ^java.lang.reflect.Method value .getName))))
38-
field-sym (when (instance? java.lang.reflect.Field value)
39-
(symbol (str (-> ^java.lang.reflect.Field value .getDeclaringClass .getCanonicalName)
40-
"/"
41-
(-> ^java.lang.reflect.Field value .getName))))
42-
fragments-sym (or method-sym field-sym class-sym)]
43-
(response-for msg resp (merge (when fragments-sym
44-
(select-keys (info/info 'user fragments-sym)
45-
[:doc-fragments
46-
:doc-first-sentence-fragments
47-
:doc-block-tags-fragments]))
48-
(binding [*print-length* nil]
49-
{:value (pr-str (seq rendered))
50-
:path (pr-str (seq path))}))))))
51+
([msg {:keys [rendered value path]} extra-response-data]
52+
(let [data (binding [*print-length* nil]
53+
{:value (pr-str (seq rendered))
54+
:path (pr-str (seq path))})]
55+
(response-for msg extra-response-data data (extract-doc-fragments value)))))
5156

5257
(defn- warmup-javadoc-cache [^Class clazz]
5358
(when-let [class-sym (some-> clazz .getCanonicalName symbol)]
5459
;; Don't spawn a `future` for already-computed caches:
5560
(when-not (get orchard.java/cache class-sym)
56-
(future
61+
(future ;; TODO: replace future with controlled threadpool.
5762
;; Warmup the Orchard cache for the class of the currently inspected
5863
;; value. This way, if the user inspects this class next, the underlying
5964
;; inspect request will complete quickly.
@@ -62,21 +67,18 @@
6267
(doseq [^Class interface (.getInterfaces clazz)]
6368
(info/info 'user (-> interface .getCanonicalName symbol)))))))
6469

65-
(defn- update-if-present [m k update-fn & args]
66-
(if (contains? m k)
67-
(apply update m k update-fn args)
68-
m))
69-
7070
(defn- msg->inspector-config [msg]
71-
(let [config (select-keys msg [:page-size :max-atom-length :max-coll-size
72-
:max-value-length :max-nested-depth])]
73-
config))
71+
(select-keys msg [:page-size :max-atom-length :max-coll-size
72+
:max-value-length :max-nested-depth]))
7473

7574
(defn inspect-reply*
7675
[msg value]
7776
(let [config (msg->inspector-config msg)
7877
inspector (swap-inspector! msg #(inspect/start (merge % config) value))]
7978
(warmup-javadoc-cache (class (:value inspector)))
79+
;; By using 3-arity `inspector-response` we ensure that the default
80+
;; `{:status :done}` is not sent with this message, as the underlying
81+
;; eval will send it on its own.
8082
(inspector-response msg inspector {})))
8183

8284
(defn inspect-reply
@@ -85,40 +87,10 @@
8587
(transport/send (:transport msg)
8688
(inspect-reply* msg value))))
8789

88-
(defn inspector-transport
89-
[{:keys [^Transport transport] :as msg}]
90-
(reify Transport
91-
(recv [_this]
92-
(.recv transport))
93-
94-
(recv [_this timeout]
95-
(.recv transport timeout))
96-
97-
(send [this response]
98-
(cond (contains? response :value)
99-
(inspect-reply msg response)
100-
101-
;; If the eval errored, propagate the exception as error in the
102-
;; inspector middleware, so that the client CIDER code properly
103-
;; renders it instead of silently ignoring it.
104-
(and (contains? (:status response) :eval-error)
105-
(contains? response ::caught/throwable))
106-
(let [e (::caught/throwable response)
107-
resp (base-error-response msg e :inspect-eval-error :done)]
108-
(.send transport resp))
109-
110-
:else (.send transport response))
111-
this)))
112-
113-
(defn eval-msg
114-
[{:keys [inspect] :as msg}]
115-
(if inspect
116-
(assoc msg :transport (inspector-transport msg))
117-
msg))
118-
119-
(defn eval-reply
120-
[handler msg]
121-
(handler (eval-msg msg)))
90+
(defn handle-eval-inspect [handler msg]
91+
;; Let eval command be executed but intercept its :value with `inspect-reply`.
92+
(handler (assoc msg :transport (eval-interceptor-transport
93+
msg inspect-reply :inspect-eval-error))))
12294

12395
(defn pop-reply [msg]
12496
(inspector-response msg (swap-inspector! msg inspect/up)))
@@ -167,9 +139,9 @@
167139
(defn tap-indexed [msg]
168140
(inspector-response msg (swap-inspector! msg inspect/tap-indexed (:idx msg))))
169141

170-
(defn handle-inspect [handler msg]
171-
(if (= (:op msg) "eval")
172-
(eval-reply handler msg)
142+
(defn handle-inspect [handler {:keys [op inspect] :as msg}]
143+
(if (and (= op "eval") inspect)
144+
(handle-eval-inspect handler msg)
173145

174146
(with-safe-transport handler msg
175147
"inspect-pop" pop-reply

src/cider/nrepl/middleware/util/error_handling.clj

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
(:require
66
[clojure.set :as set]
77
[clojure.walk :as walk]
8+
[nrepl.middleware.caught :as caught]
89
[nrepl.middleware.print :as print]
910
[nrepl.misc :refer [response-for]]
1011
[nrepl.transport :as transport])
1112
(:import
1213
java.io.InputStream
13-
clojure.lang.RT))
14+
clojure.lang.RT
15+
(nrepl.transport Transport)))
1416

1517
(def ^:private print-cause-trace
1618
(delay
@@ -186,3 +188,31 @@
186188
(try (transport/send transport# (op-handler op-action# msg#))
187189
(catch Exception e# (transport/send transport# (error-handler err-action# msg# e#)))))
188190
(~handler msg#))))
191+
192+
(defn eval-interceptor-transport
193+
"Return a transport that wraps the transport in `msg` and intercepts the
194+
`:value` coming out of it as well as any raised exceptions. Exceptions are
195+
propagated as errors in the inspector middleware (specified by
196+
`error-statuses`), so that the client CIDER code properly renders them instead
197+
of silently ignoring. `reply-fn` is invoked on the message and the `:value`
198+
when it arrives."
199+
[{:keys [^Transport transport] :as msg} reply-fn & error-statuses]
200+
(reify Transport
201+
(recv [_this]
202+
(.recv transport))
203+
204+
(recv [_this timeout]
205+
(.recv transport timeout))
206+
207+
(send [this response]
208+
(cond (contains? response :value)
209+
(reply-fn msg response)
210+
211+
(and (contains? (:status response) :eval-error)
212+
(contains? response ::caught/throwable))
213+
(let [e (::caught/throwable response)
214+
resp (apply base-error-response msg e :done error-statuses)]
215+
(.send transport resp))
216+
217+
:else (.send transport response))
218+
this)))

0 commit comments

Comments
 (0)