diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dbb549ff..feeb5fdd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/cider-client.el b/cider-client.el index 3f8d38e17..73e4b6a90 100644 --- a/cider-client.el +++ b/cider-client.el @@ -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'.") diff --git a/cider.el b/cider.el index d789c7b8f..85d17214c 100644 --- a/cider.el +++ b/cider.el @@ -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. @@ -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." @@ -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 @@ -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.