From c0e7f2526d7b5ed67c723c4fbf3ce5391f4f5123 Mon Sep 17 00:00:00 2001 From: Oleksandr Yakushev Date: Fri, 10 Jan 2025 19:55:20 +0200 Subject: [PATCH] [info] Add support for sources JAR downloading on `info' events --- CHANGELOG.md | 1 + cider-client.el | 47 +++++++++++++++---- .../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 995719d77..1e83c9bba 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 df4eacfed..b72ccf371 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 fe72f33f5..72dc04654 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 file are present, you are able to jump to class or method definition by pressing kbd:[M-.] on the class name or method name. + +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 to download the source JAR for a particular dependency per process — if it failed to download (usually, because the dependency doesn't have a source JAR published to Maven), CIDER will not retry that until the next restart. + +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 3479d3857..00a4c143e 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