|
1 | 1 | (ns cider.nrepl.middleware.inspect
|
2 | 2 | (:require
|
3 | 3 | [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]] |
6 | 6 | [nrepl.misc :refer [response-for]]
|
7 | 7 | [nrepl.transport :as transport]
|
8 | 8 | [orchard.info :as info]
|
9 | 9 | [orchard.inspect :as inspect]
|
10 |
| - [orchard.java]) |
11 |
| - (:import |
12 |
| - (nrepl.transport Transport))) |
| 10 | + [orchard.java])) |
13 | 11 |
|
14 | 12 | (defn- update-inspector [inspector f & args]
|
15 | 13 | ;; Ensure that there is valid inspector value before passing it to
|
|
24 | 22 | (alter-meta! update ::inspector #(apply update-inspector % f args))
|
25 | 23 | (get ::inspector)))
|
26 | 24 |
|
| 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 | + |
27 | 47 | (defn- inspector-response
|
28 | 48 | ([msg inspector]
|
29 | 49 | (inspector-response msg inspector {:status :done}))
|
30 | 50 |
|
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))))) |
51 | 56 |
|
52 | 57 | (defn- warmup-javadoc-cache [^Class clazz]
|
53 | 58 | (when-let [class-sym (some-> clazz .getCanonicalName symbol)]
|
54 | 59 | ;; Don't spawn a `future` for already-computed caches:
|
55 | 60 | (when-not (get orchard.java/cache class-sym)
|
56 |
| - (future |
| 61 | + (future ;; TODO: replace future with controlled threadpool. |
57 | 62 | ;; Warmup the Orchard cache for the class of the currently inspected
|
58 | 63 | ;; value. This way, if the user inspects this class next, the underlying
|
59 | 64 | ;; inspect request will complete quickly.
|
|
62 | 67 | (doseq [^Class interface (.getInterfaces clazz)]
|
63 | 68 | (info/info 'user (-> interface .getCanonicalName symbol)))))))
|
64 | 69 |
|
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 |
| - |
70 | 70 | (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])) |
74 | 73 |
|
75 | 74 | (defn inspect-reply*
|
76 | 75 | [msg value]
|
77 | 76 | (let [config (msg->inspector-config msg)
|
78 | 77 | inspector (swap-inspector! msg #(inspect/start (merge % config) value))]
|
79 | 78 | (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. |
80 | 82 | (inspector-response msg inspector {})))
|
81 | 83 |
|
82 | 84 | (defn inspect-reply
|
|
85 | 87 | (transport/send (:transport msg)
|
86 | 88 | (inspect-reply* msg value))))
|
87 | 89 |
|
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)))) |
122 | 94 |
|
123 | 95 | (defn pop-reply [msg]
|
124 | 96 | (inspector-response msg (swap-inspector! msg inspect/up)))
|
|
167 | 139 | (defn tap-indexed [msg]
|
168 | 140 | (inspector-response msg (swap-inspector! msg inspect/tap-indexed (:idx msg))))
|
169 | 141 |
|
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) |
173 | 145 |
|
174 | 146 | (with-safe-transport handler msg
|
175 | 147 | "inspect-pop" pop-reply
|
|
0 commit comments