Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[info] Add support for sources JAR downloading on `info' events #3769

Merged
merged 3 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

### 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

- Bump the injected nREPL version to [1.3.1](https://github.com/nrepl/nrepl/blob/master/CHANGELOG.md#131-2025-01-01).
- Bump the injected `cider-nrepl` to [0.51.1](https://github.com/clojure-emacs/cider-nrepl/blob/master/CHANGELOG.md#0511-2025-01-03).
- Bump the injected `cider-nrepl` to [0.52.0](https://github.com/clojure-emacs/cider-nrepl/blob/master/CHANGELOG.md#0520-2025-01-10).
- [#3574](https://github.com/clojure-emacs/cider/issues/3574): New value `per-project` for `cider-repl-history-file` to save the history on a per-project basis.

### Bugs fixed
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
3 changes: 2 additions & 1 deletion cider-docstring.el
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ that it usually has two spaces before each line used for indentation
\(see https://guide.clojure.style/#docstring-indentation). While displaying
the docstring to the user, we usually want to control indentation and
other aspects of the presentation, so we format it before displaying."
(replace-regexp-in-string "\n " "\n" string))
(when string
(replace-regexp-in-string "\n " "\n" string)))

(provide 'cider-docstring)
;;; cider-docstring.el ends here
2 changes: 1 addition & 1 deletion cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ the artifact.")

Used when `cider-jack-in-auto-inject-clojure' is set to `latest'.")

(defconst cider-required-middleware-version "0.51.1"
(defconst cider-required-middleware-version "0.52.0"
"The CIDER nREPL version that's known to work properly with CIDER.")

(defcustom cider-injected-middleware-version cider-required-middleware-version
Expand Down
2 changes: 1 addition & 1 deletion dev/docker-sample-project/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
:dependencies [[org.clojure/clojure "1.11.1"]
[clj-http "3.12.3"]]
:source-paths ["src"]
:plugins [[cider/cider-nrepl "0.51.1"]])
:plugins [[cider/cider-nrepl "0.52.0"]])
2 changes: 1 addition & 1 deletion dev/tramp-sample-project/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
:dependencies [[org.clojure/clojure "1.11.1"]
[clj-http "3.12.3"]]
:source-paths ["src"]
:plugins [[cider/cider-nrepl "0.51.1"]
:plugins [[cider/cider-nrepl "0.52.0"]
[refactor-nrepl "3.9.0"]])
12 changes: 6 additions & 6 deletions doc/modules/ROOT/pages/basics/middleware_setup.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ Use the convenient plugin for defaults, either in your project's

[source,clojure]
----
:plugins [[cider/cider-nrepl "0.51.1"]]
:plugins [[cider/cider-nrepl "0.52.0"]]
----

A minimal `profiles.clj` for CIDER would be:

[source,clojure]
----
{:repl {:plugins [[cider/cider-nrepl "0.51.1"]]}}
{:repl {:plugins [[cider/cider-nrepl "0.52.0"]]}}
----

WARNING: Be careful not to place this in the `:user` profile, as this way CIDER's
Expand All @@ -59,7 +59,7 @@ all of their projects using a `~/.boot/profile.boot` file like so:
(require 'boot.repl)
(swap! boot.repl/*default-dependencies*
concat '[[cider/cider-nrepl "0.51.1"]])
concat '[[cider/cider-nrepl "0.52.0"]])
(swap! boot.repl/*default-middleware*
conj 'cider.nrepl/cider-middleware)
Expand All @@ -76,11 +76,11 @@ run `cider-connect` or `cider-connect-cljs`.

[source,clojure]
----
:cider-clj {:extra-deps {cider/cider-nrepl {:mvn/version "0.51.1"}}
:cider-clj {:extra-deps {cider/cider-nrepl {:mvn/version "0.52.0"}}
:main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}
:cider-cljs {:extra-deps {org.clojure/clojurescript {:mvn/version "1.10.339"}
cider/cider-nrepl {:mvn/version "0.51.1"}
cider/cider-nrepl {:mvn/version "0.52.0"}
cider/piggieback {:mvn/version "0.5.3"}}
:main-opts ["-m" "nrepl.cmdline" "--middleware"
"[cider.nrepl/cider-middleware,cider.piggieback/wrap-cljs-repl]"]}
Expand All @@ -99,7 +99,7 @@ NOTE: Make sure you're using https://github.com/clojurephant/clojurephant[Clojur
----
dependencies {
devImplementation 'nrepl:nrepl:0.9.0'
devImplementation 'cider:cider-nrepl:0.51.1'
devImplementation 'cider:cider-nrepl:0.52.0'
}
tasks.named('clojureRepl') {
Expand Down
4 changes: 2 additions & 2 deletions doc/modules/ROOT/pages/basics/up_and_running.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ simple - CIDER simply passes the extra dependencies and nREPL configuration to
your build tool in the command it runs to start the nREPL server. Here's how
this looks for `tools.deps`:

$ clojure -Sdeps '{:deps {nrepl {:mvn/version "1.3.1"} cider/cider-nrepl {:mvn/version "0.51.1"}}}' -m nrepl.cmdline --middleware '["cider.nrepl/cider-middleware"]'
$ clojure -Sdeps '{:deps {nrepl {:mvn/version "1.3.1"} cider/cider-nrepl {:mvn/version "0.52.0"}}}' -m nrepl.cmdline --middleware '["cider.nrepl/cider-middleware"]'

TIP: If you don't want `cider-jack-in` to inject dependencies automatically, set
`cider-inject-dependencies-at-jack-in` to `nil`. Note that you'll have to setup
Expand Down Expand Up @@ -350,7 +350,7 @@ It is also possible for plain `clj`, although the command is somewhat longer:

[source,sh]
----
$ clj -Sdeps '{:deps {cider/cider-nrepl {:mvn/version "0.51.1"}}}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]"
$ clj -Sdeps '{:deps {cider/cider-nrepl {:mvn/version "0.52.0"}}}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]"
----

Alternatively, you can start nREPL either manually or using the facilities
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/cljs/shadow-cljs.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ And connect to it with `cider-connect`.
...For that to work, `shadow-cljs.edn` contents like the following are assumed:

```clj
:dependencies [[cider/cider-nrepl "0.51.1"] ;; mandatory (unless it's inherited from deps.edn or otherwise present in the classpath of shadow-cljs's JVM process)
:dependencies [[cider/cider-nrepl "0.52.0"] ;; mandatory (unless it's inherited from deps.edn or otherwise present in the classpath of shadow-cljs's JVM process)
[refactor-nrepl/refactor-nrepl "3.9.0"]] ;; refactor-nrepl is optional

:nrepl {:middleware [cider.nrepl/cider-middleware ;; it's advisable to explicitly add this middleware. It's automatically added by shadow-cljs (if available in the classpath), unless `:nrepl {:cider false}`
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/cljs/up_and_running.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ or in `build.gradle`:
----
dependencies {
devImplementation 'nrepl:nrepl:1.3.1'
devImplementation 'cider:cider-nrepl:0.51.1'
devImplementation 'cider:cider-nrepl:0.52.0'
devImplementation 'cider:cider-piggieback:0.5.3'
}

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
Loading
Loading