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

jack-in support for the Basilisp Clojure dialect in Python #3683

Merged
merged 3 commits into from
May 29, 2024
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
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ jobs:
- run: npm install [email protected] -g
- run: npm install [email protected] -g

- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: |
pip install basilisp==0.1.0b2

- 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 @@ -12,6 +12,7 @@
- [#3624](https://github.com/clojure-emacs/cider/pull/3624): Support new `cider.clj-reload/reload` cider-nrepl middleware.
- adds `cider-ns-code-reload-tool` defcustom, defaulting to `'tools.namespace`.
- you can change it to `'clj-reload` to use [clj-reload](https://github.com/tonsky/clj-reload) instead of [tools.namespace](https://github.com/clojure/tools.namespace).
- [#3682](https://github.com/clojure-emacs/cider/issues/3682): Add `cider-jack-in` support for [Basilisp](https://github.com/basilisp-lang/basilisp) (Python)

### Changes

Expand Down
35 changes: 31 additions & 4 deletions cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
;; Maintainer: Bozhidar Batsov <[email protected]>
;; URL: https://www.github.com/clojure-emacs/cider
;; Version: 1.14.0-snapshot
;; Package-Requires: ((emacs "26") (clojure-mode "5.18.1") (parseedn "1.2.1") (queue "0.2") (spinner "1.7") (seq "2.22") (sesman "0.3.2") (transient "0.4.1"))
;; Package-Requires: ((emacs "26") (clojure-mode "5.19") (parseedn "1.2.1") (queue "0.2") (spinner "1.7") (seq "2.22") (sesman "0.3.2") (transient "0.4.1"))
;; Keywords: languages, clojure, cider

;; This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -273,6 +273,23 @@ By default we favor the project-specific shadow-cljs over the system-wide."
:safe #'stringp
:package-version '(cider . "1.6.0"))

(defcustom cider-basilisp-command
"basilisp"
"The command used to execute Basilisp.

If Basilisp is installed in a virtual environment, update this to the
full path of the Basilisp executable within that virtual environment."
:type 'string
:safe #'stringp
:package-version '(cider . "1.14.0"))

(defcustom cider-basilisp-parameters
"nrepl-server"
"Params passed to Basilisp to start an nREPL server via `cider-jack-in'."
:type 'string
:safe #'stringp
:package-version '(cider . "1.14.0"))

(make-obsolete-variable 'cider-lein-global-options 'cider-lein-parameters "1.8.0")
(make-obsolete-variable 'cider-boot-global-options 'cider-boot-parameters "1.8.0")
(make-obsolete-variable 'cider-clojure-cli-global-options 'cider-clojure-cli-parameters "1.8.0")
Expand All @@ -296,7 +313,8 @@ to Leiningen."
(const shadow-cljs)
(const gradle)
(const babashka)
(const nbb))
(const nbb)
(const basilisp))
:safe #'symbolp
:package-version '(cider . "0.9.0"))

Expand All @@ -316,6 +334,7 @@ command when there is no ambiguity."
(const gradle)
(const babashka)
(const nbb)
(const basilisp)
(const :tag "Always ask" nil))
:safe #'symbolp
:package-version '(cider . "0.13.0"))
Expand Down Expand Up @@ -380,7 +399,8 @@ Sub-match 1 must be the project path.")
'((clojure-cli (:prefix-arg 1 :cmd (:jack-in-type clj :project-type clojure-cli :edit-project-dir t)))
(lein (:prefix-arg 2 :cmd (:jack-in-type clj :project-type lein :edit-project-dir t)))
(babashka (:prefix-arg 3 :cmd (:jack-in-type clj :project-type babashka :edit-project-dir t)))
(nbb (:prefix-arg 4 :cmd (:jack-in-type cljs :project-type nbb :cljs-repl-type nbb :edit-project-dir t))))
(nbb (:prefix-arg 4 :cmd (:jack-in-type cljs :project-type nbb :cljs-repl-type nbb :edit-project-dir t)))
(basilisp (:prefix-arg 5 :cmd (:jack-in-type clj :project-type basilisp :edit-project-dir t))))
"The list of project tools that are supported by the universal jack in command.

Each item in the list consists of the tool name and its plist options.
Expand Down Expand Up @@ -412,6 +432,7 @@ The plist supports the following keys
('shadow-cljs cider-shadow-cljs-command)
('gradle cider-gradle-command)
('nbb cider-nbb-command)
('basilisp cider-basilisp-command)
(_ (user-error "Unsupported project type `%S'" project-type))))

(defcustom cider-enrich-classpath nil
Expand Down Expand Up @@ -476,6 +497,7 @@ Throws an error if PROJECT-TYPE is unknown."
;; relative path like "./gradlew" use locate file instead of checking
;; the exec-path
('gradle (cider--resolve-project-command cider-gradle-command))
('basilisp (cider--resolve-command cider-basilisp-command))
(_ (user-error "Unsupported project type `%S'" project-type))))

(defun cider-jack-in-global-options (project-type)
Expand All @@ -488,6 +510,7 @@ Throws an error if PROJECT-TYPE is unknown."
('shadow-cljs cider-shadow-cljs-global-options)
('gradle cider-gradle-global-options)
('nbb cider-nbb-global-options)
('basilisp nil)
(_ (user-error "Unsupported project type `%S'" project-type))))

(defun cider-jack-in-params (project-type)
Expand All @@ -504,6 +527,7 @@ Throws an error if PROJECT-TYPE is unknown."
('shadow-cljs cider-shadow-cljs-parameters)
('gradle cider-gradle-parameters)
('nbb cider-nbb-parameters)
('basilisp cider-basilisp-parameters)
(_ (user-error "Unsupported project type `%S'" project-type))))


Expand Down Expand Up @@ -963,6 +987,7 @@ middleware and dependencies."
global-opts
(unless (seq-empty-p global-opts) " ")
params))
('basilisp params)
(_ (error "Unsupported project type `%S'" project-type))))


Expand Down Expand Up @@ -1245,6 +1270,7 @@ you're working on."
(const :tag "Shadow w/o Server" shadow-select)
(const :tag "Krell" krell)
(const :tag "Nbb" nbb)
(const :tag "Basilisp" basilisp)
(const :tag "Custom" custom))
:safe #'symbolp
:package-version '(cider . "0.17.0"))
Expand Down Expand Up @@ -2038,7 +2064,8 @@ PROJECT-DIR defaults to current project."
(shadow-cljs . "shadow-cljs.edn")
(gradle . "build.gradle")
(gradle . "build.gradle.kts")
(nbb . "nbb.edn"))))
(nbb . "nbb.edn")
(basilisp . "basilisp.edn"))))
(delq nil
(mapcar (lambda (candidate)
(when (file-exists-p (cdr candidate))
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 @@ -152,6 +152,7 @@ The following Clojure build tools are supported so far
- kbd:[M-2 C-c C-x j u] jack-in using leiningen.
- 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.

Here is an example of how to bind kbd:[F12] for quickly bringing up a
babashka REPL:
Expand Down
104 changes: 104 additions & 0 deletions doc/modules/ROOT/pages/platforms/basilisp.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
= Basilisp Integration with CIDER
https://github.com/basilisp-lang/basilisp[basilisp]

since CIDER 1.14

== Overview

Basilisp aims to enable writing Clojure programs on Python with full Python interoperability. It is highly compatible with Clojure.

To install Basilisp, run:

$ pip install basilisp

== Usage

There are several ways to connect to Basilisp.

* kbd:[M-x cider-jack-in] and kbd:[M-5 M-x cider-jack-in-universal]

If you have created a `basilisp.edn` project file at your root of your project tree, you can jack in to the project `M-x cider-jack-in`. The `basilisp.edn` is similar to `deps.edn` for clojure-cli projects. It can be left empty just to mark the root of your project.

If you don't have or want a basilisp project file, you can use universal jack in with a numerical argument of 5:

- kbd:[M-5 M-x cider-jack-in-universal], or
- kbd:[M-5 C-c C-x j u], from within file in clojure-mode

(Note: an alternative to kbd:[M-5] is kbd:[C-u 5])

You can also bind the universal jack-in to Basilisp to a function to use as a shortcut, for example

[source,lisp]
----
(global-set-key (kbd "<f12>") (lambda ()
(interactive)
(cider-jack-in-universal 5)))
----

* kbd:[M-x cider-connect]

You can start its bundled nREPL server:

$ basilisp nrepl-server

and connect to it afterward using `M-x cider-connect`.

To see available options, type `basilisp nrepl-server -h` in a shell prompt.

== Configuration

The jack-in command can be configured via several defcustoms:

* `cider-basilisp-command` (default is `basilisp`).

If Basilisp is installed in a virtual environment, update this to the full path of the `basilisp` executable within that virtual environment.

* `cider-basilisp-parameters` (default is `nrepl-server`).

There at few ways to setup (custom) variables in Emacs

- https://www.gnu.org/software/emacs/manual/html_node/emacs/Easy-Customization.html[Examining and Setting Variables]

kbd:[C-h v cider-basilisp-command], and
kbd:[C-h v cider-basilisp-parameters]

- https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html[Per-Diretory Local Variables]

Uses `.dir-locals.el` to setup per mode variables. This file is typically stored at the root of the project.

For example, to set the path to the basilisp executable within a virtual environment

kbd:[M-x add-dir-local-variable]
Mode or subdirectory: `clojure-mode`
Add directory-local variable: `cider-basilisp-command`
Add cider-basilisp-command with value: `"c:/dev/venvs/312/Scripts/basilisp"`

This should result to updating or creating a `.dir-local.el` file like below

[source,lisp]
----
;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables")

((clojure-mode . ((cider-basilisp-command . "c:/dev/venvs/312/Scripts/basilisp"))))
----

- https://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html[Specifying File Variables]

It is best to put this in the top of your project's `basilisp.edn` file, and always jack-in from there

For example, setting `cider-basilisp-command` to start basilisp from within a virtual environment

kbd:[M-x add-dir-local-variable]
Add file-local variable: `cider-basilisp-command`
Add cider-basilisp-command with value: `"c:/dev/venvs/312/Scripts/basilisp"`

This will result in the following in `basilisp.edn`

[source,clojure]
----
;; Local Variables:
;; cider-basilisp-command: "c:/dev/venvs/312/Scripts/basilisp"
;; End:
{}
----
1 change: 1 addition & 0 deletions doc/modules/ROOT/pages/platforms/overview.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Right now CIDER the supports to some extent the following:
* xref:platforms/clojureclr.adoc[ClojureCLR]
* Lumo (via https://github.com/djblue/nrepl-cljs)
* xref:platforms/other_platforms.adoc[scittle, joyride & friends]
* xref:platforms/basilisp.adoc[Basilisp]

All of them are derived from Clojure, so supporting them didn't really require much work.

Expand Down
69 changes: 66 additions & 3 deletions test/integration/integration-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -183,16 +183,79 @@ 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 Basilisp"
(with-cider-test-sandbox
(with-temp-dir temp-dir
;; Create a project in temp dir
(let* ((project-dir temp-dir)
(basilisp-edn (expand-file-name "basilisp.edn" project-dir)))
(write-region "" nil basilisp-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
(nrepl-proc (cider-jack-in-clj '()))
(nrepl-buf (process-buffer nrepl-proc)))

;; wait until the client successfully connects to the nREPL
;; server. A high timeout is set because Basilisp usually needs to
;; be compiled the first time it is run.
(cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 60)

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

;; 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 :basilisp? (some? sys/version))"
(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) 5)

;; ensure there are no errors and response is as expected.
(expect eval-err :to-equal '())
;; The Basilisp nREPL server sends the message in three separate
;; pieces, which is likely an area for improvement on the
;; Basilisp side.
(expect eval-out :to-equal '("true" " " ":basilisp?"))

;; 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 clojure tools cli (default)"
(jack-in-clojure-cli-test nil))

(when (eq system-type 'windows-nt)
(it "to clojure tools cli (alternative pwsh)"
(jack-in-clojure-cli-test "pwsh")))

(when (eq system-type 'windows-nt)
(it "to clojure tools cli (alternative deps.exe)"
(jack-in-clojure-cli-test "deps.exe")))
(when (eq system-type 'windows-nt)
(it "to clojure tools cli (alternative deps.exe)"
(jack-in-clojure-cli-test "deps.exe")))

(it "to leiningen"
(with-cider-test-sandbox
Expand Down
Loading