Skip to content

Commit

Permalink
jack-in support for the Basilisp Clojure dialect in Python
Browse files Browse the repository at this point in the history
  • Loading branch information
ikappaki committed May 27, 2024
1 parent 8be2bcf commit 4fe1e80
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 7 deletions.
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.0b1
- name: Test integration
run: |
# The tests occasionally fail on macos&win in what is seems to
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
- 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

- [#3626](https://github.com/clojure-emacs/cider/issues/3626): `cider-ns-refresh`: jump to the relevant file/line on errors.
Expand Down
46 changes: 42 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,13 +273,38 @@ 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-global-options
nil
"Command line options used to execute Basilisp."
: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")
(make-obsolete-variable 'cider-shadow-cljs-global-options 'cider-shadow-cljs-parameters "1.8.0")
(make-obsolete-variable 'cider-gradle-global-options 'cider-gradle-parameters "1.8.0")
(make-obsolete-variable 'cider-babashka-global-options 'cider-babashka-parameters "1.8.0")
(make-obsolete-variable 'cider-nbb-global-options 'cider-nbb-parameters "1.8.0")
(make-obsolete-variable 'cider-basilip-global-options 'cider-basilisp-parameters "1.8.0")

(defcustom cider-jack-in-default
(if (executable-find "clojure") 'clojure-cli 'lein)
Expand All @@ -296,7 +321,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 +342,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 +407,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 +440,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 +505,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 +518,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 cider-basilisp-global-options)
(_ (user-error "Unsupported project type `%S'" project-type))))

(defun cider-jack-in-params (project-type)
Expand All @@ -504,6 +535,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 +995,10 @@ middleware and dependencies."
global-opts
(unless (seq-empty-p global-opts) " ")
params))
('basilisp (concat
global-opts
(unless (seq-empty-p global-opts) " ")
params))
(_ (error "Unsupported project type `%S'" project-type))))


Expand Down Expand Up @@ -1245,6 +1281,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 +2075,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
102 changes: 102 additions & 0 deletions doc/modules/ROOT/pages/platforms/basilisp.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
= Basilisp Integration with CIDER
https://github.com/basilisp-lang/basilisp[basilisp]

== 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: "d:/dev/venvs/cider/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
73 changes: 70 additions & 3 deletions test/integration/integration-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -183,16 +183,83 @@ 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"
;; temporarily suspended on MS-Windows until the following is released on PyPi
;;
;; https://github.com/basilisp-lang/basilisp/pull/866
(assume (not (eq system-type 'windows-nt)) "temporarily skipping on MS-Windows ...")
(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

0 comments on commit 4fe1e80

Please sign in to comment.