Skip to content

Figwheel REPL plugin

Antonin Hildebrand edited this page Nov 2, 2015 · 8 revisions

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 ^:dynamic *inside-repl-plugin* false)
(defonce ^:const repl-marker-style "color:white; background-color:black; padding:0px 2px; border-radius:1px;")

(defn figwheel-repl-fix [code]
  (.replace code
    #"^\(function \(\)\{try\{return cljs\.core\.pr_str\.call"
    "(function (){try{return cljs.core.identity.call"))

(defn intellij-repl-fix [code]
  (.replace code
    #"^try\{cljs\.core\.pr_str\.call"
    "try{cljs.core.identity.call"))

(defn rewrite-repl-code-snippet [code]
  (-> code figwheel-repl-fix intellij-repl-fix))

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

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

(defn eval-inside-repl-plugin [code]
  (let [rewritten-code (rewrite-repl-code-snippet code)
        result (eval rewritten-code)]
    (present-repl-result result)
    (pr-str result)))

(defn echoing-eval [code]
  (if *inside-repl-plugin*
    (eval-inside-repl-plugin 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 the expression is just wrapped in pr-str call so we unwrap it
    • here we provided unwrapping for Figwheel's implementation and IntelliJ's nREPL
  • 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'

Clone this wiki locally