trptcolin / whidbey

nREPL middleware to allow arbitrary value rendering.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

whidbey

This project reaches into nREPL's guts to replace the default pr-values middleware with the more general render-values. This watches nREPL messages for the :renderer key, and uses it to produce the returned string value.

TL;DR: pretty-print REPL values by default!

Usage

The easiest way to use Whidbey is as a Leiningen plugin. For example, to pretty-print all values with Puget (the main motivation of this project), you can use the following in your user or system profile:

:plugins
[[mvxcvi/whidbey "0.2.1"]]

The plugin uses :whidbey-renderer if defined in the project, otherwise it uses cprint-str from Puget.

This may conflict with existing REPL customizations, so if necessary you can add the profile configuration yourself:

:repl
{:dependencies
 [[mvxcvi/puget "0.5.2"]
  [mvxcvi/whidbey "0.2.1"]]

 :repl-options
 {:init (require 'clojure.tools.nrepl.middleware.render-values 'puget.printer)
  :nrepl-middleware [clojure.tools.nrepl.middleware.render-values/render-values]
  :nrepl-context {:interactive-eval {:renderer puget.printer/cprint-str}}}}

If you have an :init key for :repl-options in another profile, you should wrap it in a (do ...) so it merges correctly. You can check this using the lein-pprint or lein-cprint plugins:

$ lein with-profile +repl cprint :repl-options

Motivation and History

As I develop Clojure and interact with the REPL, I frequently used clojure.pprint to get a better display of the results of my commands. Later, I wrote Puget to pretty print in a canonical fashion with ANSI coloring. Soon I found myself running this after almost every command:

(cprint *1)

I decided that it would be really nice if the REPL just pretty-printed colored values for me, so I dove into the Leiningen/REPLy/nREPL stack.

Learning to REPL

When you start a REPL, the basic sequence of events looks like this:

  1. Leiningen parses the :repl-options in your project map.
  2. Leiningen starts an nREPL server with any specified custom handler or middlewares. (more on this later)
  3. Leiningen starts a REPLy client with the given options and connects it to nREPL.
  4. The read-eval-print-loop starts.

However, you're actually interacting with a client/server model, so the text you type into the REPL isn't directly interpreted. Instead, in must be sent to the server and the result communicated back to the client. nREPL accomplishes this using messages with an :op key. On the server side, a handler and a stack of middleware functions (very much like Ring) process the messages and send result messages back to the client.

For example, when you type a form like (+ 1 2 3), REPLy sends the server a message like:

{:op "eval"
 :code "(+ 1 2 3)"
 :ns "user"}

nREPL's interruptible-eval middleware catches eval operations and runs a sub-REPL to read and evaluate the input. The resulting value is built into another message and sent back to the client's Transport:

{:ns "user"
 :value 6}

At a higher level in the middleware stack, nREPL's pr-values wraps the Transport passed to later handlers. When messages are sent, the :value is transformed into a string using print-method or print-dup. This is needed because the result has to be serialized back over the wire to the client, and arbitrary Clojure values are not supported.

Towards a Solution

To add enough functionality to support colored pretty-printing, it turned out to be necessary to modify REPLy, but fortunately not nREPL or Leiningen. The change looks for an :nrepl-context map in the options passed to the client. The values specified under :interactive-eval are merged with the nREPL message for interactive evaluations.

It was necessary to patch REPLy because the client sends eval ops to the server in situations other than user input. For example, when you tab-complete a name while typing an expression, REPLy is actually evaluating a completion function on the server that searches the current namespace for matching symbols. The client then read-strings the response to get the list of matches.

If we just pretty-printed all eval requests, the results to requests like these would contain extra whitespace and ANSI color codes, which break the Clojure reader. We need to be able to select when to use a custom renderer and when plain strings are desirable, and the REPL client is the only place we can do that.

Value Rendering

Now we're finally in a position to pretty print our REPL values! This library provides a render-values middleware which replaces the built-in pr-values. This watches messages for the :renderer key, and uses it to produce the returned string value.

The value of :renderer should be a symbol which resolves to a rendering function on the server. Rendering functions accept one argument (the value to render) and return a string representation. If not provided, render-values falls back to print-method or print-dup, so if you don't specify anything the REPL will behave exactly as before.

Now, eval messages are passed down the stack, handled by interruptible-eval, and the result sent to the Transport to send back to the client. The render-values middleware's inserted transport processes the response message by using the desired function to render the message value. In Puget's case, this means returning a string with embedded ANSI color codes. When REPLy receives this message, all it has to do is faithfully reprint the string and the user sees nicely colored and pretty-printed text.

Project Status

Whidbey used to require quite a bit of setup. Fortunately, the following changes have made things a lot nicer:

  • REPLy #138 to support message context on interactive evals.
  • REPLy release 0.3.1 so that it doesn't need to be installed locally.
  • Leiningen upgrade to REPLy version 0.3.1 or higher, so that it doesn't need to be cloned locally. (Done as of 2.4.2)
  • NREPL-55 for a better way to control rendering middleware in the REPL.

License

This is free and unencumbered software released into the public domain. See the UNLICENSE file for more information.

About

nREPL middleware to allow arbitrary value rendering.

License:The Unlicense