Skip to content

Commit 60c17ba

Browse files
committed
Introduce inspect-last-exception op
Part of clojure-emacs/cider#3565
1 parent 2a3e6f0 commit 60c17ba

File tree

6 files changed

+83
-11
lines changed

6 files changed

+83
-11
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## master (unreleased)
44

5+
### New features
6+
7+
* [cider#3565](https://github.com/clojure-emacs/cider/issues/3565): Add new [`inspect-last-exception`](https://docs.cider.mx/cider-nrepl/nrepl-api/ops.html#inspect-last-exception) op.
8+
59
### Changes
610

711
* [#828](https://github.com/clojure-emacs/cider-nrepl/issues/828): Warmup Orchard caches for exceptions in advance.

doc/modules/ROOT/pages/nrepl-api/ops.adoc

+18-2
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ Optional parameters::
312312
* `:class` A Java class. If ``:ns`` is passed, it will be used for fully-qualifiying the class, if necessary.
313313
* `:context` A Compliment completion context, just like the ones already passed for the "complete" op,
314314
with the difference that the symbol at point should be entirely replaced by "\__prefix__".
315-
316315
For Java interop queries, it helps inferring the precise type of the object the ``:sym`` or ``:member`` refers to,
317316
making the results more accurate (and less numerous).
318317
* `:member` A Java class member.
@@ -447,7 +446,6 @@ Optional parameters::
447446
* `:class` A Java class. If ``:ns`` is passed, it will be used for fully-qualifiying the class, if necessary.
448447
* `:context` A Compliment completion context, just like the ones already passed for the "complete" op,
449448
with the difference that the symbol at point should be entirely replaced by "\__prefix__".
450-
451449
For Java interop queries, it helps inferring the precise type of the object the ``:sym`` or ``:member`` refers to,
452450
making the results more accurate (and less numerous).
453451
* `:member` A Java class member.
@@ -528,6 +526,24 @@ Returns::
528526

529527

530528

529+
=== `inspect-last-exception`
530+
531+
Returns an Inspector response for the last exception that has been processed through ``analyze-last-stacktrace`` for the current nrepl session.
532+
Assumes that ``analyze-last-stacktrace`` has been called first, returning "no-error" otherwise.
533+
534+
Required parameters::
535+
* `:index` 0 for inspecting the top-level exception, 1 for its ex-cause, 2 for its ex-cause's ex-cause, and so on.
536+
537+
538+
Optional parameters::
539+
{blank}
540+
541+
Returns::
542+
* `:status` "done", or "no-error" if ``analyze-last-stacktrace`` wasn't called beforehand (or the ``index`` was out of bounds).
543+
* `:value` A value, as produced by the Inspector middleware.
544+
545+
546+
531547
=== `inspect-next-page`
532548

533549
Jumps to the next page in paginated collection view.

src/cider/nrepl.clj

+5
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,11 @@ if applicable, and re-render the updated value."
627627
:handles {"analyze-last-stacktrace" {:doc "Return messages describing each cause and stack frame of the most recent exception."
628628
:optional wrap-print-optional-arguments
629629
:returns {"status" "\"done\", or \"no-error\" if `*e` is nil"}}
630+
"inspect-last-exception" {:doc "Returns an Inspector response for the last exception that has been processed through `analyze-last-stacktrace` for the current nrepl session.
631+
Assumes that `analyze-last-stacktrace` has been called first, returning \"no-error\" otherwise."
632+
:requires {"index" "0 for inspecting the top-level exception, 1 for its ex-cause, 2 for its ex-cause's ex-cause, and so on."}
633+
:returns {"status" "\"done\", or \"no-error\" if `analyze-last-stacktrace` wasn't called beforehand (or the `index` was out of bounds)."
634+
"value" "A value, as produced by the Inspector middleware."}}
630635
"analyze-stacktrace" {:doc "Parse and analyze the `:stacktrace`
631636
parameter and return messages describing each cause and stack frame. The
632637
stacktrace must be a string formatted in one of the following formats:

src/cider/nrepl/middleware/inspect.clj

+10-5
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,21 @@
2424
(pr-str rendered))]
2525
(response-for msg resp {:value value}))))
2626

27-
(defn inspect-reply
28-
[{:keys [page-size transport max-atom-length max-coll-size] :as msg} eval-response]
29-
(let [value (cljs/response-value msg eval-response)
30-
page-size (or page-size 32)
27+
(defn inspect-reply*
28+
[{:keys [page-size max-atom-length max-coll-size] :as msg} value]
29+
(let [page-size (or page-size 32)
3130
inspector (swap-inspector! msg #(-> %
3231
(assoc :page-size page-size
3332
:max-atom-length max-atom-length
3433
:max-coll-size max-coll-size)
3534
(inspect/start value)))]
36-
(transport/send transport (inspector-response msg inspector {}))))
35+
(inspector-response msg inspector {})))
36+
37+
(defn inspect-reply
38+
[msg eval-response]
39+
(let [value (cljs/response-value msg eval-response)]
40+
(transport/send (:transport msg)
41+
(inspect-reply* msg value))))
3742

3843
(defn inspector-transport
3944
[{:keys [^Transport transport] :as msg}]

src/cider/nrepl/middleware/stacktrace.clj

+25-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[haystack.analyzer :as analyzer]
88
[haystack.parser :as parser]
99
[nrepl.middleware.print :as print]
10+
[cider.nrepl.middleware.inspect :as middleware.inspect]
1011
[nrepl.misc :refer [response-for]]
1112
[nrepl.transport :as t]))
1213

@@ -28,10 +29,14 @@
2829

2930
;; Analyze the last stacktrace
3031

32+
(def ^:dynamic *last-exception* nil)
33+
3134
(defn- analyze-last-stacktrace
3235
"Analyze the last exception."
3336
[{:keys [session ::print/print-fn] :as msg}]
34-
(send-analysis msg (analyzer/analyze (@session #'*e) print-fn)))
37+
(let [last-exception (@session #'*e)]
38+
(swap! session assoc #'*last-exception* last-exception) ;; note that *e can change later, so this entry isn't redundant
39+
(send-analysis msg (analyzer/analyze last-exception print-fn))))
3540

3641
(defn- handle-analyze-last-stacktrace-op
3742
"Handle the analyze last stacktrace op."
@@ -66,10 +71,27 @@
6671
(handle-analyze-last-stacktrace-op msg)
6772
(notify-client msg "The `stacktrace` op is deprecated, please use `analyze-last-stacktrace` instead." :warning))
6873

74+
(defn handle-inspect-last-exception-op [{:keys [index transport] :as msg}]
75+
(let [le (when index
76+
(some-> msg :session deref (get #'*last-exception*)))
77+
cause (when le
78+
(loop [n 0
79+
cause le]
80+
(if (= n index)
81+
cause
82+
(when cause
83+
(recur (inc n)
84+
(ex-cause cause))))))]
85+
(if cause
86+
(t/send transport (middleware.inspect/inspect-reply* msg cause))
87+
(no-error msg))
88+
(done msg)))
89+
6990
(defn handle-stacktrace
7091
"Handle stacktrace ops."
7192
[_ {:keys [op] :as msg}]
7293
(case op
7394
"analyze-last-stacktrace" (handle-analyze-last-stacktrace-op msg)
74-
"analyze-stacktrace" (handle-analyze-stacktrace-op msg)
75-
"stacktrace" (handle-stacktrace-op msg)))
95+
"inspect-last-exception" (handle-inspect-last-exception-op msg)
96+
"analyze-stacktrace" (handle-analyze-stacktrace-op msg)
97+
"stacktrace" (handle-stacktrace-op msg)))

test/clj/cider/nrepl/middleware/stacktrace_test.clj

+21-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,27 @@
3636
(testing "returns the exception message"
3737
(is (= "Don't know how to create ISeq from: java.lang.Long" (:message response))))
3838
(testing "returns done status"
39-
(is (= #{"done"} (:status response)))))))
39+
(is (= #{"done"} (:status response)))))
40+
41+
(testing "`inspect-last-exception` op"
42+
(testing "Index 0 for an exception without extra causes"
43+
(let [{[^String first-value] :value} (session/message {:op "inspect-last-exception" :index 0})]
44+
(is (.startsWith first-value "(\"Class\" \": \" (:value")
45+
"Returns an Inspector response")))
46+
(testing "Index out of bounds"
47+
(is (nil? (:value (session/message {:op "inspect-last-exception" :index 1})))))
48+
(testing "Indices 0, 1, 2 for an exception with one extra cause"
49+
(session/message {:op "eval" :code "(deref (future (/ 2 0)))"})
50+
(session/message {:op "analyze-last-stacktrace"})
51+
(is (seq (:value (session/message {:op "inspect-last-exception" :index 0}))))
52+
(let [{[^String first-value] :value} (session/message {:op "inspect-last-exception" :index 0})]
53+
(is (.startsWith first-value "(\"Class\" \": \" (:value \"java.util.concurrent.ExecutionException")
54+
"Returns an Inspector response for index 0"))
55+
(let [{[^String first-value] :value} (session/message {:op "inspect-last-exception" :index 1})]
56+
(is (.startsWith first-value "(\"Class\" \": \" (:value \"java.lang.ArithmeticException")
57+
"Returns a different Inspector response for index 1"))
58+
(is (nil? (:value (session/message {:op "inspect-last-exception" :index 2})))
59+
"Doesn't return a value past the last index")))))
4060

4161
(deftest analyze-last-stacktrace-unbound-test
4262
(testing "stacktrace op with most recent exception unbound"

0 commit comments

Comments
 (0)