Skip to content

Commit 0da71dd

Browse files
r0manbbatsov
authored andcommitted
Add support to view printed exceptions in the stacktrace inspector
This PR adds the `cider-stacktrace-analyze-at-point` and `cider-stacktrace-analyze-in-region` commands to view printed exceptions in the stacktrace inspector.
1 parent 63b33cc commit 0da71dd

File tree

4 files changed

+353
-27
lines changed

4 files changed

+353
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### New features
66

77
- [#3249](https://github.com/clojure-emacs/cider/pull/3249): Add support for Clojure Spec 2.
8+
- [#3247](https://github.com/clojure-emacs/cider/pull/3247) Add the `cider-stacktrace-analyze-at-point` and `cider-stacktrace-analyze-in-region` commands to view printed exceptions in the stacktrace inspector.
89

910
### Changes
1011

cider-stacktrace.el

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -670,29 +670,30 @@ This associates text properties to enable filtering and source navigation."
670670
'follow-link t
671671
'action (lambda (x) (browse-url (button-get x 'url)))))
672672
(nrepl-dbind-response frame (file line flags class method name var ns fn)
673-
(let ((flags (mapcar #'intern flags))) ; strings -> symbols
674-
(insert-text-button (format "%26s:%5d %s/%s"
675-
(if (member 'repl flags) "REPL" file) line
676-
(if (member 'clj flags) ns class)
677-
(if (member 'clj flags) fn method))
678-
'var var 'class class 'method method
679-
'name name 'file file 'line line
680-
'flags flags 'follow-link t
681-
'action #'cider-stacktrace-navigate
682-
'help-echo (cider-stacktrace-tooltip
683-
"View source at this location")
684-
'font-lock-face 'cider-stacktrace-face
685-
'type 'cider-plain-button)
686-
(save-excursion
687-
(let ((p4 (point))
688-
(p1 (search-backward " "))
689-
(p2 (search-forward "/"))
690-
(p3 (search-forward-regexp "[^/$]+")))
691-
(put-text-property p1 p4 'font-lock-face 'cider-stacktrace-ns-face)
692-
(put-text-property p2 p3 'font-lock-face 'cider-stacktrace-fn-face)
693-
(put-text-property (line-beginning-position) (line-end-position)
694-
'cider-stacktrace-frame t)))
695-
(insert "\n"))))))
673+
(when (or class file fn method ns name)
674+
(let ((flags (mapcar #'intern flags))) ; strings -> symbols
675+
(insert-text-button (format "%26s:%5d %s/%s"
676+
(if (member 'repl flags) "REPL" file) (or line -1)
677+
(if (member 'clj flags) ns class)
678+
(if (member 'clj flags) fn method))
679+
'var var 'class class 'method method
680+
'name name 'file file 'line line
681+
'flags flags 'follow-link t
682+
'action #'cider-stacktrace-navigate
683+
'help-echo (cider-stacktrace-tooltip
684+
"View source at this location")
685+
'font-lock-face 'cider-stacktrace-face
686+
'type 'cider-plain-button)
687+
(save-excursion
688+
(let ((p4 (point))
689+
(p1 (search-backward " "))
690+
(p2 (search-forward "/"))
691+
(p3 (search-forward-regexp "[^/$]+")))
692+
(put-text-property p1 p4 'font-lock-face 'cider-stacktrace-ns-face)
693+
(put-text-property p2 p3 'font-lock-face 'cider-stacktrace-fn-face)
694+
(put-text-property (line-beginning-position) (line-end-position)
695+
'cider-stacktrace-frame t)))
696+
(insert "\n")))))))
696697

697698
(defun cider-stacktrace-render-compile-error (buffer cause)
698699
"Emit into BUFFER the compile error CAUSE, and enable jumping to it."
@@ -844,7 +845,8 @@ the NAME. The whole group is prefixed by string INDENT."
844845
(goto-char (next-single-property-change (point) 'compile-error))
845846
(progn
846847
(while (cider-stacktrace-next-cause))
847-
(goto-char (next-single-property-change (point) 'flags)))))))))
848+
(when-let (position (next-single-property-change (point) 'flags))
849+
(goto-char position)))))))))
848850

849851
(defun cider-stacktrace-render (buffer causes &optional error-types)
850852
"Emit into BUFFER useful stacktrace information for the CAUSES.
@@ -876,6 +878,54 @@ through the `cider-stacktrace-suppressed-errors' variable."
876878
(cider-stacktrace-initialize causes)
877879
(font-lock-refresh-defaults)))
878880

881+
(defun cider-stacktrace--analyze-stacktrace-op (stacktrace)
882+
"Return the Cider NREPL op to analyze STACKTRACE."
883+
(list "op" "analyze-stacktrace" "stacktrace" stacktrace))
884+
885+
(defun cider-stacktrace--stacktrace-request (stacktrace)
886+
"Return the Cider NREPL request to analyze STACKTRACE."
887+
(thread-last
888+
(map-merge 'list
889+
(list (cider-stacktrace--analyze-stacktrace-op stacktrace))
890+
(cider--nrepl-print-request-map fill-column))
891+
(seq-mapcat #'identity)))
892+
893+
(defun cider-stacktrace--analyze-render (causes)
894+
"Render the CAUSES of the stacktrace analysis result."
895+
(let ((buffer (get-buffer-create cider-error-buffer)))
896+
(with-current-buffer buffer
897+
(cider-stacktrace-mode)
898+
(cider-stacktrace-render buffer (reverse causes))
899+
(display-buffer buffer cider-jump-to-pop-to-buffer-actions))))
900+
901+
(defun cider-stacktrace-analyze-string (stacktrace)
902+
"Analyze the STACKTRACE string and show the result."
903+
(when (stringp stacktrace)
904+
(set-text-properties 0 (length stacktrace) nil stacktrace))
905+
(let (causes)
906+
(cider-nrepl-send-request
907+
(cider-stacktrace--stacktrace-request stacktrace)
908+
(lambda (response)
909+
(setq causes (nrepl-dbind-response response (class status)
910+
(cond (class (cons response causes))
911+
((and (member "done" status) causes)
912+
(cider-stacktrace--analyze-render causes)))))))))
913+
914+
(defun cider-stacktrace-analyze-at-point ()
915+
"Analyze the stacktrace at point."
916+
(interactive)
917+
(cond ((thing-at-point 'sentence)
918+
(cider-stacktrace-analyze-string (thing-at-point 'sentence)))
919+
((thing-at-point 'paragraph)
920+
(cider-stacktrace-analyze-string (thing-at-point 'paragraph)))
921+
(t (cider-stacktrace-analyze-in-region (region-beginning) (region-end)))))
922+
923+
(defun cider-stacktrace-analyze-in-region (beg end)
924+
"Analyze the stacktrace in the region between BEG and END."
925+
(interactive (list (region-beginning) (region-end)))
926+
(let ((stacktrace (buffer-substring beg end)))
927+
(cider-stacktrace-analyze-string stacktrace)))
928+
879929
(provide 'cider-stacktrace)
880930

881931
;;; cider-stacktrace.el ends here

doc/modules/ROOT/pages/usage/dealing_with_errors.adoc

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,95 @@ for instance:
182182
----
183183
(setq cider-stacktrace-fill-column 80)
184184
----
185+
186+
=== Inspecting printed stacktraces
187+
188+
Some of the errors you encounter as a Clojurists aren't necessarily
189+
evaluation errors that happened in your REPL. Many times, you see
190+
errors printed in a textual representation in other buffers as well,
191+
like log files or the REPL for example. Cider can parse and analyze
192+
some of those printed errors as well and show them in
193+
`cider-stacktrace-mode` with the following commands:
194+
195+
* The `cider-stacktrace-analyze-at-point` command uses the `thingatpt`
196+
library to extract the current stacktrace at point. It sends the
197+
extracted stacktrace to the middleware in order to parse and analyze
198+
it, and then shows the result in Cider's `cider-stacktrace-mode`.
199+
200+
* The `cider-stacktrace-analyze-in-region` command does the same as
201+
`cider-stacktrace-analyze-at-point`, but uses the current region to
202+
extract the stacktrace.
203+
204+
===== Examples
205+
206+
Here is an example of a stacktrace printed with the Java
207+
`printStackTrace` method:
208+
209+
[source,text]
210+
----
211+
clojure.lang.ExceptionInfo: BOOM-1 {:boom "1"}
212+
at java.base/java.lang.Thread.run(Thread.java:829)
213+
----
214+
215+
To open this stacktrace in the Cider stacktrace inspector, move point
216+
somewhere over the exception and run `M-x
217+
cider-stacktrace-analyze-at-point`.
218+
219+
This also works to some extend for exceptions that are buried inside a
220+
string like the following exception:
221+
222+
[source,text]
223+
----
224+
"clojure.lang.ExceptionInfo: BOOM-1 {:boom \"1\"}\n at java.base/java.lang.Thread.run(Thread.java:829)"
225+
----
226+
227+
Those exceptions are often hard to read. The Cider stacktrace
228+
inspector can help you navigating exceptions even in those cases.
229+
230+
===== Supported formats
231+
232+
Cider recognizes stacktraces printed in the following formats:
233+
234+
- `Aviso` - Exceptions printed with the
235+
https://ioavisopretty.readthedocs.io/en/latest/exceptions.html[write-exception]
236+
function of the https://github.com/AvisoNovate/pretty[Aviso]
237+
library.
238+
239+
- `clojure.repl` - Exceptions printed with the
240+
https://clojure.github.io/clojure/branch-master/clojure.repl-api.html#clojure.repl/pst[clojure.repl/pst]
241+
function.
242+
243+
- `clojure.stacktrace` - Exceptions printed with the
244+
https://clojure.github.io/clojure/branch-master/clojure.stacktrace-api.html#clojure.stacktrace/print-cause-trace[clojure.stacktrace/print-cause-trace]
245+
function.
246+
247+
- `Java` - Exceptions printed with the
248+
https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html#printStackTrace--[Throwable/printStackTrace]
249+
method.
250+
251+
- `Tagged Literal` - Exceptions printed with the
252+
https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/pr[clojure.core/pr]
253+
function.
254+
255+
===== Limitations
256+
257+
- Cider only recognizes stacktraces that have been printed in one of
258+
the supported formats.
259+
260+
- The buffers in which `cider-stacktrace-analyze-at-point` or
261+
`cider-stacktrace-analyze-in-region` are called in, must have a
262+
Cider session associated with them. Tip: Use
263+
`sesman-link-with-project` and friends in case the buffer containing
264+
the exception is not linked to a Cider session.
265+
266+
- Stacktraces are analyzed with the classpath of the Cider session the
267+
buffer is associated with. If the stacktrace contains references to
268+
classes not on this classpath, some information might be missing
269+
from the analysis.
270+
271+
- The `cider-stacktrace-analyze-at-point` function might not detect
272+
the stacktrace at point in every situation. The thing at point might
273+
be different depending on which major mode is active in a
274+
buffer. When `cider-stacktrace-analyze-at-point` fails to detect the
275+
stacktrace, `cider-stacktrace-analyze-in-region` can be used to
276+
select the stacktrace manually.

0 commit comments

Comments
 (0)