Skip to content

Support nbb as native cljs repl #3275

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

Merged
merged 4 commits into from
Dec 16, 2022
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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ jobs:
with:
node-version: 16
- run: npm install [email protected] -g
- run: npm install [email protected] -g

- name: Test integration
run: |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New features

- [#3278](https://github.com/clojure-emacs/cider/pull/3278) Introduce integration tests, which also fix a long standing issue with orphaned process on MS-Windows by contracting `taskkill`, if available, to properly kill the nREPL server process tree.
- [#3061](https://github.com/clojure-emacs/cider/issues/3061): Add support for nbb.
- [#3249](https://github.com/clojure-emacs/cider/pull/3249): Add support for Clojure Spec 2.
- [#3247](https://github.com/clojure-emacs/cider/pull/3247): Add the `cider-stacktrace-analyze-at-point` and `cider-stacktrace-analyze-in-region` commands to view printed exceptions in the stacktrace inspector.

Expand Down
131 changes: 91 additions & 40 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: http://www.github.com/clojure-emacs/cider
;; Version: 1.6.0-snapshot
;; Package-Requires: ((emacs "26") (clojure-mode "5.15.1") (parseedn "1.0.6") (queue "0.2") (spinner "1.7") (seq "2.22") (sesman "0.3.2"))
;; Package-Requires: ((emacs "26") (clojure-mode "5.16.0") (parseedn "1.0.6") (queue "0.2") (spinner "1.7") (seq "2.22") (sesman "0.3.2"))
;; Keywords: languages, clojure, cider

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

(defcustom cider-nbb-command
"nbb"
"The command used to execute nbb."
:type 'string
:safe #'stringp
:package-version '(cider . "1.3.0"))

(defcustom cider-nbb-global-options
nil
"Command line options used to execute nbb."
:type 'string
:safe #'stringp
:package-version '(cider . "1.3.0"))

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

(defcustom cider-jack-in-default (if (executable-find "clojure") 'clojure-cli 'lein)
"The default tool to use when doing `cider-jack-in' outside a project.
This value will only be consulted when no identifying file types, i.e.
Expand All @@ -243,7 +264,8 @@ to Leiningen."
(const clojure-cli)
(const shadow-cljs)
(const gradle)
(const babashka))
(const babashka)
(const nbb))
:safe #'symbolp
:package-version '(cider . "0.9.0"))

Expand All @@ -262,6 +284,7 @@ command when there is no ambiguity."
(const shadow-cljs)
(const gradle)
(const babashka)
(const nbb)
(const :tag "Always ask" nil))
:safe #'symbolp
:package-version '(cider . "0.13.0"))
Expand Down Expand Up @@ -337,6 +360,7 @@ Sub-match 1 must be the project path.")
('babashka cider-babashka-command)
('shadow-cljs cider-shadow-cljs-command)
('gradle cider-gradle-command)
('nbb cider-nbb-command)
(_ (user-error "Unsupported project type `%S'" project-type))))

(defun cider-jack-in-resolve-command (project-type)
Expand All @@ -357,6 +381,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))
('nbb (cider--resolve-command cider-nbb-command))
(_ (user-error "Unsupported project type `%S'" project-type))))

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

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


Expand Down Expand Up @@ -766,6 +793,10 @@ dependencies."
(cider-add-clojure-dependencies-maybe
cider-jack-in-dependencies)
(cider-jack-in-normalized-nrepl-middlewares)))
('nbb (concat
global-opts
(unless (seq-empty-p global-opts) " ")
params))
(_ (error "Unsupported project type `%S'" project-type))))


Expand Down Expand Up @@ -993,30 +1024,35 @@ The supplied string will be wrapped in a do form if needed."
(def config (edn/read-string (slurp (io/file \"build.edn\"))))
(apply cider.piggieback/cljs-repl (krell.repl/repl-env) (mapcat identity config))"
cider-check-krell-requirements)
;; native cljs repl, no form required.
(nbb)
(custom cider-custom-cljs-repl-init-form nil))
"A list of supported ClojureScript REPLs.

For each one we have its name, the form we need to evaluate in a Clojure
REPL to start the ClojureScript REPL and functions to verify their requirements.
For each one we have its name, and then, if the repl is not a native
ClojureScript REPL, the form we need to evaluate in a Clojure REPL to
switch to the ClojureScript REPL and functions to verify their
requirements.

The form should be either a string or a function producing a string.")
The form, if any, should be either a string or a function producing a
string.")

(defun cider-register-cljs-repl-type (type init-form &optional requirements-fn)
(defun cider-register-cljs-repl-type (type &optional init-form requirements-fn)
"Register a new ClojureScript REPL type.

Types are defined by the following:

- TYPE - symbol identifier that will be used to refer to the REPL type
- INIT-FORM - string or function (symbol) producing string
- INIT-FORM - (optional) string or function (symbol) producing string
- REQUIREMENTS-FN - function to check whether the REPL can be started.
This param is optional.

All this function does is modifying `cider-cljs-repl-types'.
It's intended to be used in your Emacs config."
(unless (symbolp type)
(user-error "The REPL type must be a symbol"))
(unless (or (stringp init-form) (symbolp init-form))
(user-error "The init form must be a string or a symbol referring to a function"))
(unless (or (null init-form) (stringp init-form) (symbolp init-form))
(user-error "The init form must be a string or a symbol referring to a function or nil"))
(unless (or (null requirements-fn) (symbolp requirements-fn))
(user-error "The requirements-fn must be a symbol referring to a function"))
(add-to-list 'cider-cljs-repl-types (list type init-form requirements-fn)))
Expand All @@ -1036,6 +1072,7 @@ you're working on."
(const :tag "Shadow" shadow)
(const :tag "Shadow w/o Server" shadow-select)
(const :tag "Krell" krell)
(const :tag "Nbb" nbb)
(const :tag "Custom" custom))
:safe #'symbolp
:package-version '(cider . "0.17.0"))
Expand All @@ -1054,15 +1091,16 @@ DEFAULT is the default ClojureScript REPL to offer in completion."
(or default (car cider--select-cljs-repl-history))))))

(defun cider-cljs-repl-form (repl-type)
"Get the cljs REPL form for REPL-TYPE."
(if-let* ((repl-form (cadr (seq-find
(lambda (entry)
(eq (car entry) repl-type))
cider-cljs-repl-types))))
;; repl-form can be either a string or a function producing a string
(if (symbolp repl-form)
(funcall repl-form)
repl-form)
"Get the cljs REPL form for REPL-TYPE, if any."
(if-let* ((repl-type-info (seq-find
(lambda (entry)
(eq (car entry) repl-type))
cider-cljs-repl-types)))
(when-let ((repl-form (cadr repl-type-info)))
;; repl-form can be either a string or a function producing a string
(if (symbolp repl-form)
(funcall repl-form)
repl-form))
(user-error "No ClojureScript REPL type %s found. Please make sure that `cider-cljs-repl-types' has an entry for it" repl-type)))

(defun cider-verify-cljs-repl-requirements (&optional repl-type)
Expand Down Expand Up @@ -1258,8 +1296,7 @@ server buffer, in which case a new session for that server is created."
(cider--update-cljs-type)
(cider--update-cljs-init-function)
(plist-put :session-name ses-name)
(plist-put :repl-type 'cljs)
(plist-put :cider-repl-cljs-upgrade-pending t)))))
(plist-put :repl-type 'cljs)))))

;;;###autoload
(defun cider-connect-clj (&optional params)
Expand Down Expand Up @@ -1293,8 +1330,7 @@ parameters regardless of their supplied or default values."
(cider--update-cljs-type)
(cider--update-cljs-init-function)
(plist-put :session-name nil)
(plist-put :repl-type 'cljs)
(plist-put :cider-repl-cljs-upgrade-pending t))))
(plist-put :repl-type 'cljs))))

;;;###autoload
(defun cider-connect-clj&cljs (params &optional soft-cljs-start)
Expand Down Expand Up @@ -1466,27 +1502,41 @@ non-nil, don't start if ClojureScript requirements are not met."
(plist-put :port (cdr endpoint)))))))

(defun cider--update-cljs-init-function (params)
"Update PARAMS :repl-init-function for cljs connections."
"Update repl type and any init PARAMS for cljs connections.

The updated params are:

:cider-repl-cljs-upgrade-pending nil if it is a cljs REPL, or t
when the init form is required to be sent to the REPL to switch
over to cljs.

:repl-init-form The form that can switch the REPL over to cljs.

:repl-init-function The fn that switches the REPL over to cljs."
(with-current-buffer (or (plist-get params :--context-buffer)
(current-buffer))
(let* ((cljs-type (plist-get params :cljs-repl-type))
(repl-init-form (cider-cljs-repl-form cljs-type)))
(thread-first
params
(plist-put :repl-init-function
(lambda ()
(cider--check-cljs cljs-type)
;; FIXME: ideally this should be done in the state handler
(setq-local cider-cljs-repl-type cljs-type)
(cider-nrepl-send-request
(list "op" "eval"
"ns" (cider-current-ns)
"code" repl-init-form)
(cider-repl-handler (current-buffer)))
(when (and (buffer-live-p nrepl-server-buffer)
cider-offer-to-open-cljs-app-in-browser)
(cider--offer-to-open-app-in-browser nrepl-server-buffer))))
(plist-put :repl-init-form repl-init-form)))))
(if (null repl-init-form)
(plist-put params :cider-repl-cljs-upgrade-pending nil)

(thread-first
params
(plist-put :cider-repl-cljs-upgrade-pending t)
(plist-put :repl-init-function
(lambda ()
(cider--check-cljs cljs-type)
;; FIXME: ideally this should be done in the state handler
(setq-local cider-cljs-repl-type cljs-type)
(cider-nrepl-send-request
(list "op" "eval"
"ns" (cider-current-ns)
"code" repl-init-form)
(cider-repl-handler (current-buffer)))
(when (and (buffer-live-p nrepl-server-buffer)
cider-offer-to-open-cljs-app-in-browser)
(cider--offer-to-open-app-in-browser nrepl-server-buffer))))
(plist-put :repl-init-form repl-init-form))))))

(defun cider--check-existing-session (params)
"Ask for confirmation if a session with similar PARAMS already exists.
Expand Down Expand Up @@ -1666,7 +1716,8 @@ PROJECT-DIR defaults to current project."
(babashka . "bb.edn")
(shadow-cljs . "shadow-cljs.edn")
(gradle . "build.gradle")
(gradle . "build.gradle.kts"))))
(gradle . "build.gradle.kts")
(nbb . "nbb.edn"))))
(delq nil
(mapcar (lambda (candidate)
(when (file-exists-p (cdr candidate))
Expand Down
1 change: 1 addition & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Alternative Platforms
** xref:platforms/overview.adoc[Overview]
** xref:platforms/babashka.adoc[Babashka]
** xref:platforms/nbb.adoc[Nbb]
** xref:platforms/scittle_and_friends.adoc[Scittle and Friends]
* Using CIDER
** xref:usage/interactive_programming.adoc[Interactive Programming]
Expand Down
13 changes: 13 additions & 0 deletions doc/modules/ROOT/pages/platforms/nbb.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
= https://github.com/babashka/nbb[nbb]

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

It is highly compatible with ClojureScript, so it works with CIDER out of the box.

You can either jack in to an nbb project with `M-x clojure-jack-in-cljs`.

or start its bundled nREPL server:

$ nbb nrepl-server

and connect to it afterwards using `M-x cider-connect-cljs`.
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 @@ -28,6 +28,7 @@ Right now CIDER the supports to some extent the following:
* Babashka
* ClojureCLR (via Arcadia's nREPL server)
* Lumo (via https://github.com/djblue/nrepl-cljs)
* Nbb

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

Expand Down
65 changes: 65 additions & 0 deletions test/cider-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,71 @@
(expect (cider--resolve-project-command "command")
:to-equal (shell-quote-argument "/bin/command"))))

(describe "cider-connect-sibling-cljs"
;; restore:
;; - `cider-cljs-repl-types` changed by `cider-register-cljs-repl-type`.
:var (-cider-cljs-repl-types)
(before-all
(setq -cider-cljs-repl-types cider-cljs-repl-types))
(after-each
(setq cider-cljs-repl-types -cider-cljs-repl-types))

(describe "sets nrepl client local vars correctly"
(it "for nbb project"
(let* ((server-process (nrepl-start-mock-server-process))
(server-buffer (process-buffer server-process))
(client-buffer (cider-connect-sibling-cljs
'(:cljs-repl-type nbb) server-buffer)))
;; native cljs REPL
(expect (buffer-local-value 'cider-repl-type client-buffer)
:to-equal 'cljs)
(expect (buffer-local-value 'cider-repl-cljs-upgrade-pending client-buffer)
:to-equal nil)
(expect (buffer-local-value 'cider-repl-init-function client-buffer)
:to-be nil)
(delete-process (get-buffer-process client-buffer))))
(it "for shadow project"
(let* ((cider-shadow-default-options "a-shadow-alias")
(server-process (nrepl-start-mock-server-process))
(server-buffer (process-buffer server-process))
(client-buffer (cider-connect-sibling-cljs
'(:cljs-repl-type shadow) server-buffer)))
;; starts as clj REPL and requires a form to switch over to cljs
(expect (buffer-local-value 'cider-repl-type client-buffer)
:to-equal 'cljs)
(expect (buffer-local-value 'cider-repl-cljs-upgrade-pending client-buffer)
:to-equal t)
(expect (buffer-local-value 'cider-repl-init-function client-buffer)
:not :to-be nil)
(delete-process (get-buffer-process client-buffer))))
(it "for a custom cljs REPL type project"
(cider-register-cljs-repl-type 'native-cljs)
(let* ((server-process (nrepl-start-mock-server-process))
(server-buffer (process-buffer server-process))
(client-buffer (cider-connect-sibling-cljs
'(:cljs-repl-type native-cljs)
server-buffer)))
(expect (buffer-local-value 'cider-repl-type client-buffer)
:to-equal 'cljs)
(expect (buffer-local-value 'cider-repl-cljs-upgrade-pending client-buffer)
:to-equal nil)
(delete-process (get-buffer-process client-buffer))))
(it "for a custom REPL type project that needs to switch to cljs"
(cider-register-cljs-repl-type
'not-cljs-initially "(form-to-switch-to-cljs-repl)")
(let* ((server-process (nrepl-start-mock-server-process))
(server-buffer (process-buffer server-process))
(client-buffer (cider-connect-sibling-cljs
'(:cljs-repl-type not-cljs-initially)
server-buffer)))
(expect (buffer-local-value 'cider-repl-type client-buffer)
:to-equal 'cljs)
(expect (buffer-local-value 'cider-repl-cljs-upgrade-pending client-buffer)
:to-equal t)
(expect (buffer-local-value 'cider-repl-init-function client-buffer)
:not :to-be nil)
(delete-process (get-buffer-process client-buffer))))))

(provide 'cider-tests)

;;; cider-tests.el ends here
Loading