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

Double (Clojure + ClojureScript) client REPLs #1195

Merged
merged 8 commits into from
Jul 13, 2015
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### New features

* [#1195](https://github.com/clojure-emacs/cider/pull/1195): CIDER can [create cljs REPLs](https://github.com/clojure-emacs/cider#clojurescript-usage).
* [#1191](https://github.com/clojure-emacs/cider/pull/1191): New custom variables `cider-debug-print-level` and `cider-debug-print-length`.
* [#1188](https://github.com/clojure-emacs/cider/pull/1188): New debugging tool-bar.
* [#1187](https://github.com/clojure-emacs/cider/pull/1187): The list of keys displayed by the debugger can be configured with `cider-debug-prompt`.
Expand Down
61 changes: 32 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,45 +717,48 @@ section of your Leiningen project's configuration.

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

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

- At the Clojure REPL:
```clojure
[com.cemerick/piggieback "0.2.1"]
[org.clojure/clojure "1.7.0"]
```

```clojure
(require '[cemerick.piggieback :as piggieback])
(require '[cljs.repl.node :as node])
(piggieback/cljs-repl (node/repl-env))
```
as well as the following option:

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

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

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

- and in your ClojureScript:
#### Browser-connected ClojureScript REPL

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

(repl/connect "ws://localhost:9001")
```
1. Add `[weasel "0.6.0"]` to your project's `:dependencies`.

2. Issue <kbd>M-x</kbd> `customize-variable` <kbd>RET</kbd> `cider-cljs-repl`
and choose the `Weasel` option.

3. Add this to your code:

```clojure
(ns my.cljs.core
(:require [weasel.repl :as repl]))
(repl/connect "ws://localhost:9001")
```

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

Provided that a Piggieback-enabled ClojureScript environment is active in your
REPL session, code loading and evaluation will work seamlessly regardless of the
Expand Down
24 changes: 22 additions & 2 deletions cider-client.el
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,29 @@ NS specifies the namespace in which to evaluate the request."
(nrepl-request:interrupt request-id (cider-interrupt-handler (current-buffer)))))))

(defun cider-current-repl-buffer ()
"The current REPL buffer."
"The current REPL buffer.
Return the REPL buffer given by `nrepl-current-connection-buffer'.
If current buffer is a file buffer, and if the REPL has siblings, instead
return the sibling that corresponds to the current file extension. This
allows for evaluation to be properly directed to clj or cljs REPLs depending
on where they come from."
(-when-let (repl-buf (nrepl-current-connection-buffer 'no-error))
(buffer-local-value 'nrepl-repl-buffer (get-buffer repl-buf))))
;; Take the extension of current file, or nil if there is none.
(let ((ext (file-name-extension (or (buffer-file-name) ""))))
;; Go to the "globally" active REPL buffer.
(with-current-buffer repl-buf
;; If it has siblings, check which of them is associated with this file
;; extension.
(or (cdr-safe (assoc ext nrepl-sibling-buffer-alist))
;; If it has no siblings, or if this extension is not specified,
;; fallback on the old behavior to just return the currently active
;; REPL buffer (which is probably just `repl-buf').
nrepl-repl-buffer)))))

(defun cider-current-session ()
"The REPL session to use for this buffer."
(with-current-buffer (cider-current-repl-buffer)
nrepl-session))

(defun cider--var-choice (var-info)
"Prompt to choose from among multiple VAR-INFO candidates, if required.
Expand Down
4 changes: 4 additions & 0 deletions cider-debug.el
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ Each element of LOCALS should be a list of at least two elements."
(defun cider--debug-mode-redisplay ()
"Display the input prompt to the user."
(nrepl-dbind-response cider--debug-mode-response (debug-value input-type locals)
;; The overlay code relies on window boundaries, but point could have been
;; moved outside the window by some other code. Redisplay here to ensure the
;; visible window includes point.
(redisplay)
(when (or (eq cider-debug-prompt t)
(eq cider-debug-prompt 'overlay))
(if (overlayp cider--debug-prompt-overlay)
Expand Down
33 changes: 23 additions & 10 deletions cider-interaction.el
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ keep track of a namespace.
This should never be set in Clojure buffers, as there the namespace
should be extracted from the buffer's ns form.")

(defvar-local cider-repl-type nil
"The type of this REPL buffer, usually either \"clj\" or \"cljs\".")

(defun cider-ensure-op-supported (op)
"Check for support of middleware op OP.
Signal an error if it is not supported."
Expand Down Expand Up @@ -309,7 +312,10 @@ Signal an error if it is not supported."
Info contains project name, current REPL namespace, host:port
endpoint and Clojure version."
(with-current-buffer (get-buffer connection-buffer)
(format "Active nREPL connection: %s@%s:%s (Java %s, Clojure %s, nREPL %s)"
(format "Active nREPL connection: %s%s@%s:%s (Java %s, Clojure %s, nREPL %s)"
(if nrepl-sibling-buffer-alist
(upcase (concat cider-repl-type " "))
"")
(or (nrepl--project-name nrepl-project-dir) "<no project>")
(car nrepl-endpoint)
(cadr nrepl-endpoint)
Expand All @@ -320,7 +326,7 @@ endpoint and Clojure version."
(defun cider-display-current-connection-info ()
"Display information about the current connection."
(interactive)
(message (cider--connection-info (nrepl-current-connection-buffer))))
(message (cider--connection-info (cider-current-repl-buffer))))

(defun cider-rotate-connection ()
"Rotate and display the current nREPL connection."
Expand Down Expand Up @@ -491,7 +497,7 @@ supplied project directory."
(format (if connection-buffer
"Switched to REPL: %s"
"Could not determine relevant nREPL connection, using: %s")
(with-current-buffer (nrepl-current-connection-buffer)
(with-current-buffer (cider-current-repl-buffer)
(format "%s:%s, %s:%s"
(or (nrepl--project-name nrepl-project-dir) "<no project>")
cider-buffer-ns
Expand Down Expand Up @@ -1637,13 +1643,13 @@ form independently.")

(defun cider--cache-ns-form ()
"Cache the form in the current buffer for the current connection."
(puthash (nrepl-current-connection-buffer)
(puthash (cider-current-repl-buffer)
(cider-ns-form)
cider--ns-form-cache))

(defun cider--cached-ns-form ()
"Retrieve the cached ns form for the current buffer & connection."
(gethash (nrepl-current-connection-buffer) cider--ns-form-cache))
(gethash (cider-current-repl-buffer) cider--ns-form-cache))

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

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

(defun cider-connected-p ()
"Return t if CIDER is currently connected, nil otherwise."
Expand Down Expand Up @@ -2160,6 +2169,10 @@ the string contents of the region into a formatted string."
;;; quiting
(defun cider--close-buffer (buffer)
"Close the BUFFER and kill its associated process (if any)."
(when nrepl-session
(nrepl-sync-request:close nrepl-session))
(when nrepl-tooling-session
(nrepl-sync-request:close nrepl-tooling-session))
(when (get-buffer-process buffer)
(delete-process (get-buffer-process buffer)))
(when (get-buffer buffer)
Expand Down Expand Up @@ -2192,7 +2205,7 @@ and all ancillary CIDER buffers."
(dolist (connection nrepl-connection-list)
(cider--quit-connection connection))
(message "All active nREPL connections were closed"))
(cider--quit-connection (nrepl-current-connection-buffer)))
(cider--quit-connection (cider-current-repl-buffer)))
;; if there are no more connections we can kill all ancillary buffers
(unless (cider-connected-p)
(cider-close-ancillary-buffers))))
Expand All @@ -2217,7 +2230,7 @@ If RESTART-ALL is t, then restarts all connections."
(if restart-all
(dolist (conn nrepl-connection-list)
(cider--restart-connection conn))
(cider--restart-connection (nrepl-current-connection-buffer))))
(cider--restart-connection (cider-current-repl-buffer))))

(defvar cider--namespace-history nil
"History of user input for namespace prompts.")
Expand Down
22 changes: 17 additions & 5 deletions cider-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,29 @@
(require 'cider-interaction)
(require 'cider-eldoc)

(defcustom cider-mode-line-show-connection t
"If the mode-line lighter should detail the connection."
:group 'cider
:type 'boolean
:package-version '(cider "0.10.0"))

(defun cider--modeline-info ()
"Return info for the `cider-mode' modeline.

Info contains project name and host:port endpoint."
(let ((current-connection (nrepl-current-connection-buffer t)))
(let ((current-connection (cider-current-repl-buffer)))
(if current-connection
(with-current-buffer current-connection
(format "%s@%s:%s"
(or (nrepl--project-name nrepl-project-dir) "<no project>")
(car nrepl-endpoint)
(cadr nrepl-endpoint)))
(concat
(when nrepl-sibling-buffer-alist
(concat cider-repl-type ":"))
(when cider-mode-line-show-connection
(format "%s@%s:%s"
(or (nrepl--project-name nrepl-project-dir) "<no project>")
(pcase (car nrepl-endpoint)
("localhost" "")
(x x))
(cadr nrepl-endpoint)))))
"not connected")))

;;;###autoload
Expand Down
4 changes: 3 additions & 1 deletion cider-repl.el
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,9 @@ namespace to switch to."
(cider-current-ns))))
(if (and ns (not (equal ns "")))
(nrepl-request:eval (format "(in-ns '%s)" ns)
(cider-repl-switch-ns-handler (cider-current-repl-buffer)))
(cider-repl-switch-ns-handler (cider-current-repl-buffer))
nil
(cider-current-session))
(error "No namespace selected")))


Expand Down
63 changes: 60 additions & 3 deletions cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,58 @@ Sub-match 1 must be the project path.")
("lein" cider-lein-parameters)
("boot" cider-boot-parameters)))

(defcustom cider-cljs-repl "(cemerick.piggieback/cljs-repl (cljs.repl.rhino/repl-env))"
"Clojure form that returns a ClojureScript REPL environment.
This is evaluated in a Clojure REPL and it should start a ClojureScript
REPL."
:type '(choice (const :tag "Rhyno"
"(cemerick.piggieback/cljs-repl (cljs.repl.rhino/repl-env))")
(const :tag "Node (requires NodeJS to be installed)"
"(cemerick.piggieback/cljs-repl (cljs.repl.node/repl-env))")
(const :tag "Weasel (see Readme for additional configuration)"
"(cemerick.piggieback/cljs-repl (weasel.repl.websocket/repl-env :ip \"127.0.0.1\" :port 9001))")
(string :tag "Custom"))
:group 'cider)

(defun cider-create-sibling-cljs-repl (client-buffer)
"Create a ClojureScript REPL with the same server as CLIENT-BUFFER.
Link the new buffer with CLIENT-BUFFER, which should be the regular Clojure
REPL started by the server process filter."
(interactive (list (cider-current-repl-buffer)))
(let* ((nrepl-repl-buffer-name-template "*cider-repl CLJS%s*")
(nrepl-create-client-buffer-function #'cider-repl-create)
(nrepl-use-this-as-repl-buffer 'new)
(client-process-args (with-current-buffer client-buffer
(unless (or nrepl-server-buffer nrepl-endpoint)
(error "This is not a REPL buffer, is there a REPL active?"))
(list (car nrepl-endpoint)
(elt nrepl-endpoint 1)
(when (buffer-live-p nrepl-server-buffer)
(get-buffer-process nrepl-server-buffer)))))
(cljs-proc (apply #'nrepl-start-client-process client-process-args))
(cljs-buffer (process-buffer cljs-proc))
(alist `(("cljs" . ,cljs-buffer)
("clj" . ,client-buffer))))
(with-current-buffer client-buffer
(setq cider-repl-type "clj")
(setq nrepl-sibling-buffer-alist alist))
(with-current-buffer cljs-buffer
(setq cider-repl-type "cljs")
(setq nrepl-sibling-buffer-alist alist)
(nrepl-send-request
(list "op" "eval"
"ns" (cider-current-ns)
"session" nrepl-session
"code" cider-cljs-repl)
(cider-repl-handler (current-buffer))))))

;;;###autoload
(defun cider-jack-in (&optional prompt-project)
(defun cider-jack-in (&optional prompt-project cljs-too)
"Start a nREPL server for the current project and connect to it.
If PROMPT-PROJECT is t, then prompt for the project for which to
start the server."
start the server.
If CLJS-TOO is non-nil, also start a ClojureScript REPL session with its
own buffer."
(interactive "P")
(setq cider-current-clojure-buffer (current-buffer))
(let ((project-type (or (cider-project-type) cider-default-repl-command)))
Expand All @@ -193,10 +240,20 @@ start the server."
(-when-let (repl-buff (nrepl-find-reusable-repl-buffer nil project-dir))
(let ((nrepl-create-client-buffer-function #'cider-repl-create)
(nrepl-use-this-as-repl-buffer repl-buff))
(nrepl-start-server-process project-dir cmd))))
(nrepl-start-server-process
project-dir cmd
(when cljs-too #'cider-create-sibling-cljs-repl)))))
(message "The %s executable (specified by `cider-lein-command' or `cider-boot-command') isn't on your exec-path"
(cider-jack-in-command project-type)))))

;;;###autoload
(defun cider-jack-in-clojurescript (&optional prompt-project)
"Start a nREPL server and connect to it both Clojure and ClojureScript REPLs.
If PROMPT-PROJECT is t, then prompt for the project for which to
start the server."
(interactive "P")
(cider-jack-in prompt-project 'cljs-too))

;;;###autoload
(defun cider-connect (host port)
"Connect to an nREPL server identified by HOST and PORT.
Expand Down
Loading