Misc Functionality

This page covers some of the non-essential functionality provided by nREPL. Why "non-essential"? For two reasons:

  • Only eval is truly essential

  • Some of this functionality is specific to Clojure (e.g. pretty printing)

Most of the functionality that follows is implemented as middleware and caters directly to the needs of development tools.

Code Completion

Code completion support was added in nREPL 0.8.

nREPL bundles a completion middleware with a flexible interface. By default it will use nREPL’s own code completion in nrepl.util.completion, but you can instruct nREPL to use another completion function (e.g. one based on compliment).

The completion function should accept 3 parameters:

  • prefix: The completion prefix.

  • ns: The namespace in which to look for completion candidates.

  • options: A map of additional options.

The function should ideally return results in the following format:

[{:candidate "map", :ns "clojure.core", :type :function}
 {:candidate "map-entry?", :ns "clojure.core", :type :function}
 {:candidate "map-indexed", :ns "clojure.core", :type :function}
 {:candidate "map?", :ns "clojure.core", :type :function}
 {:candidate "mapcat", :ns "clojure.core", :type :function}
 {:candidate "mapv", :ns "clojure.core", :type :function}]

You can either specify a custom completion function in the requests (via the complete-fn key) or by setting nrepl.middleware.completion/*complete-fn*.

user=> (set! nrepl.middleware.completion/*complete-fn* my.namespace/my-completion)

Options

The default completion function accepts the extra-metadata option. It allows clients to request additional metadata about completion items. For example, to request docstrings and argument lists for applicable completion items:

;; -> client sends completion op
{:op "completions"
 :prefix "ma"
 :ns "clojure.core"
 :options {:extra-metadata ["arglists" "doc"]}}

;; <- server returns completion items
{:completions
 [,,,
  {:candidate "map"
   :type "function"
   :arglists "([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])"
   :doc "Returns a lazy sequence..."}
  ,,,]}

Symbol Lookup

Symbol lookup support was added in nREPL 0.8.
In nREPL 0.8.1 and older, the lookup op fails for some symbols because the result contains data that the Bencode transport does not support (see nrepl/nrepl#207). In versions newer than 0.8.1, :arglists and :arglists-str have identical string values. :arglists-str is subject to removal.

nREPL bundles a lookup middleware with a flexible interface. By default it will use nREPL’s own lookup logic in nrepl.util.lookup, but you can instruct nREPL to use another lookup function (e.g. info from orchard).

The lookup function should accept 2 parameters:

  • ns: The namespace in which to look for completion candidates.

  • sym: The symbol to lookup.

The function should ideally return results in the following format:

{:added "1.0",
 :ns "clojure.core",
 :name "map",
 :file
 "jar:file:/Users/bozhidar/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar!/clojure/core.clj",
 :static true,
 :column 1,
 :line 2727,
 :arglists "([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])",
 :doc
 "Returns a lazy sequence consisting of the result of applying f to\n  the set of first items of each coll, followed by applying f to the\n  set of second items in each coll, until any one of the colls is\n  exhausted.  Any remaining items in other colls are ignored. Function\n  f should accept number-of-colls arguments. Returns a transducer when\n  no collection is provided."}
nREPL’s default lookup function is pretty basic and it can only handle symbols that resolve to special forms and vars.

You can either specify a custom lookup function in the requests (via the lookup-fn key) or by setting nrepl.middleware.lookup/*lookup-fn*.

user=> (set! nrepl.middleware.lookup/*lookup-fn* my.namespace/my-lookup)

Pretty Printing

Pretty printing support was added in nREPL 0.6.

The namespace nrepl.middleware.print contains some dynamic vars you can set! at the REPL to alter how evaluation results will be printed. Note that if your nREPL client supports passing these options in requests, then it may override some or all of these options.

  • *print-fn*: the function to use for printing. Defaults to the equivalent of clojure.core/pr. The function must take two arguments:

    • value: the value to print.

    • writer: the java.io.Writer to print on.

  • *stream?*: if logical true, the result of printing each value will be streamed to the client over one or more messages. Defaults to false.

    • This lets you see results being printed incrementally, and optionally interrupt the evaluation while it is printing.

    • Your nREPL client may not fully support this mode of operation.

  • *buffer-size*: size of the buffer to use when streaming results. Defaults to 1024.

  • *quota*: a hard limit on the number of bytes printed for each value. Defaults to nil (no limit).

    • Your nREPL client may not indicate if truncation has occurred.

For example, to prevent printing infinite lazy sequences from causing the REPL to hang:

user=> (set! nrepl.middleware.print/*quota* 32)
32
user=> (range)
(0 1 2 3 4 5 6 7 8 9 10 11 12 13
user=>

Or to use clojure.pprint to print evaluation results:

user=> (set! nrepl.middleware.print/*print-fn* clojure.pprint/pprint)
#object[clojure.pprint$pprint 0x2bc11aa0 "clojure.pprint$pprint@2bc11aa0"]

user=> (meta #'int)
{:added "1.0",
 :ns #object[clojure.lang.Namespace 0xea12515 "clojure.core"],
 :name int,
 :file "clojure/core.clj",
 :column 1,
 :line 882,
 :arglists ([x]),
 :doc "Coerce to int",
 :inline
 #object[clojure.core$int__inliner__5509 0x3c79d89 "clojure.core$int__inliner__5509@3c79d89"]}

user=>

However, note well that clojure.pprint/pprint rebinds *out* internally, and so if anything else prints to *out* while evaluating, that output will end up intermingled in the result. See the print middleware documentation for a more detailed explanation.

For that reason nREPL provides a simple alternative to Clojure’s pprint, that plays well with the print middleware - nrepl.util.print/pprint.

You can also easily leverage more powerful pretty-printers like fipp or zprint with nREPL. Here’s an example zprint wrapper compatible with nREPL’s print middleware:

(require 'zprint.core :as z)

(defn zprint-pprint
  ([value writer]
   (zprint-pprint value writer {}))
  ([value writer options]
   (binding [*out* writer]
     (z/zprint value options))))

Evaluation Errors

The dynamic var nrepl.middleware.caught/*caught-fn* can be set! at the REPL to alter how evaluation errors will be handled. Like the :caught option to clojure.main/repl, this is a function that takes a java.lang.Throwable (default clojure.main/repl-caught) and is called when either read, eval, or print throws an exception or error.

For example, to automatically print the stacktrace of each error:

user> (set! nrepl.middleware.caught/*caught-fn* clojure.repl/pst)
#function[clojure.repl/pst]
user> (first 1)
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long
	clojure.lang.RT.seqFrom (RT.java:542)
	clojure.lang.RT.seq (RT.java:523)
	clojure.lang.RT.first (RT.java:668)
	clojure.core/first--4339 (core.clj:55)
	clojure.core/first--4339 (core.clj:55)
	user/eval11339 (form-init6612168545889071220.clj:12)
	user/eval11339 (form-init6612168545889071220.clj:12)
	clojure.lang.Compiler.eval (Compiler.java:6927)
	clojure.lang.Compiler.eval (Compiler.java:6890)
	clojure.core/eval (core.clj:3105)
	clojure.core/eval (core.clj:3101)
	clojure.main/repl/read-eval-print--7408/fn--7411 (main.clj:240)

user=>

Or to use the pretty stacktrace printing library to print stacktraces:

user=> (set! nrepl.middleware.caught/*caught-fn* io.aviso.repl/pretty-pst)
#function[io.aviso.repl/pretty-pst]
user=> (first 1)
 clojure.core/eval   core.clj: 3214
               ...
     user/eval5945  REPL Input
clojure.core/first   core.clj:   55
               ...
java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Long

user=>

Hot-loading dependencies

From time to time you’d want to experiment with some library without adding it as a dependency of your project. Starting with Clojure 1.12, you can use the built-in clojure.repl.deps/add-lib to hot-load dependencies:

user=> (require '[clojure.repl.deps :refer [add-lib]])
nil
user=> (add-lib 'org.clojure/core.memoize {:mvn/version "1.1.266"})
true
user=> (require 'clojure.core.memoize)
nil
On older Clojure versions you can use pomegranate for similar functionality.

System Output Forwarding

System output forwarding was added in nREPL 1.5.

By default, nREPL captures output written to *out* and *err* within each evaluation and sends it back to the client. However, output written directly to System/out or System/err from Java code, background threads, or the root bindings of *out*/*err* is not captured — it goes to the server’s console and the client never sees it.

The forward-system-output op addresses this by intercepting System/out and System/err at the JVM level and forwarding the output to the requesting client’s session. To enable it, send:

{:op "forward-system-output"}

Once enabled, any output written to System/out or System/err from any thread will be forwarded to the client as messages with an extra :source :system key (to distinguish them from regular eval output).

A few things to keep in mind:

  • Multiple clients — Each client that sends forward-system-output receives a copy of the system output. This is useful when multiple editors are connected to the same server.

  • Cancellation — There is no dedicated op to stop forwarding. To stop receiving system output, close the session and create a new one.

  • Global side-effect — The first call to forward-system-output globally replaces System/out and System/err with wrapped streams. The original output destinations still receive the output (it is teed, not redirected), but this is a JVM-wide change.

Do not enable system output forwarding if the client runs inside the same JVM process as the server. The forwarded output will itself produce more output, leading to an infinite loop.