Reveal: Use at the REPL
In addition to using Reveal as a REPL output pane, you can also use Reveal at the REPL to create and show various views. All Reveal public functions live in vlaaad.reveal
namespace, which is assumed to be aliased to r
in following Clojure snippets.
- Launching REPLs
- Opening windows
- Using views
- Interacting with Reveal windows from code
- Reveal Pro only functionality
Launching REPLs
These functions are mainly intended to be invoked from the command line via -X
.
repl
It is a repl that wraps clojure.main/repl
with additional support for :repl/quit
and tap>
. It is as simple as it gets. I use it all the time. Example:
clj \
-Sdeps '{:deps {vlaaad/reveal {:mvn/version "1.3.280"}}}' \
-X vlaaad.reveal/repl :always-on-top true
# Reveal sticker window appears
Clojure 1.10.1
user=>
io-prepl
This prepl works like clojure.core.server/io-prepl
. Its purpose is to be run in a process on your machine that you want to connect to using another prepl-aware tool. Example:
clj \
-Sdeps '{:deps {vlaaad/reveal {:mvn/version "1.3.280"}}}' \
-J-Dclojure.server.reveal='{:port 5555 :accept vlaaad.reveal/io-prepl}'
Now you can connect to this process using any socket repl and it will show a new Reveal sticker (always-on-top) window for every connection:
nc localhost 5555
# Reveal sticker window appears
# Input:
(+ 1 2 3)
# Prepl output (+ displayed in Reveal):
{:tag :ret, :val "6", :ns "user", :ms 1, :form "(+ 1 2 3)"}
remote-prepl
Reveal is the most useful when it runs in the process where the development happens. This prepl, unlike the previous two, is not like that: it connects to a remote process and shows a window for values that arrived from the network. It can’t benefit from easy access to printed references because these references are pointing to values deserialized from bytes, not values in the target VM. It’s still nice and performant repl, and it’s useful when you want to use Reveal to talk to another process that does not have Reveal on the classpath (e.g. production or ClojureScript prepl).
Example:
- Start a prepl without Reveal on the classpath:
clj \ -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.10.764"}}}' \ -J-Dclojure.server.cljs-prepl='{:port 50505 :accept cljs.server.browser/prepl}'
- Connect to that prepl using Reveal:
clj \ -Sdeps '{:deps {vlaaad/reveal {:mvn/version "1.3.280"}}}' \ -X vlaaad.reveal/remote-prepl :always-on-top true :port 50505 # at this point, 2 things happen: # 1. Browser window with cljs prepl appears # 2. Reveal window opens # Input: js/window # Output: {:tag :ret, :val #object [Window [object Window]], :ns "cljs.user", :ms 25, :form "js/window"}
Opening windows
ui
Calling this function will create and show a Reveal window. It returns a function that you can submit values to — they will appear in the output panel. All built-in visual REPLs use a window created by this generic function. Example:
;; Create and show a window
(def window (r/ui))
;; Submit huge data structure that is hard to explore when printed:
(window (map bean (all-ns)))
tap-log
To display tapped values in Reveal window without using Reveal as a REPL, you can use the specialized tap log window:
;; Open a window that will show tapped values:
(r/tap-log)
;; Submit values from anywhere to the window:
(tap> (all-ns))
inspect
You can open a new temporary sticker window for value inspection using r/inspect
or #reveal/inspect
data reader:
;; Inspect all loaded namespaces
#reveal/inspect (all-ns)
sticker
This is a generic sticker window that is similar to inspect
. It exists for a different purpose though — creating longer-lived always-on-top windows for frequently used views (e.g. Component/Integrant/Mount state trackers). It’s harder to close — you need to use close window shortcut like Ctrl W instead of Escape.
;; Simple mount sticker window:
(r/sticker
{:fx/type r/ref-watch-latest-view
:ref @#'mount.core/running}
:title "mount system")
Using views
Reveal uses cljfx — declarative, functional and extensible wrapper of JavaFX inspired by react — for its views. In short, views are described using maps with a special key — :fx/type
— that defines a type of UI element, while other keys define properties of that element.
Value windows like r/inspect
and r/sticker
will show cljfx UI descriptions as UI elements instead of maps. Reveal provides a variety of different cljfx description functions to inspect data.
Test runner views
Reveal provides a test runner for Clojure tests that you can start from the REPL, for example:
;; Show test runner for running tests defined in current ns:
#reveal/inspect {:fx/type r/test-runner-view
:runner (r/test-runner *ns*)}
;; Or a test view with internally managed runner:
#reveal/inspect {:fx/type r/test-view
:test *ns*}
;; Show test runner sticker that only displays a test summary with controls
;; (You can click the summary to open the full output in a new inspector popup)
(r/sticker
{:fx/type r/test-runner-controls-view
:runner (r/test-runner)}
:title "Project tests")
;; Or show a built-in sticker with managed runner:
(r/test-sticker)
Vega(-Lite) views
Reveal provides Vega(-Lite) visualizations out of the box:
(r/inspect {:fx/type r/vega-view
:spec {:mark :bar
:encoding {:x {:field :a
:type :nominal
:axis {:labelAngle 0}}
:y {:field :b
:type :quantitative}}}
:data [{:a "A" :b 28}
{:a "B" :b 55}
{:a "C" :b 43}
{:a "D" :b 91}
{:a "E" :b 81}
{:a "F" :b 53}
{:a "G" :b 19}
{:a "H" :b 87}
{:a "I" :b 52}]})
Ref watchers
You can watch IRef instances (like Vars, Atoms, Agents and Refs) in a live-updated views:
;; Show the current state of the system
(r/inspect {:fx/type r/ref-watch-latest-view
:ref #'my.app/integrant-system})
;; Show a log of all state changes of the system
(r/inspect {:fx/type r/ref-watch-all-view
:ref #'my.app/integrant-system})
;; Iterate on vega spec with vega view that is auto-updated on Var redefinition
(r/inspect {:fx/type r/observable-view
:ref #'my-vega-spec
:fn r/vega-view})
Derefable view
This view asynchronously derefs a blocking derefable (e.g. future or promise) and eventually shows the result (or thrown exception):
(r/inspect {:fx/type r/derefable-view
:derefable (future (Thread/sleep 10000))})
Table
This view shows a collection of values in a table:
(r/inspect {:fx/type r/table-view
:columns [{:fn identity} {:fn first}]
:items [[:a 1] {:a 1} "a=1"]})
Tree
This view shows a tree-like data structure as a tree view. The API is similar to tree-seq
. It is fine to run long-running computations (like performing http requests) in :children
fn.
(r/inspect {:fx/type r/tree-view
:root [0 [1 [2] [[3]]] [4 [5 [[6 7 8]]]]]
:branch? seqable?
:children seq})
Built-in charts
You can use built-in JavaFX charts with Reveal:
;; Pie chart
(r/inspect {:fx/type r/pie-chart-view
:data {:a 1 :b 2}})
;; Bar chart
(r/inspect {:fx/type r/bar-chart-view
:data {:me {:apples 10 :oranges 5}
:you {:apples 3 :oranges 15}}})
;; Line chart
(r/inspect {:fx/type r/line-chart-view
:data {:squared (map #(* % %) (range 100))
:linear (range 100)}})
;; Scatter chart
(r/inspect {:fx/type r/scatter-chart-view
:data {:uniform (repeatedly 500 #(vector (rand) (rand)))
:gaussian (repeatedly 500 #(vector (* 0.5 (+ (rand) (rand)))
(* 0.5 (+ (rand) (rand)))))}})
Action view
Reveal provides a variety of contextual actions for objects. You can invoke them from the REPL using action view:
(r/inspect {:fx/type r/action-view
:action :vlaaad.reveal.action/java-bean
:value #'inc})
Value view
This view shows any object using Reveal’s syntax-highlighting formatting that allows selecting objects for further inspection, e.g.:
#reveal/inspect {:fx/type r/value-view
:value (all-ns)}
This is actually identical to just inspecting the value without explicit cljfx description.
Interacting with Reveal windows from code
If value submitted to Reveal window is a map with :vlaaad.reveal/command
key, instead of being shown in the output panel it will be interpreted as a command that will change the UI. Only queue-based windows accept commands by default (e.g. REPLs and a window created using (r/ui)
fn).
You can also use (r/submit-command! ...)
fn to submit a command to all shown windows, or to submit a command to a subset of all windows filtered with predicate of window opts.
There are 2 types of commands: predefined commands and evaluated command forms
Predefined commands
This is a list of built-in commands that you can evaluate to control Reveal window:
Command | Effect |
---|---|
(r/submit value) |
Submit value to the output panel (even if the value is a command) |
(r/clear-output) |
Clear the output panel |
(r/open-view value) |
Open value in a result panel |
(r/all ...commands) |
Execute a sequence of commands at once |
(r/dispose) |
Dispose Reveal window |
(r/minimize) |
Minimize the window |
(r/restore) |
Unminimize the window |
(r/toggle-minimized) |
Switch between minimized/unminimized state of the window |
These are pure functions that return maps with :vlaaad.reveal/command
key.
Evaluated command forms
You can evaluate code in a JVM that runs Reveal by submitting a map that has a code form on its :vlaaad.reveal/command
key, for example:
{:vlaaad.reveal/command '(clear-output)}
The benefit of using this form is that you don’t need to have Reveal on the classpath to construct this map, which makes it suitable for use cases where reveal might not be on the classpath, for example it can be in a committed code that taps some values during system startup that is used in development, but ignored in production. It also can be used to control Reveal when it’s using remote pREPLs, e.g. talking to ClojureScript environment where it’s impossible to have Reveal on the classpath.
By default the form will be evaluated in vlaaad.reveal
ns, but that is configurable with :ns
key.
You can also supply :env
— a map of symbols to arbitrary values that will be resolvable when evaluated. This map is useful when you want to pass a value to code form without embedding it in the form, for example:
;; Show public vars of this ns as table
{:vlaaad.reveal/command '(open-view {:fx/type action-view
:action :vlaaad.reveal.action/view:table
:value v})
:env {'v (ns-publics *ns*)}}
Reveal Pro only functionality
You get extra features when using paid version of Reveal.
Forms
Forms allow you to convert data structure specifications to UI input elements for creating these data structures. This is a generic and multi-purpose library that supports Clojure spec and json schema out of the box and can be extended to other data specification libraries. This library has a big API surface, so it lives in a separate namespace — vlaaad.reveal.pro.form
.
;; Add forms ns
(require '[vlaaad.reveal.pro.form :as form])
;; Open a view for creating data structure defined by spec
(r/inspect {:fx/type form/form-view
:form (form/spec-alpha-form `ns)})
;; Open a view for creating data structures defined by JSON schema
(r/inspect {:fx/type form/form-view
:form (form/json-schema-form "https://vega.github.io/schema/vega/v5.json")})
;; ...Or use bundled Vega(-Lite) form:
(r/inspect {:fx/type form/form-view
:form (form/vega-form)})
Vega form view
In addition to forms for creating Vega(-Lite) specifications, Reveal Pro bundles a Vega(-Lite) form view with a live-updated corresponding visualization:
(r/inspect {:fx/type r/vega-form-view})
System stickers
If you are using component, integrant or mount, you will find it useful to have a small sticker window that shows current state of your dev system with controls to start and stop it:
;; Mount can be used straight away
(r/mount-sticker)
;; Integrant requires system ref and config
(r/integrant-sticker :ref #'my-system :config my-integrant-config)
;; Integrant REPL library support: uses integrant.repl's system state and config
(r/integrant-repl-sticker)
;; Component has no way to tell if the system is running, so you need to tell it
(r/component-sticker :ref #'my-system :running #(-> % :db :connection))
There are corresponding cljfx description view functions, e.g. r/mount-view
etc.
Resources sticker
Resources sticker window shows memory and CPU utilization by JVM:
(r/resources-sticker)
Like with system stickers, there is a corresponding r/resources-view
that you can use to embed the view into other views.