branch: elpa/cider
commit c0e7f2526d7b5ed67c723c4fbf3ce5391f4f5123
Author: Oleksandr Yakushev <a...@bytopia.org>
Commit: Bozhidar Batsov <bozhi...@batsov.dev>

    [info] Add support for sources JAR downloading on `info' events
---
 CHANGELOG.md                                       |  1 +
 cider-client.el                                    | 47 +++++++++++++++++-----
 .../pages/usage/working_with_documentation.adoc    | 18 +++++++--
 nrepl-client.el                                    | 23 +++++++----
 4 files changed, 68 insertions(+), 21 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 995719d77f..1e83c9bbad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### New features
 
+- Automatic downloading of third-party Java sources for better Java 
documentation and jump-to-definition functionality. See [Obtaining source 
JARs](https://docs.cider.mx/cider/usage/working_with_documentation.html#obtaining-source-jars).
 - CIDER [History](https://docs.cider.mx/cider/repl/history.html): Add a 
command to delete history item at point.
 
 ### Changes
diff --git a/cider-client.el b/cider-client.el
index df4eacfedb..b72ccf3712 100644
--- a/cider-client.el
+++ b/cider-client.el
@@ -189,16 +189,20 @@ the current connection.  Return the id of the sent 
message.
 If TOOLING is truthy then the tooling session is used."
   (nrepl-send-request request callback (or connection (cider-current-repl 'any 
'ensure)) tooling))
 
-(defun cider-nrepl-send-sync-request (request &optional connection 
abort-on-input)
+(defun cider-nrepl-send-sync-request (request &optional connection
+                                              abort-on-input callback)
   "Send REQUEST to the nREPL server synchronously using CONNECTION.
 Hold till final \"done\" message has arrived and join all response messages
 of the same \"op\" that came along and return the accumulated response.
 If ABORT-ON-INPUT is non-nil, the function will return nil
 at the first sign of user input, so as not to hang the
-interface."
+interface.
+if CALLBACK is non-nil, it will additionally be called on all received 
messages."
   (nrepl-send-sync-request request
                            (or connection (cider-current-repl 'any 'ensure))
-                           abort-on-input))
+                           abort-on-input
+                           nil
+                           callback))
 
 (defun cider-nrepl-send-unhandled-request (request &optional connection)
   "Send REQUEST to the nREPL CONNECTION and ignore any responses.
@@ -342,6 +346,17 @@ The default value in nREPL is 1024."
   :group 'cider
   :package-version '(cider . "0.25.0"))
 
+(defcustom cider-download-java-sources nil
+  "Whether to automatically download source artifacts for 3rd-party Java 
classes.
+
+When enabled, CIDER will attempt to download source JARs from Maven for
+Java classes if the source file is not found locally.  This downloading only
+happens once per artifact, and only when the user jumps to definition or
+requests `cider-doc' on a Java class or a member of the class."
+  :type 'boolean
+  :group 'cider
+  :package-version '(cider . "1.17.0"))
+
 (defun cider--print-fn ()
   "Return the value to send in the nrepl.middleware.print/print slot."
   (pcase cider-print-fn
@@ -681,13 +696,25 @@ CONTEXT represents a completion context for compliment."
 
 (defun cider-sync-request:info (symbol &optional class member context)
   "Send \"info\" op with parameters SYMBOL or CLASS and MEMBER, honor CONTEXT."
-  (let ((var-info (thread-first `("op" "info"
-                                  "ns" ,(cider-current-ns)
-                                  ,@(when symbol `("sym" ,symbol))
-                                  ,@(when class `("class" ,class))
-                                  ,@(when member `("member" ,member))
-                                  ,@(when context `("context" ,context)))
-                                (cider-nrepl-send-sync-request 
(cider-current-repl)))))
+  (let* ((req
+          `("op" "info"
+            "ns" ,(cider-current-ns)
+            ,@(when symbol `("sym" ,symbol))
+            ,@(when class `("class" ,class))
+            ,@(when member `("member" ,member))
+            ,@(when context `("context" ,context))
+            ,@(when cider-download-java-sources `("download-sources-jar" 
"1"))))
+         (callback
+          (lambda (resp)
+            (let ((status (nrepl-dict-get resp "status"))
+                  (coords (nrepl-dict-get resp "coords")))
+              (when (member "download-sources-jar" status)
+                (message "Local source not found, downloading Java sources for 
artifact %s/%s %s..."
+                         (nrepl-dict-get coords "group")
+                         (nrepl-dict-get coords "artifact")
+                         (nrepl-dict-get coords "version"))))))
+         (var-info
+          (cider-nrepl-send-sync-request req (cider-current-repl) nil 
callback)))
     (if (member "no-info" (nrepl-dict-get var-info "status"))
         nil
       var-info)))
diff --git a/doc/modules/ROOT/pages/usage/working_with_documentation.adoc 
b/doc/modules/ROOT/pages/usage/working_with_documentation.adoc
index fe72f33f5f..72dc046540 100644
--- a/doc/modules/ROOT/pages/usage/working_with_documentation.adoc
+++ b/doc/modules/ROOT/pages/usage/working_with_documentation.adoc
@@ -17,11 +17,21 @@ as some people prefer to keep holding `Control` and some 
don't.
 
 Normally the command operates on the symbol at point.  If invoked with a 
prefix argument, or no symbol is found at point, it will prompt for a symbol.
 
-NOTE: If using `enrich-classpath`, Java doc comments are available and 
rendered in the same way that Clojure docstrings are.
-They're often much more handy than opening Javadoc in a browser. Starting from 
CIDER 1.8.0,
-the HTML-like language that they use is nicely rendered into syntax-colored 
strings, well-aligned tables, etc
+== Local JavaDoc
 
-== JavaDoc
+Most JDK distributions ship with a `src.zip` file (an archive with all base 
Java source files). If you have such archive present in your JDK, CIDER will 
automatically parse the source file when you query the documentation for a Java 
class (e.g. `java.lang.Thread`) or a method (e.g. 
`java.lang.Thread/currentThread`) and will display the properly formatted 
JavaDoc in the documentation buffer. You will also see better Eldoc 
documentation (minibuffer hints) for Java methods. If the source fi [...]
+
+Furthermore, CIDER is able to parse JavaDoc source files and jump to 
definitions for third-party Java libraries if you have downloaded the special 
`-sources.jar` file for that library. See the next section on how to download 
source JARs.
+
+== Obtaining source JARs
+
+Since version 1.17, CIDER is able to download the necessary source JAR file 
automatically when you either request the documentation for a Java class/method 
or when you jump to the definition of a Java class/method. In order for the 
sources to be downloaded, you need to enable custom variable 
`cider-download-java-sources`. When the download triggers, CIDER displays a 
minibuffer message about that. Fetching a single source JAR usually takes a few 
seconds. CIDER will make only one attempt t [...]
+
+NOTE: While Eldoc functionality benefits from having Java sources, the eldoc 
itself will not trigger the downloading of Java source JARs. You will have to 
lookup the documentation once manually or jump to the definition in order for 
the JAR is downloaded. After that, Eldoc will pick up the Java sources and 
display better hints.
+
+Alternatively, you can use 
https://github.com/clojure-emacs/enrich-classpath[`enrich-classpath`] to 
download all source JARs used by your current project at once. This will incur 
longer startup time, but will not trigger individual JARs fetching at the 
runtime.
+
+== Online JavaDoc
 
 CIDER provides a quick access to the online Javadoc documentation
 via the command `cider-javadoc` (kbd:[C-c C-d j] or kbd:[C-c C-d C-j]), using 
your default browser.
diff --git a/nrepl-client.el b/nrepl-client.el
index 3479d38575..00a4c143e3 100644
--- a/nrepl-client.el
+++ b/nrepl-client.el
@@ -939,21 +939,30 @@ the standard session."
 (declare-function cider-repl-emit-interactive-stderr "cider-repl")
 (declare-function cider--render-stacktrace-causes "cider-eval")
 
-(defun nrepl-send-sync-request (request connection &optional abort-on-input 
tooling)
+(defun nrepl-send-sync-request (request connection &optional abort-on-input
+                                        tooling callback)
   "Send REQUEST to the nREPL server synchronously using CONNECTION.
 Hold till final \"done\" message has arrived and join all response messages
 of the same \"op\" that came along.
 If ABORT-ON-INPUT is non-nil, the function will return nil at the first
 sign of user input, so as not to hang the interface.
-If TOOLING, use the tooling session rather than the standard session."
+If TOOLING, use the tooling session rather than the standard session.
+
+If CALLBACK is non-nil, it will additionally be called on all received
+messages. This shouldn't be used this for any control logic — use the
+asynchronous `nrepl-send-request' directly for that. CALLBACK here should
+be used to react to some intermediate events in an otherwise synchronous
+command and e.g. notify the user about them."
   (let* ((time0 (current-time))
          (response (cons 'dict nil))
          (nrepl-ongoing-sync-request t)
+         (cb (lambda (resp)
+               ;; If caller has provided `callback', call it on the response.
+               (when callback
+                 (funcall callback resp))
+               (nrepl--merge response resp)))
          status)
-    (nrepl-send-request request
-                        (lambda (resp) (nrepl--merge response resp))
-                        connection
-                        tooling)
+    (nrepl-send-request request cb connection tooling)
     (while (and (not (member "done" status))
                 (not (and abort-on-input
                           (input-pending-p))))
@@ -962,7 +971,7 @@ If TOOLING, use the tooling session rather than the 
standard session."
       ;; anywhere, and we'll just timeout. So we forward it to the user.
       (if (member "need-input" status)
           (progn (cider-need-input (current-buffer))
-                 ;; If the used took a few seconds to respond, we might
+                 ;; If the user took a few seconds to respond, we might
                  ;; unnecessarily timeout, so let's reset the timer.
                  (setq time0 (current-time)))
         ;; break out in case we don't receive a response for a while

Reply via email to