Clojure + ClojureScript + Vim + REPL

June 10, 2015

Read time 7 min

UPDATE Jul 22, 2015: Latest version of fireplace.vim (commit hash 9f7b184...) supports evaluation of forms using cpp also on ClojureScript which is more convenient to the usage of cq% – sequence. Post is now updated accordingly.

Although there are multiple blog posts about getting Clojure / ClojureScript toolchain to work on Vim I decided that there is room for one more. Motivation for this post is to make it easy for anybody to set up toolchain for Clojure + ClojureScript project where Vim REPL integration works seamlessly for both Clojure and ClojureScript code. In this setup Clojure REPL runs in host Clojure runtime and ClojureScript REPL executes code on browser.

The sample project that is built here can also be found from github.

First thing to do is to make sure you have fireplace.vim installed in your Vim bundles. For the setup described here I am using fireplace version from commit hash 9f7b184... (for lack of better versioning) and Vim 7.4.52. Just follow installation instructions at fireplace github repo. This should be straight forward.

What follows is not that straight forward though. We are going to get Vim REPL integration work on ClojureScript so that the ClojureScript code is executed in the browser. On the side we will also get REPL integration from Vim to Clojure.

Create test project

Create an empty Clojure Compojure project for this exercise. After doing this once it should be easy to integrate REPL integration to an existing project as well:

lein new compojure vimcljsrepl

Upgrade the used Clojure, Compojure and Leiningen versions immediatelly to something newer. We are going to need these versions when we start to work on ClojureScript. Modify the dependencies in project.clj to look like this:

:min-lein-version "2.5.0"
:dependencies [[org.clojure/clojure "1.7.0-RC1"]
               [compojure "1.3.4"]
               [ring/ring-defaults "0.1.2"]]

Also create clj directory for Clojure code to separate it from ClojureScript code and move handler.cljthere:

mkdir -p src/clj/vimcljsrepl
mv src/vimcljsrepl/handler.clj src/clj/vimcljsrepl/

Add the clj path as a source path to project.clj:

:source-paths ["src/clj"]

Enable Vim + REPL integration on Clojure code

Add function to handler.clj that can be called from REPL to start the server:

(defn run []
  (run-jetty (wrap-reload #'app '(vimcljsrepl.handler)) {:port 8080 :join? false}))

run-jetty will run the service in Jetty standalone server. wrap-reload will reload the namespace before each request is handled.

The above relies on some Jetty dependencies. Add them to the :require – clause of the handler.clj:

(ns clj-cljs-vim-repl.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [ring.adapter.jetty :refer [run-jetty]]
            [ring.middleware.reload :refer [wrap-reload]]))

And to project.clj:

:dependencies [[org.clojure/clojure "1.7.0-RC1"]
               [compojure "1.3.4"]
               [ring/ring-defaults "0.1.2"]
               [ring/ring-jetty-adapter "1.3.2"]
               [ring/ring-devel "1.3.2"]]

You can now try the REPL and the service by running

lein repl

and executing the following in the REPL:

user=> (use 'vimcljsrepl.handler)
user=> (run)

This will start the server in port 8080. Directing your browser to http://localhost:8080/ should show friendly Hello World.

REPL integration to Vim through fireplace.vim should now work. To give it a try open handler.clj in Vim and edit the the string "Hello World" to "Hello REPL". Evaluate both functions approutes and app. You can do this for instance by key sequence cpp (evaluate innermost form under the cursor) on the name of the functions. For more instructions on how to use fireplace see their github page. Now refresh the browser and it should say Hello REPL instead of Hello World. No need to restart the server or save the file. Nice.

Enable Vim + REPL integration on ClojureScript

Next we are going to get the live browser REPL evaluation work from Vim on ClojureScript code. First we need to create some ClojureScript code that we can later modify and evaluate.

Create a new ClojureScript file core.cljs with following contents in src/cljs/vimcljsrepl:

(ns vimcljsrepl.core
  (:require [weasel.repl :as weasel]))

(weasel/connect "ws://localhost:9001" :verbose true)

When ran in browser this peace of code will connect browser to ClojureScript REPL that we will have running shortly. Connection will be created using weasel.

In order to make the above ClojureScript code work we will have to compile it. Add ClojureScript compilation step to application startup by modifying the run function in handler.clj to look like this:

(defn run []
  (cljs.build.api/build "src/cljs" {:optimizations :none
                                    :pretty-print true
                                    :source-map true
                                    :main "vimcljsrepl.core"
                                    :asset-path "js/out"
                                    :output-to "resources/public/js/main.js"
                                    :output-dir "resources/public/js/out"})
  (run-jetty (wrap-reload #'app '(vimcljsrepl.handler)) {:port 8080 :join? false}))

DISCLAIMER: You might want to use lein-cljsbuild and/or lein-figwheel for ClojureScript compilation but to keep it simple we will go with the approach above.

In order to make the compilation work, add dependency to handler.clj so that namespace declaration looks something like this:

(ns vimcljsrepl.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [cljs.build.api :refer [build]]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [ring.adapter.jetty :refer [run-jetty]]
            [ring.middleware.reload :refer [wrap-reload]]))

Also add ClojureScript dependency to project.clj:

:dependencies [[org.clojure/clojure "1.7.0-RC1"]
               [org.clojure/clojurescript "0.0-3291" :scope "provided"]
               [compojure "1.3.4"]
               [ring/ring-defaults "0.1.2"]
               [ring/ring-jetty-adapter "1.3.2"]
               [ring/ring-devel "1.3.2"]]

In order for weasel to work add weasel as dependency into the development profile in project.clj:

{:dev {:dependencies [[weasel "0.7.0"]]}}

Notice also that you can remove the dependencies javax.servlet/servlet-api and ring-mock since we are not going to need them for this.

Add ClojureScript source path to source paths in project.clj. source-paths should now look like:

:source-paths ["src/clj" "src/cljs"]

Finally, to load the newly compiled ClojureScript code create index.html in resources/public with contents like this:

<html>
<head>
</head>
<body>
  <script type="application/javascript" src="js/main.js"></script>
</body>
</html>

Now if you restart the lein repl and run the commands to start the server and direct your browser to http://localhost:8080/index.html you should see from browser development tools that the browser tries to establish connection with the REPL.

Finally we are going to put in building blocks so that fireplace.vim can use piggieback to setup weasel nREPL session to which the browser can connect.

We are going to setup the project.clj first. Modify the development environment section so that it looks like following:

{:dev {:repl-options {:init-ns vimcljsrepl.handler
                      :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
       :dependencies [[weasel "0.7.0"]
                      [org.clojure/tools.nrepl "0.2.10"]
                      [com.cemerick/piggieback "0.2.1"]]}}

As you can see we will use piggieback as the nREPL middleware. This allows Vim to “piggieback” to weasel REPL. piggieback requires org.clojure/tools.nrepl to be included explicitly. init-ns is set to vimcljsrepl.handler. This is just to remove the need to explicitly run (use 'vimcljsrepl.handler) when REPL is launched and can be left out.

We need a function that will launch REPL session in piggieback. Create one in handler.clj:

(defn repl-env []
  (weasel.repl.server/stop)
  (weasel.repl.websocket/repl-env :ip "0.0.0.0" :port 9001))

And update requires in handler.clj namespace accordingly:

(ns vimcljsrepl.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            weasel.repl.websocket
            weasel.repl.server
            [cljs.build.api :refer [build]]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [ring.adapter.jetty :refer [run-jetty]]
            [ring.middleware.reload :refer [wrap-reload]]))

Everything is now set for the ClojureScript Vim REPL integration to work.

To try it out run the REPL with lein repl, start the server by running (run) (notice that since init-ns was modified you don’t need to run (use 'vimcljsrepl.handler) anymore). When the server is running go to Vim and run :Piggieback (vimcljsrepl.handler/repl-env). This will halt the Vim until you connect to REPL with your browser. Direct your browser to http://localhost:8080/index.html. Connection from browser to REPL should now be established and the Vim should respond normally again. Go to core.cljs – file and add following line (no need to save the file):

(js/alert "Hello Browser!")

Evaluate the alert for instance with key sequence cpp while cursor is within the form and observe the alert box on the browser.

Your REPL integration to both Clojure and ClojureScript are now working.

Never miss a post