Skip to content

Figwheel REPL plugin

Antonin Hildebrand edited this page Dec 19, 2016 · 8 revisions

This method is obsolete, check out Dirac DevTools

REPL can echo results into devtools

With cljs-devtools installed, this allows you to:

  • navigate native javascript values in devtools console
  • drill down more complex cljs data structures interactively

Additional tips

  • you want to run Figwheel REPL with rlwrap - this will give you history, bracket matching and other great enhancements
  • it is handy to have REPL terminal available on a global keyboard shortcut - for example "Hotkey Window" in iTerm2
  • Figwheel supports nREPL, so you can connect to its REPL remotely. I did this with IntelliJ and it worked like a charm. This way you can get additional sugary features from your IDE.

Implementation

By default Figwheel REPL is silent on browser side. Luckily, thanks to Figwheel configurability, you are allowed to specify a custom REPL plugin. Let's implement a REPL plugin which will echo evaluated expressions entered in REPL into devtools javascript console (to be presented by cljs-devtools).

Here is an example implementation:

(ns your-project.figwheel
  (:require [figwheel.client :as figwheel]))

(defonce ^:const repl-marker-style "color:white; background-color:black; padding:0px 2px; border-radius:1px;")
(defonce ^:const figwheel-pattern #"^\(function \(\)\{try\{return cljs\.core\.pr_str\.call")
(defonce ^:const figwheel-replacement "(function (){try{return cljs.core.identity.call")
(defonce ^:const intellij-pattern #"^try\{cljs\.core\.pr_str\.call")
(defonce ^:const intellij-replacement "try{cljs.core.identity.call")

(defonce ^:dynamic *inside-repl-plugin* false)

(defn should-be-ignored? [code]
  (boolean (.match code #"^goog\.(addDependency|require|provide)")))                                                  ; for some reason we are getting goog.* calls from figwheel inside repl plugin

(defn detect-repl-kind [code]
  (cond
    (.match code figwheel-pattern) :figwheel
    (.match code intellij-pattern) :intellij
    :else :unknown))

(defn unwrap-code [repl-kind code]
  (case repl-kind
    :figwheel (.replace code figwheel-pattern figwheel-replacement)
    :intellij (.replace code intellij-pattern intellij-replacement)
    code))

(defn wrap-result [repl-kind result]
  (case repl-kind
    :figwheel (pr-str result)
    :intellij (pr-str result)
    result))

(defn eval [code]
  (js* "eval(~{code})"))

(defn echo-result [result]
  (.log js/console "%cREPL" repl-marker-style result))

(defn eval-with-echoing [code]
  (let [repl-kind (detect-repl-kind code)
        rewritten-code (unwrap-code repl-kind code)
        result (eval rewritten-code)]
    (echo-result result)
    (wrap-result repl-kind result)))

(defn echoing-eval [code]
  (if (and *inside-repl-plugin* (not (should-be-ignored? code)))
    (eval-with-echoing code)
    (eval code)))

(defn repl-plugin [& args]
  (let [standard-impl (apply figwheel/repl-plugin args)]
    (fn [& args]
      (binding [*inside-repl-plugin* true]
        (apply standard-impl args)))))

(defn start-figwheel []
  (figwheel/start
    {; your config goes here...
     :eval-fn       echoing-eval
     :merge-plugins {:repl-plugin repl-plugin}}))

Implementation notes

  • we are hijacking evaluation by specifying our own eval-fn which does the echoing
  • echoing is achieved by rewriting incoming javascript code snippets
    • code snippets are produced by REPL backends and may differ between nREPL implementations
    • usually a raw expression is just wrapped in pr-str call => so we unwrap it
    • above we provided unwrapping for Figwheel's REPL and IntelliJ's nREPL (yours can differ slightly)
  • because eval-fn can be called by Figwheel in different situations than REPL evaluations, we have to implement a repl-plugin which marks situations when eval-fn is being called from *inside-repl-plugin*

The above code is meant to be an example for you to implement your own version. It worked for me with Figwheel 0.5.0-SNAPSHOT, but I believe it should be compatible with official 0.4.1 release as well. It is likely that this code will break in future. I have existing maintained implementation in the plastic project. Feel free to steal it.

Happy REPLin'