Skip to content

Commit ae8cda3

Browse files
committed
Merge pull request #1195 from Malabarba/double-client
Double (Clojure + ClojureScript) client REPLs
2 parents cf625a1 + fe05ca4 commit ae8cda3

File tree

9 files changed

+235
-70
lines changed

9 files changed

+235
-70
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### New features
66

7+
* [#1195](https://github.com/clojure-emacs/cider/pull/1195): CIDER can [create cljs REPLs](https://github.com/clojure-emacs/cider#clojurescript-usage).
78
* [#1191](https://github.com/clojure-emacs/cider/pull/1191): New custom variables `cider-debug-print-level` and `cider-debug-print-length`.
89
* [#1188](https://github.com/clojure-emacs/cider/pull/1188): New debugging tool-bar.
910
* [#1187](https://github.com/clojure-emacs/cider/pull/1187): The list of keys displayed by the debugger can be configured with `cider-debug-prompt`.

README.md

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -717,45 +717,48 @@ section of your Leiningen project's configuration.
717717

718718
ClojureScript support relies on the
719719
[piggieback](https://github.com/cemerick/piggieback) nREPL middleware being
720-
present in your REPL session. Version 0.2.0 or higher is recommended, and the
721-
below examples assume this, but version 0.1.5 is currently also supported.
720+
present in your REPL session.
722721

723-
* Example usage of a non-browser connected Node.js REPL:
722+
1. Add the following dependencies to your `project.clj`
724723

725-
- At the Clojure REPL:
724+
```clojure
725+
[com.cemerick/piggieback "0.2.1"]
726+
[org.clojure/clojure "1.7.0"]
727+
```
726728

727-
```clojure
728-
(require '[cemerick.piggieback :as piggieback])
729-
(require '[cljs.repl.node :as node])
730-
(piggieback/cljs-repl (node/repl-env))
731-
```
729+
as well as the following option:
732730

733-
* Example usage of browser-connected Weasel REPL (requires
734-
e.g. `[weasel "0.6.0"]` in your project's `:dependencies`):
731+
```clojure
732+
:repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
733+
```
735734

736-
- At the Clojure REPL:
735+
2. Issue <kbd>M-x</kbd> `customize-variable` <kbd>RET</kbd> `cider-cljs-repl` if
736+
you'd like to change the REPL used (the default is `rhino`).
737737

738-
```clojure
739-
(require '[cemerick.piggieback :as piggieback])
740-
(require '[weasel.repl.websocket :as weasel])
741-
(piggieback/cljs-repl (weasel/repl-env :ip "127.0.0.1"
742-
:port 9001))
743-
```
738+
3. Open a file in your project and issue <kbd>M-x</kbd>
739+
`cider-jack-in-cljs`. This will start up the nREPL server, and then create
740+
two REPL buffers for you, one in Clojure and one in ClojureScript. All usual
741+
CIDER commands will be automatically directed to the appropriate REPL,
742+
depending on whether you're visiting a `clj` or a `cljs` file.
744743

745-
- and in your ClojureScript:
744+
#### Browser-connected ClojureScript REPL
746745

747-
```clojure
748-
(ns my.cljs.core
749-
(:require [weasel.repl :as repl]))
746+
Using Weasel, you can also have a browser-connected REPL.
750747

751-
(repl/connect "ws://localhost:9001")
752-
```
748+
1. Add `[weasel "0.6.0"]` to your project's `:dependencies`.
749+
750+
2. Issue <kbd>M-x</kbd> `customize-variable` <kbd>RET</kbd> `cider-cljs-repl`
751+
and choose the `Weasel` option.
752+
753+
3. Add this to your code:
754+
755+
```clojure
756+
(ns my.cljs.core
757+
(:require [weasel.repl :as repl]))
758+
(repl/connect "ws://localhost:9001")
759+
```
753760

754-
The [clojure-quick-repls](https://github.com/symfrog/clojure-quick-repls)
755-
library provides helper functions to automate REPL creation for both Clojure and
756-
Clojurescript, and will also automatically route requests to the correct REPL
757-
according to the file extension of the current buffer (note that CIDER does not
758-
provide the latter functionality out-of-the-box).
761+
4. Open a file in your project and issue `M-x cider-jack-in-cljs`.
759762

760763
Provided that a Piggieback-enabled ClojureScript environment is active in your
761764
REPL session, code loading and evaluation will work seamlessly regardless of the

cider-client.el

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,29 @@ NS specifies the namespace in which to evaluate the request."
110110
(nrepl-request:interrupt request-id (cider-interrupt-handler (current-buffer)))))))
111111

112112
(defun cider-current-repl-buffer ()
113-
"The current REPL buffer."
113+
"The current REPL buffer.
114+
Return the REPL buffer given by `nrepl-current-connection-buffer'.
115+
If current buffer is a file buffer, and if the REPL has siblings, instead
116+
return the sibling that corresponds to the current file extension. This
117+
allows for evaluation to be properly directed to clj or cljs REPLs depending
118+
on where they come from."
114119
(-when-let (repl-buf (nrepl-current-connection-buffer 'no-error))
115-
(buffer-local-value 'nrepl-repl-buffer (get-buffer repl-buf))))
120+
;; Take the extension of current file, or nil if there is none.
121+
(let ((ext (file-name-extension (or (buffer-file-name) ""))))
122+
;; Go to the "globally" active REPL buffer.
123+
(with-current-buffer repl-buf
124+
;; If it has siblings, check which of them is associated with this file
125+
;; extension.
126+
(or (cdr-safe (assoc ext nrepl-sibling-buffer-alist))
127+
;; If it has no siblings, or if this extension is not specified,
128+
;; fallback on the old behavior to just return the currently active
129+
;; REPL buffer (which is probably just `repl-buf').
130+
nrepl-repl-buffer)))))
131+
132+
(defun cider-current-session ()
133+
"The REPL session to use for this buffer."
134+
(with-current-buffer (cider-current-repl-buffer)
135+
nrepl-session))
116136

117137
(defun cider--var-choice (var-info)
118138
"Prompt to choose from among multiple VAR-INFO candidates, if required.

cider-debug.el

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,10 @@ Each element of LOCALS should be a list of at least two elements."
268268
(defun cider--debug-mode-redisplay ()
269269
"Display the input prompt to the user."
270270
(nrepl-dbind-response cider--debug-mode-response (debug-value input-type locals)
271+
;; The overlay code relies on window boundaries, but point could have been
272+
;; moved outside the window by some other code. Redisplay here to ensure the
273+
;; visible window includes point.
274+
(redisplay)
271275
(when (or (eq cider-debug-prompt t)
272276
(eq cider-debug-prompt 'overlay))
273277
(if (overlayp cider--debug-prompt-overlay)

cider-interaction.el

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ keep track of a namespace.
232232
This should never be set in Clojure buffers, as there the namespace
233233
should be extracted from the buffer's ns form.")
234234

235+
(defvar-local cider-repl-type nil
236+
"The type of this REPL buffer, usually either \"clj\" or \"cljs\".")
237+
235238
(defun cider-ensure-op-supported (op)
236239
"Check for support of middleware op OP.
237240
Signal an error if it is not supported."
@@ -309,7 +312,10 @@ Signal an error if it is not supported."
309312
Info contains project name, current REPL namespace, host:port
310313
endpoint and Clojure version."
311314
(with-current-buffer (get-buffer connection-buffer)
312-
(format "Active nREPL connection: %s@%s:%s (Java %s, Clojure %s, nREPL %s)"
315+
(format "Active nREPL connection: %s%s@%s:%s (Java %s, Clojure %s, nREPL %s)"
316+
(if nrepl-sibling-buffer-alist
317+
(upcase (concat cider-repl-type " "))
318+
"")
313319
(or (nrepl--project-name nrepl-project-dir) "<no project>")
314320
(car nrepl-endpoint)
315321
(cadr nrepl-endpoint)
@@ -320,7 +326,7 @@ endpoint and Clojure version."
320326
(defun cider-display-current-connection-info ()
321327
"Display information about the current connection."
322328
(interactive)
323-
(message (cider--connection-info (nrepl-current-connection-buffer))))
329+
(message (cider--connection-info (cider-current-repl-buffer))))
324330

325331
(defun cider-rotate-connection ()
326332
"Rotate and display the current nREPL connection."
@@ -491,7 +497,7 @@ supplied project directory."
491497
(format (if connection-buffer
492498
"Switched to REPL: %s"
493499
"Could not determine relevant nREPL connection, using: %s")
494-
(with-current-buffer (nrepl-current-connection-buffer)
500+
(with-current-buffer (cider-current-repl-buffer)
495501
(format "%s:%s, %s:%s"
496502
(or (nrepl--project-name nrepl-project-dir) "<no project>")
497503
cider-buffer-ns
@@ -1637,13 +1643,13 @@ form independently.")
16371643

16381644
(defun cider--cache-ns-form ()
16391645
"Cache the form in the current buffer for the current connection."
1640-
(puthash (nrepl-current-connection-buffer)
1646+
(puthash (cider-current-repl-buffer)
16411647
(cider-ns-form)
16421648
cider--ns-form-cache))
16431649

16441650
(defun cider--cached-ns-form ()
16451651
"Retrieve the cached ns form for the current buffer & connection."
1646-
(gethash (nrepl-current-connection-buffer) cider--ns-form-cache))
1652+
(gethash (cider-current-repl-buffer) cider--ns-form-cache))
16471653

16481654
(defun cider--prep-interactive-eval (form)
16491655
"Prepares the environment for an interactive eval of FORM.
@@ -1685,7 +1691,7 @@ arguments and only proceed with evaluation if it returns nil."
16851691
;; always eval ns forms in the user namespace
16861692
;; otherwise trying to eval ns form for the first time will produce an error
16871693
(if (cider-ns-form-p form) "user" (cider-current-ns))
1688-
nil
1694+
(cider-current-session)
16891695
point)))
16901696

16911697
(defun cider-interactive-pprint-eval (form &optional callback right-margin)
@@ -1722,7 +1728,7 @@ If invoked with a PREFIX argument, print the result in the current buffer."
17221728
(interactive)
17231729
(let ((last-sexp (cider-last-sexp)))
17241730
;; we have to be sure the evaluation won't result in an error
1725-
(nrepl-sync-request:eval last-sexp)
1731+
(nrepl-sync-request:eval last-sexp nil (cider-current-session))
17261732
;; seems like the sexp is valid, so we can safely kill it
17271733
(backward-kill-sexp)
17281734
(cider-interactive-eval last-sexp (cider-eval-print-handler))))
@@ -1845,7 +1851,10 @@ If invoked with a prefix ARG eval the expression after inserting it."
18451851
(defun cider-ping ()
18461852
"Check that communication with the nREPL server works."
18471853
(interactive)
1848-
(message (read (nrepl-dict-get (nrepl-sync-request:eval "\"PONG\"") "value"))))
1854+
(-> (nrepl-sync-request:eval "\"PONG\"" nil (cider-current-session))
1855+
(nrepl-dict-get "value")
1856+
(read)
1857+
(message)))
18491858

18501859
(defun cider-connected-p ()
18511860
"Return t if CIDER is currently connected, nil otherwise."
@@ -2160,6 +2169,10 @@ the string contents of the region into a formatted string."
21602169
;;; quiting
21612170
(defun cider--close-buffer (buffer)
21622171
"Close the BUFFER and kill its associated process (if any)."
2172+
(when nrepl-session
2173+
(nrepl-sync-request:close nrepl-session))
2174+
(when nrepl-tooling-session
2175+
(nrepl-sync-request:close nrepl-tooling-session))
21632176
(when (get-buffer-process buffer)
21642177
(delete-process (get-buffer-process buffer)))
21652178
(when (get-buffer buffer)
@@ -2192,7 +2205,7 @@ and all ancillary CIDER buffers."
21922205
(dolist (connection nrepl-connection-list)
21932206
(cider--quit-connection connection))
21942207
(message "All active nREPL connections were closed"))
2195-
(cider--quit-connection (nrepl-current-connection-buffer)))
2208+
(cider--quit-connection (cider-current-repl-buffer)))
21962209
;; if there are no more connections we can kill all ancillary buffers
21972210
(unless (cider-connected-p)
21982211
(cider-close-ancillary-buffers))))
@@ -2217,7 +2230,7 @@ If RESTART-ALL is t, then restarts all connections."
22172230
(if restart-all
22182231
(dolist (conn nrepl-connection-list)
22192232
(cider--restart-connection conn))
2220-
(cider--restart-connection (nrepl-current-connection-buffer))))
2233+
(cider--restart-connection (cider-current-repl-buffer))))
22212234

22222235
(defvar cider--namespace-history nil
22232236
"History of user input for namespace prompts.")

cider-mode.el

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,29 @@
3333
(require 'cider-interaction)
3434
(require 'cider-eldoc)
3535

36+
(defcustom cider-mode-line-show-connection t
37+
"If the mode-line lighter should detail the connection."
38+
:group 'cider
39+
:type 'boolean
40+
:package-version '(cider "0.10.0"))
41+
3642
(defun cider--modeline-info ()
3743
"Return info for the `cider-mode' modeline.
3844
3945
Info contains project name and host:port endpoint."
40-
(let ((current-connection (nrepl-current-connection-buffer t)))
46+
(let ((current-connection (cider-current-repl-buffer)))
4147
(if current-connection
4248
(with-current-buffer current-connection
43-
(format "%s@%s:%s"
44-
(or (nrepl--project-name nrepl-project-dir) "<no project>")
45-
(car nrepl-endpoint)
46-
(cadr nrepl-endpoint)))
49+
(concat
50+
(when nrepl-sibling-buffer-alist
51+
(concat cider-repl-type ":"))
52+
(when cider-mode-line-show-connection
53+
(format "%s@%s:%s"
54+
(or (nrepl--project-name nrepl-project-dir) "<no project>")
55+
(pcase (car nrepl-endpoint)
56+
("localhost" "")
57+
(x x))
58+
(cadr nrepl-endpoint)))))
4759
"not connected")))
4860

4961
;;;###autoload

cider-repl.el

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,9 @@ namespace to switch to."
711711
(cider-current-ns))))
712712
(if (and ns (not (equal ns "")))
713713
(nrepl-request:eval (format "(in-ns '%s)" ns)
714-
(cider-repl-switch-ns-handler (cider-current-repl-buffer)))
714+
(cider-repl-switch-ns-handler (cider-current-repl-buffer))
715+
nil
716+
(cider-current-session))
715717
(error "No namespace selected")))
716718

717719

cider.el

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,58 @@ Sub-match 1 must be the project path.")
171171
("lein" cider-lein-parameters)
172172
("boot" cider-boot-parameters)))
173173

174+
(defcustom cider-cljs-repl "(cemerick.piggieback/cljs-repl (cljs.repl.rhino/repl-env))"
175+
"Clojure form that returns a ClojureScript REPL environment.
176+
This is evaluated in a Clojure REPL and it should start a ClojureScript
177+
REPL."
178+
:type '(choice (const :tag "Rhyno"
179+
"(cemerick.piggieback/cljs-repl (cljs.repl.rhino/repl-env))")
180+
(const :tag "Node (requires NodeJS to be installed)"
181+
"(cemerick.piggieback/cljs-repl (cljs.repl.node/repl-env))")
182+
(const :tag "Weasel (see Readme for additional configuration)"
183+
"(cemerick.piggieback/cljs-repl (weasel.repl.websocket/repl-env :ip \"127.0.0.1\" :port 9001))")
184+
(string :tag "Custom"))
185+
:group 'cider)
186+
187+
(defun cider-create-sibling-cljs-repl (client-buffer)
188+
"Create a ClojureScript REPL with the same server as CLIENT-BUFFER.
189+
Link the new buffer with CLIENT-BUFFER, which should be the regular Clojure
190+
REPL started by the server process filter."
191+
(interactive (list (cider-current-repl-buffer)))
192+
(let* ((nrepl-repl-buffer-name-template "*cider-repl CLJS%s*")
193+
(nrepl-create-client-buffer-function #'cider-repl-create)
194+
(nrepl-use-this-as-repl-buffer 'new)
195+
(client-process-args (with-current-buffer client-buffer
196+
(unless (or nrepl-server-buffer nrepl-endpoint)
197+
(error "This is not a REPL buffer, is there a REPL active?"))
198+
(list (car nrepl-endpoint)
199+
(elt nrepl-endpoint 1)
200+
(when (buffer-live-p nrepl-server-buffer)
201+
(get-buffer-process nrepl-server-buffer)))))
202+
(cljs-proc (apply #'nrepl-start-client-process client-process-args))
203+
(cljs-buffer (process-buffer cljs-proc))
204+
(alist `(("cljs" . ,cljs-buffer)
205+
("clj" . ,client-buffer))))
206+
(with-current-buffer client-buffer
207+
(setq cider-repl-type "clj")
208+
(setq nrepl-sibling-buffer-alist alist))
209+
(with-current-buffer cljs-buffer
210+
(setq cider-repl-type "cljs")
211+
(setq nrepl-sibling-buffer-alist alist)
212+
(nrepl-send-request
213+
(list "op" "eval"
214+
"ns" (cider-current-ns)
215+
"session" nrepl-session
216+
"code" cider-cljs-repl)
217+
(cider-repl-handler (current-buffer))))))
218+
174219
;;;###autoload
175-
(defun cider-jack-in (&optional prompt-project)
220+
(defun cider-jack-in (&optional prompt-project cljs-too)
176221
"Start a nREPL server for the current project and connect to it.
177222
If PROMPT-PROJECT is t, then prompt for the project for which to
178-
start the server."
223+
start the server.
224+
If CLJS-TOO is non-nil, also start a ClojureScript REPL session with its
225+
own buffer."
179226
(interactive "P")
180227
(setq cider-current-clojure-buffer (current-buffer))
181228
(let ((project-type (or (cider-project-type) cider-default-repl-command)))
@@ -193,10 +240,20 @@ start the server."
193240
(-when-let (repl-buff (nrepl-find-reusable-repl-buffer nil project-dir))
194241
(let ((nrepl-create-client-buffer-function #'cider-repl-create)
195242
(nrepl-use-this-as-repl-buffer repl-buff))
196-
(nrepl-start-server-process project-dir cmd))))
243+
(nrepl-start-server-process
244+
project-dir cmd
245+
(when cljs-too #'cider-create-sibling-cljs-repl)))))
197246
(message "The %s executable (specified by `cider-lein-command' or `cider-boot-command') isn't on your exec-path"
198247
(cider-jack-in-command project-type)))))
199248

249+
;;;###autoload
250+
(defun cider-jack-in-clojurescript (&optional prompt-project)
251+
"Start a nREPL server and connect to it both Clojure and ClojureScript REPLs.
252+
If PROMPT-PROJECT is t, then prompt for the project for which to
253+
start the server."
254+
(interactive "P")
255+
(cider-jack-in prompt-project 'cljs-too))
256+
200257
;;;###autoload
201258
(defun cider-connect (host port)
202259
"Connect to an nREPL server identified by HOST and PORT.

0 commit comments

Comments
 (0)