Skip to content

Instantly share code, notes, and snippets.

@SevereOverfl0w
Last active January 1, 2018 19:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SevereOverfl0w/c145f2635ae8b72d1e3ac0fa450ff701 to your computer and use it in GitHub Desktop.
Save SevereOverfl0w/c145f2635ae8b72d1e3ac0fa450ff701 to your computer and use it in GitHub Desktop.
Some thoughts on integrating Neo(vim) and Clojure tooling

Async nREPL client for Clojure

In this document I’m going to attempt describe the problems that may arise when building a Vim integration with the Clojure nREPL

nREPL client functionality can be broken down into 2 parts:

  1. Managing connections to an nREPL server

    • bencoding

    • connecting

  2. Interacting with the nREPL (UI)

    • Jump to source

    • Show source

    • Evaluate code

    • Session choosing

[fireplace.vim] has done a very good job of the UI part of this equation. There are some things I’d change, but these are mostly API design niggles, that would allow more extensions for users.

There are some additional UI concerns that an async nREPL client may need to consider, if it decides to offer additional features such as tracking simultaneous evaluations:

  • Reacting to incoming messages: should you immediately display the message, or save it for upon completion (not blocking in the meantime)

  • If you immediately display the message, your options for displaying messages become more limited. As far as I am aware your only option would be buffers, which comes with overhead of managing those buffers.

Porting fireplace

Fireplace is a very good nREPL client for Vim, and could be ported to create a non-streaming client. This would be non-trivial (based on a cursory look) despite the callback in Fireplace, as the callback isn’t always used, and it isn’t blocked based on the command.

There is a long chain of calls which will need tweaking in order to handle to the callback model.

Michael Bruce (@mikepjb) has looked into this undertaking, and decided it would be less effort to rewrite.

Buffers, output

Buffer numbers are maintained in a session, so are safe to persist. I had questioned this. It would be possible to maintain an in-memory reference like:

{"blah-message-id" {:type :eval-bg :buf-no 10}
 "zap-id" {:type :eval-callback :fn 'myplugin#echo'}
 "baz-id" {:type :wrap-out}
 "bosh-id" {:type :eval-callback :fn 'completion#completion'}}
" Some of these might be useful, but not all
silent! setlocal
      \ bufhidden=wipe
      \ colorcolumn=
      \ nobuflisted
      \ nocursorcolumn
      \ nocursorline
      \ nolist
      \ nonumber
      \ norelativenumber
      \ nospell
      \ noswapfile

setlocal readonly nomodifiable nomodified
" Get ready to make challenges
setlocal noreadonly modifiable
" Make changes
" Re-lock buffer:
setlocal readonly nomodifiable nomodified

I had thought it might be cool to do something like:

nrepl://zap-id/show

Would make a great buffer that you could just open, but I’m not sure on the value. It might be a better place to "store" information on what one wants to do when receiving an incoming message

Executing the nREPL client from (Neo)vim

There are 3 models of implementing an nREPL client.

Process per-operation

There is some performance overhead in this model. Although, there is a high possibility it would be negligible. The current implementation of [fireplace.vim] is written in a model similar to this.

It’s somewhat easier to encode messages in this model compared with others, if you’re willing to give up on streaming responses, each process could return a JSON array of messages from the operation. This would be suitable in a way that it isn’t for a long running process as there is no delimiter to be concerned with.

Long running process

A long running process would need to take some encoded form of message on stdin and display responses on stdout. Delimiting the messages on stdout would be difficult without an encoder, that would need implementing in vimscript. JSON would not be particularly suitable as any character that might be used as the delimiter could also be contained inside the JSON (e.g. a newline)

There is a reduced performance overhead from being a persistent process. Again, whether this matters is questionable.

Msgpack API (& Plugin hosts)

This is the easiest solution, if you only concern yourself with Neovim. It shares many of the characteristics with a long running process, but has Msgpack-RPC to encode messages with. Msgpack-RPC is infinitely better suited to encoding of binary data than JSON, and has a native decoder in Neovim which makes it very efficient (as opposed to implementing it in vimscript)

vim-hug-neovim-rpc is a plugin for running Neovim remote plugins inside of Vim8. This opens up a lot of possibilities, and more importantly allows the use of the RPC api with all of it’s benefits, without giving up Vim8.

Async omnicompletion

Deoplete.nvim, the current leading solution for "as you type" completion in Neovim, can only have completions written in Python. This has resulted in plugins like [Acid.nvim] being mandated to be also written in Python in order to work with this completion system.

There is now [nvim-completion-manager] which is entirely callback based. That means that it can work with any plugin host, or jobs in both vim & neovim. This opens up a lot of possibilities for the nREPL client.

Socket REPL

Socket REPL is now an endorsed REPL that people are looking to interact with. There are some attempts to make the socket REPL suitable for use by tooling:

A new client plugin may need to be aware of this, and be loosely coupled to allow for both Socket REPL and nREPL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment