Skip to content

Commit ec31a46

Browse files
authored
[Fix #3179] Introduce cider-jack-in-universal command (#3300)
It supports jacking-in without a project from a set of pre-configured Clojure project tools. The new command can also be used for both Clojure and ClojureScript (and potentially other platforms down the road).
1 parent 42a8a0c commit ec31a46

File tree

4 files changed

+281
-7
lines changed

4 files changed

+281
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [#3262](https://github.com/clojure-emacs/cider/issues/3262): Add navigation functionality to `npfb` keys inside the data inspector's buffer.
99
- [#3310](https://github.com/clojure-emacs/cider/issues/3310): Add ability to use custom coordinates in jack-in-dependencies.
1010
* [#766](https://github.com/clojure-emacs/cider-nrepl/issues/766): Complete local bindings for ClojureScript files.
11+
- [#3179](https://github.com/clojure-emacs/cider/issues/3179): Introduce `cider-jack-in-universal` to support jacking-in without a project from a set of pre-configured Clojure project tools.
1112

1213
### Changes
1314

cider.el

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
(require 'cider-debug)
8787
(require 'cider-util)
8888

89+
(require 'cl-lib)
8990
(require 'tramp-sh)
9091
(require 'subr-x)
9192
(require 'seq)
@@ -346,6 +347,26 @@ Sub-match 1 must be the project path.")
346347
(defvar cider-host-history nil
347348
"Completion history for connection hosts.")
348349

350+
(defvar cider-jack-in-universal-options
351+
'((clojure-cli (:prefix-arg 1 :cmd (:jack-in-type clj :project-type babashka :edit-project-dir t)))
352+
(lein (:prefix-arg 2 :cmd (:jack-in-type clj :project-type clojure-cli :edit-project-dir t)))
353+
(babashka (:prefix-arg 3 :cmd (:jack-in-type clj :project-type lein :edit-project-dir t)))
354+
(nbb (:prefix-arg 4 :cmd (:jack-in-type cljs :project-type nbb :cljs-repl-type nbb :edit-project-dir t))))
355+
"The list of project tools that are supported by the universal jack in command.
356+
357+
Each item in the list consists of the tool name and its plist options.
358+
359+
The plist supports the following keys
360+
361+
- :prefix-arg the numerical prefix arg to use to jack in to the tool.
362+
363+
- :cmd a plist of instructions how to invoke the jack in command, with keys
364+
365+
- :jack-in-type 'clj to start a clj repl and 'cljs for a cljs repl.
366+
367+
- &rest the same set of params supported by the `cider-jack-in-clj' and
368+
`cider-jack-in-cljs' commands.")
369+
349370
;;;###autoload
350371
(defun cider-version ()
351372
"Display CIDER's version."
@@ -1169,6 +1190,7 @@ nil."
11691190
(define-key map (kbd "j j") #'cider-jack-in-clj)
11701191
(define-key map (kbd "j s") #'cider-jack-in-cljs)
11711192
(define-key map (kbd "j m") #'cider-jack-in-clj&cljs)
1193+
(define-key map (kbd "j u") #'cider-jack-in-universal)
11721194
(define-key map (kbd "C-j j") #'cider-jack-in-clj)
11731195
(define-key map (kbd "C-j s") #'cider-jack-in-cljs)
11741196
(define-key map (kbd "C-j m") #'cider-jack-in-clj&cljs)
@@ -1393,9 +1415,15 @@ non-nil, don't start if ClojureScript requirements are not met."
13931415
(t params)))
13941416

13951417
(defun cider--update-project-dir (params)
1396-
"Update :project-dir in PARAMS."
1418+
"Update :project-dir in PARAMS.
1419+
1420+
Params is a plist with the following keys (non-exhaustive)
1421+
1422+
:edit-project-dir prompt (optional) ask user to confirm the project root
1423+
directory."
13971424
(let* ((params (cider--update-do-prompt params))
1398-
(proj-dir (if (plist-get params :do-prompt)
1425+
(proj-dir (if (or (plist-get params :do-prompt)
1426+
(plist-get params :edit-project-dir))
13991427
(read-directory-name "Project: "
14001428
(clojure-project-dir (cider-current-dir)))
14011429
(plist-get params :project-dir)))
@@ -1460,10 +1488,17 @@ non-nil, don't start if ClojureScript requirements are not met."
14601488
(format "-encodedCommand %s" (base64-encode-string utf-16le-command t))))
14611489

14621490
(defun cider--update-jack-in-cmd (params)
1463-
"Update :jack-in-cmd key in PARAMS."
1491+
"Update :jack-in-cmd key in PARAMS.
1492+
1493+
PARAMS is a plist with the following keys (non-exhaustive list)
1494+
1495+
:project-type optional, the project type to create the command for; see
1496+
`cider-jack-in-command' for the list of valid types)."
14641497
(let* ((params (cider--update-do-prompt params))
14651498
(project-dir (plist-get params :project-dir))
1466-
(project-type (cider-project-type project-dir))
1499+
(params-project-type (plist-get params :project-type))
1500+
(project-type (or params-project-type
1501+
(cider-project-type project-dir)))
14671502
(command (cider-jack-in-command project-type))
14681503
(command-resolved (cider-jack-in-resolve-command project-type))
14691504
(command-global-opts (cider-jack-in-global-options project-type))
@@ -1484,7 +1519,8 @@ non-nil, don't start if ClojureScript requirements are not met."
14841519
(eq cider-allow-jack-in-without-project t)
14851520
(and (null project-dir)
14861521
(eq cider-allow-jack-in-without-project 'warn)
1487-
(y-or-n-p "Are you sure you want to run `cider-jack-in' without a Clojure project? ")))
1522+
(or params-project-type
1523+
(y-or-n-p "Are you sure you want to run `cider-jack-in' without a Clojure project? "))))
14881524
(let ((cmd (format "%s %s" command-resolved (if (or (string-equal command "powershell")
14891525
(string-equal command "pwsh"))
14901526
(cider--powershell-encode-command cmd-params)
@@ -1767,6 +1803,55 @@ PROJECT-DIR defaults to the current project."
17671803
;; 0.18, therefore the need for `cider-maybe-intern'
17681804
(t (cider-maybe-intern cider-jack-in-default)))))
17691805

1806+
;;;###autoload
1807+
(defun cider-jack-in-universal (arg)
1808+
"Start and connect to an nREPL server for the current project or ARG project id.
1809+
1810+
If a project is found in current dir, call `cider-jack-in' passing ARG as
1811+
first parameter, of which see. Otherwise, ask user which project type to
1812+
start an nREPL server and connect to without a project.
1813+
1814+
But if invoked with a numeric prefix ARG, then start an nREPL server for
1815+
the project type denoted by ARG number and connect to it, even if there is
1816+
no project for it in the current dir.
1817+
1818+
The supported project tools and their assigned numeric prefix ids are
1819+
sourced from `cider-jack-in-universal-options', of which see.
1820+
1821+
You can pass a numeric prefix argument n with `M-n` or `C-u n`.
1822+
1823+
For example, to jack in to leiningen which is assigned to prefix arg 2 type
1824+
1825+
M-2 \\[cider-jack-in-universal]."
1826+
(interactive "P")
1827+
(let ((cpt (clojure-project-dir (cider-current-dir))))
1828+
(if (or (integerp arg) (null cpt))
1829+
(let* ((project-types-available (mapcar #'car cider-jack-in-universal-options))
1830+
(project-type (if (null arg)
1831+
(intern (completing-read
1832+
"No project found in current dir, select project type to jack in: "
1833+
project-types-available
1834+
nil t))
1835+
1836+
(or (seq-some (lambda (elt)
1837+
(cl-destructuring-bind
1838+
(project-type (&key prefix-arg &allow-other-keys)) elt
1839+
(when (= arg prefix-arg)
1840+
project-type)))
1841+
cider-jack-in-universal-options)
1842+
(error ":cider-jack-in-universal :unsupported-prefix-argument %S :no-such-project"
1843+
arg))))
1844+
(project-options (cadr (seq-find (lambda (elt) (equal project-type (car elt)))
1845+
cider-jack-in-universal-options)))
1846+
(jack-in-opts (plist-get project-options :cmd))
1847+
(jack-in-type (plist-get jack-in-opts :jack-in-type)))
1848+
(pcase jack-in-type
1849+
('clj (cider-jack-in-clj jack-in-opts))
1850+
('cljs (cider-jack-in-cljs jack-in-opts))
1851+
(_ (error ":cider-jack-in-universal :jack-in-type-unsupported %S" jack-in-type))))
1852+
1853+
(cider-jack-in-clj arg))))
1854+
17701855

17711856
;; TODO: Implement a check for command presence over tramp
17721857
(defun cider--resolve-command (command)

doc/modules/ROOT/pages/basics/up_and_running.adoc

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,57 @@ but was subsequently switched to `clj`, Clojure's basic startup command.
125125
TIP: You can set `cider-allow-jack-in-without-project` to `t` if you'd like to
126126
disable the warning displayed when jacking-in outside a project.
127127

128+
==== Universal jack-in
129+
130+
`cider-jack-in-universal` kbd:[C-c C-x j u] is another way to quickly
131+
jack in without a project choosing from a list of pre-configured
132+
Clojure build tools. When this command is called from outside of a
133+
project, the user is given the option to select to jack in with one of
134+
the pre-configured tools, as well as to confirm the root directory to
135+
use as a base. If the command is called from within a project
136+
directory, it behaves exactly the same as `cider-jack-in` does.
137+
138+
It utilizes Emacs's
139+
https://www.gnu.org/software/emacs/manual/html_node/elisp/Prefix-Command-Arguments.html[numeric
140+
prefix arguments] to quickly jack in to a project type. Numeric prefix
141+
arguments can be set with the Meta key followed by a number.
142+
143+
The following clojure build tools are supported so far
144+
145+
- kbd:[M-1 C-c C-x j u] jack-in using clojure-cli.
146+
- kbd:[M-2 C-c C-x j u] jack-in using leiningen.
147+
- kbd:[M-3 C-c C-x j u] jack-in using babashka.
148+
- kbd:[M-4 C-c C-x j u] jack-in using nbb.
149+
150+
Here is an example of how to bind F12 for quickly bringing up a
151+
babashka REPL:
152+
153+
[source,lisp]
154+
----
155+
(global-set-key (kbd "<f12>") (lambda ()
156+
(interactive)
157+
(cider-jack-in-universal 3)))
158+
----
159+
160+
The list of available build tools to consider is configured in
161+
`cider-jack-in-universal-options`. Each element of the list consists
162+
of the tool name and its setup options. Taking `nbb` as an example
163+
from the list:
164+
165+
[source,lisp]
166+
----
167+
(nbb (:prefix-arg 4 :cmd (:jack-in-type cljs :project-type nbb :cljs-repl-type nbb :edit-project-dir t)))
168+
----
169+
170+
with
171+
172+
. `:prefix-arg` assigns the `nbb` tool name a numerical argument prefix of 4.
173+
. `:cmd` how to invoke the command.
174+
.. `:jack-in-type` use a `cljs` repl.
175+
.. `:project-type` use `nbb` (see `jack-in-command`) to bring up the nREPL server.
176+
.. `:cljs-repl-type` client uses the `nbb` cljs repl type (see `cider-cljs-repl-types`) to initialize server.
177+
.. `:edit-project-dir` ask the user to confirm root directory to use as base.
178+
128179
=== Customizing the Jack-in Command Behavior
129180

130181
You can use kbd:[C-u M-x] `cider-jack-in` kbd:[RET] to

test/integration/integration-tests.el

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@
263263
(cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 5)
264264

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

268268
;; send command to the REPL, and push stdout/stderr to
269269
;; corresponding eval-xxx variables.
@@ -393,7 +393,144 @@
393393
(with-temp-buffer
394394
(insert-file-contents "nrepl-mdlw-log.log")
395395
(message ":ikappaki/nrepl-mdlw-log-dump\n%s\n" (buffer-string)))
396-
(message ":!nrepl-mdlw-log-found"))))))))))
396+
(message ":!nrepl-mdlw-log-found")))))))))
397+
398+
;; jacking in without a current project
399+
;;
400+
(it "no project, user choice to nbb"
401+
(with-cider-test-sandbox
402+
(with-temp-dir temp-dir
403+
;; setup empty project dir
404+
(let* ((project-dir temp-dir))
405+
;; fake user input
406+
(spy-on 'completing-read
407+
:and-call-fake (lambda (prompt _collection &optional _predicate _require-match
408+
initial-input _hist _def _inherit-input-method)
409+
(pcase prompt
410+
;; select nbb
411+
("No project found in current dir, select project type to jack in: "
412+
"nbb")
413+
;; project src directory, use suggested
414+
("Project: " initial-input)
415+
(_ (error ":integration-test-unsupported-prompt-error %S" prompt)))))
416+
417+
(with-temp-buffer
418+
;; set default directory to temp project
419+
(setq-local default-directory project-dir)
420+
421+
(let* (;; Get a gv reference so as to poll if the client has
422+
;; connected to the nREPL server.
423+
(client-is-connected* (cider-itu-nrepl-client-connected-ref-make!))
424+
425+
;; jack in and get repl buffer
426+
(nrepl-proc (cider-jack-in-universal '()))
427+
(nrepl-buf (process-buffer nrepl-proc)))
428+
429+
;; wait until the client has successfully connected to the
430+
;; nREPL server.
431+
(cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 5)
432+
433+
;; give it some time to setup the clj REPL
434+
(cider-itu-poll-until (cider-repls 'cljs nil) 5)
435+
436+
;; send command to the REPL, and push stdout/stderr to
437+
;; corresponding eval-xxx variables.
438+
(let ((repl-buffer (cider-current-repl))
439+
(eval-err '())
440+
(eval-out '()))
441+
(expect repl-buffer :not :to-be nil)
442+
443+
;; send command to the REPL
444+
(cider-interactive-eval
445+
;; ask REPL to return a string that uniquely identifies it.
446+
"(print :nbb? (some? (nbb.core/version)))"
447+
(lambda (return)
448+
(nrepl-dbind-response
449+
return
450+
(out err)
451+
(when err (push err eval-err))
452+
(when out (push out eval-out)))) )
453+
454+
;; wait for a response to come back.
455+
(cider-itu-poll-until (or eval-err eval-out) 5)
456+
457+
;; ensure there are no errors and response is as expected.
458+
(expect eval-err :to-equal '())
459+
(expect eval-out :to-equal '(":nbb? true"))
460+
461+
;; exit the REPL.
462+
(cider-quit repl-buffer)
463+
464+
;; wait for the REPL to exit
465+
(cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5)
466+
(expect (member (process-status nrepl-proc) '(exit signal))))))))))
467+
468+
(it "no project, numeric prefix argument, to leiningen"
469+
(with-cider-test-sandbox
470+
(with-temp-dir temp-dir
471+
;; setup empty dir
472+
(let* ((project-dir temp-dir))
473+
;; fake user input
474+
(spy-on 'completing-read
475+
:and-call-fake (lambda (prompt _collection &optional _predicate _require-match
476+
initial-input _hist _def _inherit-input-method)
477+
(pcase prompt
478+
;; project src directory
479+
("Project: " initial-input)
480+
(_ (error ":integration-test-unsupported-prompt-error %S" prompt)))))
481+
(with-temp-buffer
482+
;; set default directory to temp project
483+
(setq-local default-directory project-dir)
484+
485+
(let* (;; Get a gv reference so as to poll if the client has
486+
;; connected to the nREPL server.
487+
(client-is-connected* (cider-itu-nrepl-client-connected-ref-make!))
488+
489+
;; jack in and get repl buffer.
490+
;;
491+
;; The numerical prefix arg for `lein` in
492+
;; `cider-jack-in-universal-options' is 2.
493+
(nrepl-proc (cider-jack-in-universal 2))
494+
(nrepl-buf (process-buffer nrepl-proc)))
495+
496+
;; wait until the client has successfully connected to the
497+
;; nREPL server.
498+
(cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 90)
499+
500+
;; give it some time to setup the clj REPL
501+
(cider-itu-poll-until (cider-repls 'clj nil) 90)
502+
503+
;; send command to the REPL, and push stdout/stderr to
504+
;; corresponding eval-xxx variables.
505+
(let ((repl-buffer (cider-current-repl))
506+
(eval-err '())
507+
(eval-out '()))
508+
(expect repl-buffer :not :to-be nil)
509+
510+
;; send command to the REPL
511+
(cider-interactive-eval
512+
;; ask REPL to return a string that uniquely identifies it.
513+
"(print :clojure? (some? (clojure-version)))"
514+
(lambda (return)
515+
(nrepl-dbind-response
516+
return
517+
(out err)
518+
(when err (push err eval-err))
519+
(when out (push out eval-out)))) )
520+
521+
;; wait for a response to come back.
522+
(cider-itu-poll-until (or eval-err eval-out) 10)
523+
524+
;; ensure there are no errors and response is as expected.
525+
(expect eval-err :to-equal '())
526+
(expect eval-out :to-equal '(":clojure? true"))
527+
528+
;; exit the REPL.
529+
(cider-quit repl-buffer)
530+
531+
;; wait for the REPL to exit
532+
(cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 15)
533+
(expect (member (process-status nrepl-proc) '(exit signal)))))))))))
397534

398535
(provide 'integration-tests)
399536

0 commit comments

Comments
 (0)