Skip to content

Commit

Permalink
cider-ns-refresh: summarize errors as an overlay
Browse files Browse the repository at this point in the history
Fixes #3628
  • Loading branch information
vemv authored and bbatsov committed Mar 7, 2024
1 parent cfe2de3 commit 9edb2d2
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
### Changes

- [#3626](https://github.com/clojure-emacs/cider/issues/3626): `cider-ns-refresh`: jump to the relevant file/line on errors.
- [#3628](https://github.com/clojure-emacs/cider/issues/3628): `cider-ns-refresh`: summarize errors as an overlay.
- Bump the injected nREPL to [1.1.1](https://github.com/nrepl/nrepl/blob/v1.1.1/CHANGELOG.md#111-2024-02-20).
- Bump the injected `cider-nrepl` to [0.46.0](https://github.com/clojure-emacs/cider-nrepl/blob/1cc9b2/CHANGELOG.md#0460-2024-0305).
- Updates [Orchard](https://github.com/clojure-emacs/orchard/blob/v0.23.0/CHANGELOG.md#0230-2024-03-03).
Expand Down
19 changes: 17 additions & 2 deletions cider-ns.el
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ namespace-qualified function of zero arity."

(defun cider-ns--present-error (error)
"Render the `ERROR' stacktrace,
and jump to the adequate file/line location."
and jump to the adequate file/line location,
presenting the error message as an overlay."
(let* ((buf)
(jump-args (seq-some (lambda (cause-dict) ;; a dict representing an exception cause
(nrepl-dbind-response cause-dict (file-url line column)
Expand All @@ -132,7 +133,21 @@ and jump to the adequate file/line location."
(list buf (cons line column)))))
error)))
(when jump-args
(apply #'cider-jump-to jump-args))
(apply #'cider-jump-to jump-args)
(when-let ((message (seq-some (lambda (cause-dict)
(nrepl-dbind-response cause-dict (message)
message))
;; `reverse' the causes as the first one typically is a CompilerException, which the second one is the actual exception:
(reverse error))))
(with-current-buffer buf
(let ((cider-result-use-clojure-font-lock nil)
(trimmed-err (funcall cider-inline-error-message-function message)))
(cider--display-interactive-eval-result trimmed-err
'error
(save-excursion
(end-of-defun)
(point))
'cider-error-overlay-face)))))
(cider--render-stacktrace-causes error)
;; Select the window displaying the 'culprit' buffer so that the user can immediately fix it,
;; as most times the displayed stacktrace doesn't need much inspection:
Expand Down
169 changes: 169 additions & 0 deletions test/cider-ns-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,172 @@
(it "raises a user error if cider is not connected"
(spy-on 'cider-connected-p :and-return-value nil)
(expect (cider-ns-refresh) :to-throw 'user-error)))

(defvar cider-ns-tests--sample-file-url
"file:test/cider_ns_tests.clj")

(defvar cider-ns-tests--sample-causes
`((dict "class" "clojure.lang.Compiler$CompilerException" "column" 0 "compile-like" "false" "data" "{:clojure.error/phase :read-source,
:clojure.error/line 23,
:clojure.error/column 0,
:clojure.error/source \"gpml/handler/chat.clj\"}" "file" "gpml/handler/chat.clj" "file-url" ,cider-ns-tests--sample-file-url "line" 23 "location"
(dict "clojure.error/column" 0 "clojure.error/line" 23 "clojure.error/phase" "read-source" "clojure.error/source" "gpml/handler/chat.clj")
"message" "Syntax error reading source at (gpml/handler/chat.clj:23:0)." "path" "gpml/handler/chat.clj" "phase" "read-source" "stacktrace"
((dict "class" "clojure.lang.Compiler" "file" "Compiler.java" "file-url" nil "flags"
("tooling" "java")
"line" 7643 "method" "load" "name" "clojure.lang.Compiler/load" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("tooling" "java")
"line" 381 "method" "loadResourceScript" "name" "clojure.lang.RT/loadResourceScript" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("dup" "tooling" "java")
"line" 372 "method" "loadResourceScript" "name" "clojure.lang.RT/loadResourceScript" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("tooling" "java")
"line" 459 "method" "load" "name" "clojure.lang.RT/load" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("dup" "tooling" "java")
"line" 424 "method" "load" "name" "clojure.lang.RT/load" "type" "java")
(dict "class" "clojure.core$load$fn__6924" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load/fn" "line" 6167 "method" "invoke" "name" "clojure.core$load$fn__6924/invoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load")
(dict "class" "clojure.core$load" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load" "line" 6166 "method" "invokeStatic" "name" "clojure.core$load/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load")
(dict "class" "clojure.core$load" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load" "line" 6150 "method" "doInvoke" "name" "clojure.core$load/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 411 "method" "invoke" "name" "clojure.lang.RestFn/invoke" "type" "java")
(dict "class" "clojure.core$load_one" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-one" "line" 5939 "method" "invokeStatic" "name" "clojure.core$load_one/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-one")
(dict "class" "clojure.core$load_one" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-one" "line" 5934 "method" "invoke" "name" "clojure.core$load_one/invoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-one")
(dict "class" "clojure.core$load_lib$fn__6866" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-lib/fn" "line" 5981 "method" "invoke" "name" "clojure.core$load_lib$fn__6866/invoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-lib")
(dict "class" "clojure.core$load_lib" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-lib" "line" 5980 "method" "invokeStatic" "name" "clojure.core$load_lib/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-lib")
(dict "class" "clojure.core$load_lib" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-lib" "line" 5959 "method" "doInvoke" "name" "clojure.core$load_lib/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-lib")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 145 "method" "applyTo" "name" "clojure.lang.RestFn/applyTo" "type" "java")
(dict "class" "clojure.core$apply" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("tooling" "clj")
"fn" "apply" "line" 669 "method" "invokeStatic" "name" "clojure.core$apply/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/apply")
(dict "class" "clojure.core$load_libs" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-libs" "line" 6022 "method" "invokeStatic" "name" "clojure.core$load_libs/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-libs")
(dict "class" "clojure.core$load_libs" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-libs" "line" 6006 "method" "doInvoke" "name" "clojure.core$load_libs/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-libs")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 140 "method" "applyTo" "name" "clojure.lang.RestFn/applyTo" "type" "java")
(dict "class" "clojure.core$apply" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("tooling" "clj")
"fn" "apply" "line" 669 "method" "invokeStatic" "name" "clojure.core$apply/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/apply")
(dict "class" "clojure.core$require" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "require" "line" 6044 "method" "invokeStatic" "name" "clojure.core$require/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/require")
(dict "class" "clojure.core$require" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("dup" "clj")
"fn" "require" "line" 6044 "method" "doInvoke" "name" "clojure.core$require/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/require")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 424 "method" "invoke" "name" "clojure.lang.RestFn/invoke" "type" "java")))
(dict "class" "java.lang.RuntimeException" "compile-like" "false" "message" "Invalid token: :::a" "phase" nil "stacktrace"
((dict "class" "clojure.lang.Util" "file" "Util.java" "file-url" nil "flags"
("java")
"line" 221 "method" "runtimeException" "name" "clojure.lang.Util/runtimeException" "type" "java")
(dict "class" "clojure.lang.LispReader" "file" "LispReader.java" "file-url" nil "flags"
("java")
"line" 412 "method" "interpretToken" "name" "clojure.lang.LispReader/interpretToken" "type" "java")
(dict "class" "clojure.lang.LispReader" "file" "LispReader.java" "file-url" nil "flags"
("java")
"line" 305 "method" "read" "name" "clojure.lang.LispReader/read" "type" "java")
(dict "class" "clojure.lang.LispReader" "file" "LispReader.java" "file-url" nil "flags"
("dup" "java")
"line" 216 "method" "read" "name" "clojure.lang.LispReader/read" "type" "java")
(dict "class" "clojure.lang.Compiler" "file" "Compiler.java" "file-url" nil "flags"
("tooling" "java")
"line" 7631 "method" "load" "name" "clojure.lang.Compiler/load" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("tooling" "java")
"line" 381 "method" "loadResourceScript" "name" "clojure.lang.RT/loadResourceScript" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("dup" "tooling" "java")
"line" 372 "method" "loadResourceScript" "name" "clojure.lang.RT/loadResourceScript" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("tooling" "java")
"line" 459 "method" "load" "name" "clojure.lang.RT/load" "type" "java")
(dict "class" "clojure.lang.RT" "file" "RT.java" "file-url" nil "flags"
("dup" "tooling" "java")
"line" 424 "method" "load" "name" "clojure.lang.RT/load" "type" "java")
(dict "class" "clojure.core$load$fn__6924" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load/fn" "line" 6167 "method" "invoke" "name" "clojure.core$load$fn__6924/invoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load")
(dict "class" "clojure.core$load" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load" "line" 6166 "method" "invokeStatic" "name" "clojure.core$load/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load")
(dict "class" "clojure.core$load" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load" "line" 6150 "method" "doInvoke" "name" "clojure.core$load/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 411 "method" "invoke" "name" "clojure.lang.RestFn/invoke" "type" "java")
(dict "class" "clojure.core$load_one" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-one" "line" 5939 "method" "invokeStatic" "name" "clojure.core$load_one/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-one")
(dict "class" "clojure.core$load_one" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-one" "line" 5934 "method" "invoke" "name" "clojure.core$load_one/invoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-one")
(dict "class" "clojure.core$load_lib$fn__6866" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-lib/fn" "line" 5981 "method" "invoke" "name" "clojure.core$load_lib$fn__6866/invoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-lib")
(dict "class" "clojure.core$load_lib" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-lib" "line" 5980 "method" "invokeStatic" "name" "clojure.core$load_lib/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-lib")
(dict "class" "clojure.core$load_lib" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-lib" "line" 5959 "method" "doInvoke" "name" "clojure.core$load_lib/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-lib")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 145 "method" "applyTo" "name" "clojure.lang.RestFn/applyTo" "type" "java")
(dict "class" "clojure.core$apply" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("tooling" "clj")
"fn" "apply" "line" 669 "method" "invokeStatic" "name" "clojure.core$apply/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/apply")
(dict "class" "clojure.core$load_libs" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-libs" "line" 6022 "method" "invokeStatic" "name" "clojure.core$load_libs/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-libs")
(dict "class" "clojure.core$load_libs" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "load-libs" "line" 6006 "method" "doInvoke" "name" "clojure.core$load_libs/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/load-libs")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 140 "method" "applyTo" "name" "clojure.lang.RestFn/applyTo" "type" "java")
(dict "class" "clojure.core$apply" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("tooling" "clj")
"fn" "apply" "line" 669 "method" "invokeStatic" "name" "clojure.core$apply/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/apply")
(dict "class" "clojure.core$require" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("clj")
"fn" "require" "line" 6044 "method" "invokeStatic" "name" "clojure.core$require/invokeStatic" "ns" "clojure.core" "type" "clj" "var" "clojure.core/require")
(dict "class" "clojure.core$require" "file" "core.clj" "file-url" "jar:file:/Users/vemv/.m2/repository/org/clojure/clojure/1.12.900/clojure-1.12.900.jar!/clojure/core.clj" "flags"
("dup" "clj")
"fn" "require" "line" 6044 "method" "doInvoke" "name" "clojure.core$require/doInvoke" "ns" "clojure.core" "type" "clj" "var" "clojure.core/require")
(dict "class" "clojure.lang.RestFn" "file" "RestFn.java" "file-url" nil "flags"
("tooling" "java")
"line" 424 "method" "invoke" "name" "clojure.lang.RestFn/invoke" "type" "java") ))))

(describe "cider-ns--present-error"
(it "Works without throwing errors"
(with-clojure-buffer ""
(cider-ns--present-error cider-ns-tests--sample-causes)
(when-let ((b (get-buffer "*cider-error*"))) ;; Clean it up for other tests
(kill-buffer b)))))
2 changes: 2 additions & 0 deletions test/cider_ns_tests.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns cider-ns-tests
"Supports cider-ns-tests.el")

0 comments on commit 9edb2d2

Please sign in to comment.