Skip to content

Commit

Permalink
[info] Add support for sources JAR downloading on `info' events
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-yakushev authored and bbatsov committed Jan 16, 2025
1 parent 3c89404 commit c0e7f25
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 37 additions & 10 deletions cider-client.el
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand Down
18 changes: 14 additions & 4 deletions doc/modules/ROOT/pages/usage/working_with_documentation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 16 additions & 7 deletions nrepl-client.el
Original file line number Diff line number Diff line change
Expand Up @@ -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))))
Expand All @@ -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
Expand Down

0 comments on commit c0e7f25

Please sign in to comment.