Skip to content

Commit

Permalink
[#2202] Check in advance if a ClojureScript REPL can be created or not
Browse files Browse the repository at this point in the history
The current logic relies mostly on namespace checks, but it can
certainly be improved down the road.

The most important thing is that now users are going to get more
meaningful errors earlier. Before this we'd first create the second
REPL buffer and just get there some obscure errors because the command
didn't check at all if piggieback or the target REPL were even
available.
  • Loading branch information
bbatsov committed Mar 5, 2018
1 parent 6c16e8a commit 7d91a6d
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* Fix `cider-eval-region` masking `clojure-refactor-map` in `cider-repl-mode`.
* [#2171](https://github.com/clojure-emacs/cider/issues/2171): Update `See Also` mappings for Clojure 1.9.
* [#2202](https://github.com/clojure-emacs/cider/issues/2202): Make `cider-jack-in-clojurescript` prompt from the ClojureScript REPL type to use.
* [#2202](https://github.com/clojure-emacs/cider/issues/2202): Don't try to start a ClojureScript REPL before checking whether that's possible or not.

## 0.16.0 (2017-12-28)

Expand Down
8 changes: 8 additions & 0 deletions cider-client.el
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,14 @@ going to clobber *1/2/3)."
t ; tooling
))

(defun cider-namespace-present-p (ns)
"Check whether NS is present or not."
(if (thread-first (cider-sync-tooling-eval (format "(if (find-ns '%s) 1 nil)" ns))
(nrepl-dict-get "value")
(read))
t
nil))

(defalias 'cider-current-repl-buffer #'cider-current-connection
"The current REPL buffer.
Return the REPL buffer given by `cider-current-connection'.")
Expand Down
177 changes: 107 additions & 70 deletions cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -504,22 +504,48 @@ dependencies."


;;; ClojureScript REPL creation
(defun cider-check-nashorn-requirements ()
"Check whether we can start a Nashorn ClojureScript REPL."
(when (string-prefix-p "1.7" (cider--java-version))
(user-error "Nashorn is supported only on Java 8 or newer")))

(defun cider-check-node-requirements ()
"Check whether we can start a Node ClojureScript REPL."
(unless (executable-find "node")
(user-error "Node.js is not present on the exec-path. Make sure you've installed it and your exec-path is properly set")))

(defun cider-check-figwheel-requirements ()
"Check whether we can start a Figwheel ClojureScript REPL."
(unless (cider-namespace-present-p "figwheel-sidecar.repl-api")
(user-error "Figwheel is not available. Please check http://cider.readthedocs.io/en/latest/up_and_running/#clojurescript-usage")))

(defun cider-check-weasel-requirements ()
"Check whether we can start a Weasel ClojureScript REPL."
(unless (cider-namespace-present-p "weasel.repl.websocket")
(user-error "Weasel in not available. Please check http://cider.readthedocs.io/en/latest/up_and_running/#browser-connected-clojurescript-repl")))

(defun cider-check-boot-requirements ()
"Check whether we can start a Boot ClojureScript REPL."
(unless (cider-namespace-present-p "adzerk.boot-cljs-repl")
(user-error "The Boot ClojureScript REPL is not available. Please check https://github.com/adzerk-oss/boot-cljs-repl/blob/master/README.md")))

(defconst cider-cljs-repl-types
'(("Rhino" "(cemerick.piggieback/cljs-repl (cljs.repl.rhino/repl-env))" "")
("Nashorn" "(cemerick.piggieback/cljs-repl (cljs.repl.nashorn/repl-env))" " (part of Java 8 and newer)")
'(("Rhino" "(cemerick.piggieback/cljs-repl (cljs.repl.rhino/repl-env))"
nil)
("Nashorn" "(cemerick.piggieback/cljs-repl (cljs.repl.nashorn/repl-env))"
cider-check-nashorn-requirements)
("Figwheel" "(do (require 'figwheel-sidecar.repl-api) (figwheel-sidecar.repl-api/start-figwheel!) (figwheel-sidecar.repl-api/cljs-repl))"
" (add figwheel-sidecar to your plugins)")
cider-check-figwheel-requirements)
("Node" "(do (require 'cljs.repl.node) (cemerick.piggieback/cljs-repl (cljs.repl.node/repl-env)))"
" (requires NodeJS to be installed)")
cider-check-node-requirements)
("Weasel" "(do (require 'weasel.repl.websocket) (cemerick.piggieback/cljs-repl (weasel.repl.websocket/repl-env :ip \"127.0.0.1\" :port 9001)))"
" (see http://cider.readthedocs.io/en/latest/up_and_running/#browser-connected-clojurescript-repl)")
cider-check-weasel-requirements)
("Boot" "(do (require 'adzerk.boot-cljs-repl) (adzerk.boot-cljs-repl/start-repl))"
" (see https://github.com/adzerk-oss/boot-cljs-repl/blob/master/README.md"))
cider-check-boot-requirements))
"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 some installation instructions (if
necessary).")
REPL to start the ClojureScript REPL and functions to very their requirements.")

(defcustom cider-default-cljs-repl nil
"The default ClojureScript REPL to start.
Expand All @@ -544,20 +570,23 @@ you're working on."

(defun cider-select-cljs-repl ()
"Select the ClojureScript REPL to use with `cider-jack-in-clojurescript'."
(let* ((repl-types (mapcar #'car cider-cljs-repl-types))
(selected-type (completing-read "Select ClojureScript REPL type: " repl-types)))
(cadr (seq-find (lambda (entry)
(equal (car entry)
selected-type))
cider-cljs-repl-types))))

(defun cider-default-cljs-repl-form ()
"Get the default cljs REPL form if set."
(when cider-default-cljs-repl
(cadr (seq-find
(lambda (entry)
(equal (car entry) cider-default-cljs-repl))
cider-cljs-repl-types))))
(let ((repl-types (mapcar #'car cider-cljs-repl-types)))
(completing-read "Select ClojureScript REPL type: " repl-types)))

(defun cider-cljs-repl-form (repl-type)
"Get the cljs REPL form for REPL-TYPE."
(cadr (seq-find
(lambda (entry)
(equal (car entry) repl-type))
cider-cljs-repl-types)))

(defun cider-verify-cljs-repl-requirements (repl-type)
"Verify that the requirements for REPL-TYPE are met."
(when-let* ((fun (nth 2 (seq-find
(lambda (entry)
(equal (car entry) repl-type))
cider-cljs-repl-types))))
(funcall fun)))

(defun cider--offer-to-open-app-in-browser (server-buffer)
"Look for a server address in SERVER-BUFFER and offer to open it."
Expand All @@ -570,6 +599,11 @@ you're working on."
(when (y-or-n-p (format "Visit ‘%s’ in a browser? " url))
(browse-url url)))))))

(defun cider-verify-piggieback-is-present ()
"Check whether the piggieback middleware is present."
(unless (cider-namespace-present-p "cemerick.piggieback")
(user-error "Piggieback is not available. See http://cider.readthedocs.io/en/latest/up_and_running/#clojurescript-usage for details")))

(defun cider-create-sibling-cljs-repl (client-buffer)
"Create a ClojureScript REPL with the same server as CLIENT-BUFFER.
The new buffer will correspond to the same project as CLIENT-BUFFER, which
Expand All @@ -578,54 +612,57 @@ should be the regular Clojure REPL started by the server process filter.
Normally this would prompt for the ClojureScript REPL to start (e.g. Node,
Figwheel, etc), unless you've set `cider-default-cljs-repl'."
(interactive (list (cider-current-connection)))
;; Load variables in .dir-locals.el into the server process buffer, so
;; cider-default-cljs-repl can be set for each project individually.
(hack-local-variables)
(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))
(cljs-repl-form (or (cider-default-cljs-repl-form)
(cider-select-cljs-repl))))
(with-current-buffer cljs-buffer
;; The new connection has now been bumped to the top, but it's still a
;; Clojure REPL! Additionally, some ClojureScript REPLs can actually take
;; a while to start (some even depend on the user opening a browser).
;; Meanwhile, this REPL will gladly receive requests in place of the
;; original Clojure REPL. Our solution is to bump the original REPL back
;; up the list, so it takes priority on Clojure requests.
(cider-make-connection-default client-buffer)
(setq cider-repl-type "cljs")
(pcase cider-cljs-repl-types
(`(,name ,_ ,info)
(message "Starting a %s REPL%s" name (or info ""))))
(cider-nrepl-send-request
`("op" "eval"
"ns" ,(cider-current-ns)
"code" ,cljs-repl-form)
(cider-repl-handler (current-buffer)))
(when cider-offer-to-open-cljs-app-in-browser
(cider--offer-to-open-app-in-browser nrepl-server-buffer)))))

(defun cider--select-zombie-buffer (repl-buffers)
"Return a zombie buffer from REPL-BUFFERS, or nil if none exists."
(when-let* ((zombie-buffs (seq-remove #'get-buffer-process repl-buffers)))
(when (y-or-n-p
(format "Zombie REPL buffers exist (%s). Reuse? "
(mapconcat #'buffer-name zombie-buffs ", ")))
(if (= (length zombie-buffs) 1)
(car zombie-buffs)
(completing-read "Choose REPL buffer: "
(mapcar #'buffer-name zombie-buffs)
nil t)))))
(cider-verify-piggieback-is-present)
(let* ((cljs-repl-type (or cider-default-cljs-repl
(cider-select-cljs-repl)))
(cljs-repl-form (cider-cljs-repl-form cljs-repl-type)))
(cider-verify-cljs-repl-requirements cljs-repl-type)
;; Load variables in .dir-locals.el into the server process buffer, so
;; cider-default-cljs-repl can be set for each project individually.
(hack-local-variables)
(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)))
(with-current-buffer cljs-buffer
;; The new connection has now been bumped to the top, but it's still a
;; Clojure REPL! Additionally, some ClojureScript REPLs can actually take
;; a while to start (some even depend on the user opening a browser).
;; Meanwhile, this REPL will gladly receive requests in place of the
;; original Clojure REPL. Our solution is to bump the original REPL back
;; up the list, so it takes priority on Clojure requests.
(cider-make-connection-default client-buffer)
(setq cider-repl-type "cljs")
(pcase cider-cljs-repl-types
(`(,name ,_ ,info)
(message "Starting a %s REPL%s" name (or info ""))))
(cider-nrepl-send-request
`("op" "eval"
"ns" ,(cider-current-ns)
"code" ,cljs-repl-form)
(cider-repl-handler (current-buffer)))
(when cider-offer-to-open-cljs-app-in-browser
(cider--offer-to-open-app-in-browser nrepl-server-buffer)))))

(defun cider--select-zombie-buffer (repl-buffers)
"Return a zombie buffer from REPL-BUFFERS, or nil if none exists."
(when-let* ((zombie-buffs (seq-remove #'get-buffer-process repl-buffers)))
(when (y-or-n-p
(format "Zombie REPL buffers exist (%s). Reuse? "
(mapconcat #'buffer-name zombie-buffs ", ")))
(if (= (length zombie-buffs) 1)
(car zombie-buffs)
(completing-read "Choose REPL buffer: "
(mapcar #'buffer-name zombie-buffs)
nil t))))))

(defun cider-find-reusable-repl-buffer (endpoint project-directory)
"Check whether a reusable connection buffer already exists.
Expand Down

0 comments on commit 7d91a6d

Please sign in to comment.