From 229ea0e75b859fb33c8b71276112a625b941f734 Mon Sep 17 00:00:00 2001 From: Oleksandr Yakushev Date: Wed, 9 Apr 2025 19:47:45 +0300 Subject: [PATCH] [stacktrace] Add support for directly inspecting ex-data --- CHANGELOG.md | 1 + doc/modules/ROOT/pages/nrepl-api/ops.adoc | 3 ++- src/cider/nrepl.clj | 1 + src/cider/nrepl/middleware/stacktrace.clj | 28 +++++++++++++++-------- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0663666f..bc06e9ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Bump `orchard` to [0.33.0](https://github.com/clojure-emacs/orchard/blob/master/CHANGELOG.md#0330-2025-04-08). * [#929](https://github.com/clojure-emacs/cider-nrepl/pull/929): Migrate profiling middleware to orchard.profile. +* [#930](https://github.com/clojure-emacs/cider-nrepl/pull/930): Stacktrace: add support for directly inspecting ex-data ## 0.54.0 (2025-04-05) diff --git a/doc/modules/ROOT/pages/nrepl-api/ops.adoc b/doc/modules/ROOT/pages/nrepl-api/ops.adoc index a4e220ad1..7fdc2da7b 100644 --- a/doc/modules/ROOT/pages/nrepl-api/ops.adoc +++ b/doc/modules/ROOT/pages/nrepl-api/ops.adoc @@ -475,7 +475,8 @@ Required parameters:: Optional parameters:: -{blank} +* `:ex-data` When equal to "true", inspect ex-data of the exception instead of full exception. + Returns:: * `:status` "done", or "no-error" if ``analyze-last-stacktrace`` wasn't called beforehand (or the ``index`` was out of bounds). diff --git a/src/cider/nrepl.clj b/src/cider/nrepl.clj index 3fd9c65ff..8ac05afc5 100644 --- a/src/cider/nrepl.clj +++ b/src/cider/nrepl.clj @@ -686,6 +686,7 @@ those configured directories will be honored." "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. Assumes that `analyze-last-stacktrace` has been called first, returning \"no-error\" otherwise." :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."} + :optional {"ex-data" "When equal to \"true\", inspect ex-data of the exception instead of full exception."} :returns {"status" "\"done\", or \"no-error\" if `analyze-last-stacktrace` wasn't called beforehand (or the `index` was out of bounds)." "value" "A value, as produced by the Inspector middleware."}} "stacktrace" {:doc "Return messages describing each cause and diff --git a/src/cider/nrepl/middleware/stacktrace.clj b/src/cider/nrepl/middleware/stacktrace.clj index 34d1bf3e0..d2341b609 100644 --- a/src/cider/nrepl/middleware/stacktrace.clj +++ b/src/cider/nrepl/middleware/stacktrace.clj @@ -17,13 +17,13 @@ ;; Analyze the last stacktrace -(def ^:dynamic *last-exception* nil) - (defn- analyze-last-stacktrace "Analyze the last exception." [{:keys [session] :as msg}] (let [last-exception (@session #'*e)] - (swap! session assoc #'*last-exception* last-exception) ;; note that *e can change later, so this entry isn't redundant + ;; We need to remember the analyzed exception separately because we need to + ;; provide a way to inspect it while *e can change. + (alter-meta! session assoc ::analyzed-exception last-exception) (send-analysis msg (stacktrace/analyze last-exception)))) (defn- handle-analyze-last-stacktrace-op @@ -42,14 +42,22 @@ (handle-analyze-last-stacktrace-op msg) (notify-client msg "The `stacktrace` op is deprecated, please use `analyze-last-stacktrace` instead." :warning)) +(defn- get-last-exception-cause [{:keys [session index] :as msg}] + (when index + (let [last-exception (::analyzed-exception (meta session)) + causes (when last-exception + (->> (iterate #(.getCause ^Throwable %) last-exception) + (take-while some?)))] + (nth causes index nil)))) + (defn handle-inspect-last-exception-op [{:keys [session index transport] :as msg}] - (let [last-exception (@session #'*last-exception*) - causes (->> (iterate #(.getCause ^Throwable %) last-exception) - (take-while some?)) - cause (when index - (nth causes index nil))] - (if cause - (t/send transport (middleware.inspect/inspect-reply* msg cause)) + (let [inspect-ex-data? (= (:ex-data msg) "true") + cause (get-last-exception-cause msg) + object (if inspect-ex-data? + (ex-data cause) + cause)] + (if object + (t/send transport (middleware.inspect/inspect-reply* msg object)) (respond-to msg :status :no-error)) (respond-to msg :status :done)))