Skip to content
Open
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
14 changes: 14 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,21 @@ jobs:
os: [ubuntu-latest]
emacs_version: ['28.2', '29.3', '30.1']
java_version: ['21']
dotnet_version: ['10.0.7']
include:
# For other OSes, test only the latest stable Emacs version.
- os: macos-latest # aarch64
emacs_version: '30.1'
java_version: '21'
dotnet_version: '10.0.7'
- os: macos-15 # x64
emacs_version: '30.1'
java_version: '21'
dotnet_version: '10.0.7'
- os: windows-latest
emacs_version: '30.1'
java_version: '21'
dotnet_version: '10.0.7'

steps:
- name: Set up Emacs
Expand Down Expand Up @@ -109,6 +113,16 @@ jobs:
- run: |
pip install basilisp==0.5.0

- name: Prepare dotnet
uses: xt0rted/setup-dotnet@v1.5.0
with:
dotnet-version: ${{matrix.dotnet_version}}

- name: Prepare ClojureCLR
run: |
dotnet tool install --global Clojure.Main --version 1.12.3-alpha8
dotnet tool install --global Clojure.Cljr --version 0.1.0-alpha11

- name: Test integration
run: |
# The tests occasionally fail on macos&win in what is seems to
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Cache the result of `cider--running-nrepl-paths` (used by `cider-locate-running-nrepl-ports`) for `cider-running-nrepl-paths-cache-ttl` seconds (default 5). Repeated `cider-connect` completions no longer re-spawn a fresh round of `ps`/`lsof` subprocesses each time. `cider-clear-running-nrepl-paths-cache` discards the cache on demand.
- New `nrepl-make-eval-handler` with a keyword-arg API (`:on-value`, `:on-stdout`, `:on-stderr`, `:on-done`, `:on-eval-error`, `:on-content-type`, `:on-truncated`). Sub-handlers no longer take a buffer argument -- they close over whatever they need. `nrepl-make-response-handler`, the legacy 7-positional-arg form, is preserved as an obsolete shim that adapts the old (buffer x) lambdas to the new (x) lambdas, so existing extensions keep working.
- Decouple the nREPL transport layer from CIDER's UI layer (closes [#1099](https://github.com/clojure-emacs/cider/issues/1099)). `nrepl-make-eval-handler` is now CIDER-agnostic: it no longer references `nrepl-namespace-handler-function`, `nrepl-err-handler-function`, `nrepl-need-input-handler-function`, or any hardcoded UI strings. New `:on-ns` and `:on-status` keyword slots let any consumer wire up their own namespace tracking and status handling. The editor-level `cider-make-eval-handler` wraps it with CIDER's UI behavior (ns tracking, default error handler, need-input prompt, "Evaluation interrupted." / "Namespace not found." messages); in-tree callers all use it.
- [#3839](https://github.com/clojure-emacs/cider/pull/3839): Add jack-in support for ClojureCLR.

### Bugs fixed

Expand Down
1 change: 1 addition & 0 deletions doc/modules/ROOT/pages/basics/up_and_running.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ The following Clojure build tools are supported so far
- kbd:[M-3 C-c C-x j u] jack-in using babashka.
- kbd:[M-4 C-c C-x j u] jack-in using nbb.
- kbd:[M-5 C-c C-x j u] jack-in using basilisp.
- kbd:[M-6 C-c C-x j u] jack-in using ClojureCLR.

Here is an example of how to bind kbd:[F12] for quickly bringing up a
babashka REPL:
Expand Down
10 changes: 4 additions & 6 deletions doc/modules/ROOT/pages/caveats.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,11 @@ from your Emacs config.

== ClojureCLR Support

CIDER currently has very basic support for ClojureCLR (via Arcadia's nREPL server). The reasons for this are the following:
CIDER currently has very basic support for ClojureCLR (via either a port of Babashka's nREPL server or Arcadia's nREPL server). The reasons for this are the following:

* nREPL itself runs only on the JVM (because it leverages Java APIs
internally). There's an
https://github.com/clojure/clr.tools.nrepl[nREPL port for ClojureCLR], but
it's not actively maintained and it doesn't behave like the Clojure nREPL.
* `cider-nrepl` uses a lot of Java code internally itself.
* The https://github.com/clojure/clr.tools.nrepl/tree/master/partial-nrepl-nrepl-port[nrepl/nrepl port to ClojureCLR] is not yet working
* `cider-nrepl` uses a lot of Java code internally itself and would need to be adapted/ported like any
other clojure library adapted/ported to ClojureCLR.

Those issues are not insurmountable, but are beyond the scope of our current roadmap.
If someone would like to tackle them, we'd be happy to provide assistance.
Expand Down
16 changes: 8 additions & 8 deletions doc/modules/ROOT/pages/platforms/clojureclr.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@

== Current Status

ClojureCLR on CIDER is not great due to the lack of a fully-functional nREPL
server for ClojureCLR. There are currently two options:
You will get basic CIDER functionality with ClojureCLR. Three nREPL server options:

- https://github.com/clojure/clr.tools.nrepl[clr.tools.nrepl]: A direct (but incomplete) port of the reference Clojure nREPL server.
- https://github.com/clojure/clr.tools.nrepl[clr.tools.nrepl]: At present port of babashka's nREPL server (https://github.com/babashka/babashka.nrepl[babashka.nrepl]).
- https://github.com/clojure/clr.tools.nrepl/tree/master/partial-nrepl-nrepl-port[port of nrepl/nrepl]: A non-working, work-in-progress port of nrepl/nrepl, which may
ultimately better integrate with CIDER once CIDER's middleware (cider-nrepl) is also adapted/ported.
- https://github.com/arcadia-unity/Arcadia/blob/master/Editor/NRepl.cs[Arcadia's nREPL]: A basic, but working nREPL implementation in C#.

If you need to use CIDER with ClojureCLR today Arcadia's nREPL is your only usable option. That being said - `clr.tools.nrepl` is a much
more sophisticated project and ideally we should get it over to the finish line.
An alternative to CIDER & a nREPL server is inf-clojure with ClojureCLR's stock socket REPL server.

== Usage

NOTE: Contributions welcome!

As `cider-jack-in` doesn't support ClojureCLR projects out-of-the-box currently, you'll need to start an nREPL server externally and
connect to it with `cider-connect`.
`cider-jack-in-universal` will jack into a clr.tools.nrepl server as long as a `deps-clr.edn` file
exists in the project directory, otherwise you may call `cider-jack-in-universal` with prefix
argument 6, by either `M-6` or `C-u 6` followed by `M-x cider-jack-in-universal`.

== Plans

In an ideal world we'll achieve the following objectives:

- out-of-the-box ClojureCLR support with `cider-jack-in`
- feature parity between Clojure's nREPL implementation and `clr.tools.nrepl` (the project can use some help)
- adapting `cider-nrepl` for ClojureCLR (some of its codebase is JVM-specific)

Expand Down
65 changes: 64 additions & 1 deletion lisp/cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,27 @@ By default we favor the project-specific shadow-cljs over the system-wide."
:safe #'stringp
:package-version '(cider . "1.14.0"))

(defcustom cider-clr-command
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should used the more descriptive naming "clojure-clr-cli" (or the shorter "clr-cli") instead of just "clr" here, in case at some point we want to add a different way to jack in for Clojure CLR projects. Although, I guess that's somewhat unlikely.

Copy link
Copy Markdown
Author

@rene-descartes2021 rene-descartes2021 Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should used the more descriptive naming "clojure-clr-cli" (or the shorter "clr-cli") instead of just "clr" here,

So cider-clojure-clr-cli-command in full? Anywhere else? I wasn't really sure how to name things I just tried to follow any patterns I could make out.

in case at some point we want to add a different way to jack in for Clojure CLR projects.

A different way being for Arcadia? That makes sense I think. I personally don't use Unity. Looking around in their issues/PRs I think they've made do just connecting with CIDER manually.

From what I see the commercial support for Arcadia collapsed and they suggested someone else fork it and maintain it.

Looking at the 3 public forks active on GitHub after Arcadia's last commit, they didn't last more than a year, and all had some nREPL changes/fixes [1][2][3].

Well that's a tangent just exploring ideas.

Maybe instead the features within the Arcadia fork of ClojureCLR will get de-fragmented back in/around ClojureCLR by a motivated person(s), I think that would be the best path rather than forking Arcadia... in their videos they described their rationale for the fork and I think it makes sense now to de-fragment. But I wouldn't worry about it till a motivated person shows up and comment.

So I won't worry about supporting a different way to jack into ClojureCLR or a variant of it. Upcoming Clojure LLVM/C++ dialect Jank might better suit their Unity and performance use-case more anyway... or maybe not, I'm not sure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see the commercial support for Arcadia collapsed and they suggested someone else fork it and maintain it.

Ah, I wasn't aware of this. I did remember that for a while that was the only way to run nREPL on .NET, but I guess things have changed since then.

But I was mostly think how with Clojure there were many different build tools over the years - I think the dotnet CLI itself is a fairly "new" development in the .NET community. I learned about it only when I started to play with F# last year. :D

"cljr"
"The command used to execute ClojureCLR."
:type 'string
:safe #'stringp
:package-version '(cider . "1.20.0"))

(defcustom cider-clr-parameters
"-X clojure.tools.nrepl/start-server!"
"Params passed to ClojureCLR to start an nREPL server via `cider-jack-in'."
:type 'string
:safe #'stringp
:package-version '(cider . "1.20.0"))

(defcustom cider-clr-nrepl-sha "a6bf822a5f72ec613f703eaf95420758591f2437"
"The version of clr.tools.nrepl injected on jack-in with ClojureCLR."
;; Unlike Maven, GitHub tags are not supported by Clojure
:type 'string
:safe #'stringp
:package-version '(cider . "1.20.0"))

(defcustom cider-jack-in-default nil
"The default tool to use when doing `cider-jack-in' outside a project.
This value is consulted when no identifying file types (e.g. project.clj
Expand All @@ -254,7 +275,8 @@ explicitly to skip the auto-detection."
(const gradle)
(const babashka)
(const nbb)
(const basilisp))
(const basilisp)
(const clr))
:safe #'symbolp
:package-version '(cider . "0.9.0"))

Expand All @@ -274,6 +296,7 @@ command when there is no ambiguity."
(const babashka)
(const nbb)
(const basilisp)
(const clr)
(const :tag "Always ask" nil))
:safe #'symbolp
:package-version '(cider . "0.13.0"))
Expand Down Expand Up @@ -757,6 +780,30 @@ Does so by concatenating PARAMS and DEPENDENCIES."
" "
params)))

(defun cider-clr-jack-in-dependencies (params dependencies &optional command)
"Create ClojureCLR clr.core.cli jack-in dependencies.
Does so by concatenating DEPENDENCIES, and PARAMS into a
suitable `cljr` invocation and quoting, also accounting for COMMAND if
provided."
(let* ((all-deps (thread-last dependencies
(cider--dedupe-deps)
(seq-map (lambda (dep)
(if (listp (cadr dep))
(format "%s {%s}"
(car dep)
(seq-reduce
(lambda (acc v)
(concat acc (format " :%s \"%s\" " (car v) (cdr v))))
(cadr dep)
""))
(format "%s {:git/sha \"%s\"}" (car dep) (cadr dep)))))))
(deps (format "{:deps {%s}}"
(string-join all-deps " ")))
(deps-quoted (cider--shell-quote-argument deps command)))
(format "-Sdeps %s %s"
deps-quoted
(if params (format " %s" params) ""))))

(defun cider-add-clojure-dependencies-maybe (dependencies)
"Return DEPENDENCIES with an added Clojure dependency if requested.
See also `cider-jack-in-auto-inject-clojure'."
Expand Down Expand Up @@ -816,6 +863,12 @@ COMMAND is the resolved jack-in command, used to handle PowerShell quoting."
(cider-add-clojure-dependencies-maybe cider-jack-in-dependencies)
(cider-jack-in-normalized-nrepl-middlewares)))

(defun cider--clr-inject-deps (params _project-type _command)
"Inject CIDER deps into PARAMS for a Gradle project."
(cider-clr-jack-in-dependencies
params
`(("io.github.clojure/clr.tools.nrepl" ,cider-clr-nrepl-sha))))

(defun cider-inject-jack-in-dependencies (params project-type &optional command)
"Return PARAMS with injected REPL dependencies for PROJECT-TYPE.
Looks up the tool's :inject-fn in `cider-jack-in-tools' and calls it with
Expand Down Expand Up @@ -881,6 +934,16 @@ with its nREPL middleware and dependencies."
:project-files '("basilisp.edn")
:universal-prefix-arg 5)

(cider-register-jack-in-tool 'clr
:command-var 'cider-clr-command
:params-var 'cider-clr-parameters
:project-files '("deps-clr.edn")
:resolver #'cider--resolve-prefix-command
:universal-prefix-arg 6
:jack-in-type 'clj
:cljs-repl-type 'nbb
:inject-fn #'cider--clr-inject-deps)


;;; ClojureScript REPL creation

Expand Down
62 changes: 62 additions & 0 deletions test/integration/integration-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,68 @@ If CLI-COMMAND is nil, then use the default."
(cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5)
(expect (member (process-status nrepl-proc) '(exit signal))))))))))

(it "to clr"
(with-cider-test-sandbox
(with-temp-dir temp-dir
;; Create a project in temp dir
(let* ((project-dir temp-dir)
(clr-edn (expand-file-name "deps-clr.edn" project-dir)))
(write-region "{}" nil clr-edn)

(with-temp-buffer
;; set default directory to temp project
(setq-local default-directory project-dir)

(let* (;; Get a gv reference so as to poll if the client has
;; connected to the nREPL server.
(client-is-connected* (cider-itu-nrepl-client-connected-ref-make!))

;; jack in and get repl buffer
;;
;; The numerical prefix arg for `clr` in
;; `cider-jack-in-universal-options' is 6.
(nrepl-proc (cider-jack-in-universal 6))
(nrepl-buf (process-buffer nrepl-proc)))

;; wait until the client has successfully connected to the
;; nREPL server.
(cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 20)

;; give it some time to setup the clj REPL
(cider-itu-poll-until (cider-repls 'clj nil) 60)

;; send command to the REPL, and push stdout/stderr to
;; corresponding eval-xxx variables.
(let ((repl-buffer (cider-current-repl))
(eval-err '())
(eval-out '()))
(expect repl-buffer :not :to-be nil)

;; send command to the REPL
(cider-interactive-eval
;; ask REPL to return a string that uniquely identifies it.
"(print :clr? (some? (. RuntimeInformation FrameworkDescription)))"
(lambda (return)
(nrepl-dbind-response
return
(out err)
(when err (push err eval-err))
(when out (push out eval-out)))) )

;; wait for a response to come back.
(cider-itu-poll-until (or eval-err eval-out) 20)

;; ensure there are no errors and response is as expected.
(expect eval-err :to-equal '())
(expect eval-out :to-equal '(":clr? true"))

;; exit the REPL.
(cider-quit repl-buffer)

;; wait for the REPL to exit
(cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5)
(expect (member (process-status nrepl-proc) '(exit signal))))))))))

(it "to Basilisp"
(with-cider-test-sandbox
(with-temp-dir temp-dir
Expand Down
Loading