Nekthuth Plugin System

Nekthuth works by establishing a contract between vim and common lisp which can be extended by the enterprising user. Each message between the two parts starts with a single command letter, followed by the message. This is largely abstracted from you, but the two important concepts are that all messaging is asynchronous from vim, and each mesasge is uniquely identified by command character. Synchronous calls are faked by polling with a timeout of ten seconds, and by convention all user defined plugins should use lowercase command letters.

Creating a plugin file

To create a plugin file, just create a bash script named "<pluginname>.nek". Each plugin file is a bash script split into three parts as shown in the followign example: the meta information, the lisp code and the vim code. See plugins I've created for examples.
#!/bin/bash

# Example plugin
#
Author="Frank Duncan <frank@kank.net>"
Revision=1.0
Date="26 June 2009"
Description="A description"

vim << VIMEND

...
SOME VIM CODE
...

VIMEND

lisp << LISPEND

...
SOME LISP CODE
...

LISPEND

Meta information

The meta information segment defines four variables that will someday be used for a type of registry. Right now they are a little pointless, but I will hopefully put something! They are:

  • Author - you!
  • Revision - What version this is!
  • Date - When you created it
  • Description - A short description of what it does

Vim code

The vim code should be piped to a function entitled vim. This code will be installed in the NEKTHUTH_HOME/vim directory of the user in a file named after your plugins filename but with the .nek extension replaced by .vim

The section of the .nek file should look like the following:

vim << VIMEND

...
SOME VIM CODE
...

VIMEND

Lisp code

Like the vim code above, the lisp code should be piped to a function called lisp. The destination file on the users machine will be in a similar location.

The section of the .nek file should look like the following:

lisp << LISPEND

...
SOME LISP CODE
...

LISPEND

Installing your created plugins

You can install your plugin the same way any user may. See the regular manual for more information

Distributing your plugins

Mostly that's a problem for you to solve as I have no system to get the word out! Though I'll happily put it on my site if you take the time to build one and email it to me.

Three types of plugins

One way communication from vim to common lisp

Inside vim, you can use the lispSend command to send a one-way message to lisp. On the lisp side, you will need to use the register-receiver function to set up the callback. The function can take any number of arguments, and will be called with apply and the list of read items from the vim stream. In this manner, you can send the string "12 42" from vim, and register a two-argument receiver, or one that takes a &rest argument. Please note that if you would like to send a string, you must escape the quotes, as read is called on everything.

One way communication from common lisp to vim

Inside lisp, you can use the #'register-sender function to let nekthuth know that each time a repl is evaluated or a command is processed, your function should be called, the return value will be sent to vim. register-sender takes a command character and a function, which should be matched on the vim side by registering a receiver. The function registerReceiver in vim/pythong takes the same command character, a function, and a boolean which when set will alert the user that a message from vim is waiting.

Because there may be multiple threads interfacing with the same running lisp due to multiple open vim seesions, global variables are highly discouraged. However, you may need state to be maintained from sender call to sender call, you can return multiple values from your sender call. The first will be sent to vim, but every other on will be passed again to your sender function the next time it is called. You should use &optional or &rest arguments for this to ensure you don't crash your sender.

Note, you can create an async RPC by creating a callback inside vim, and sending a one way message to common lisp. This is how the REPL and debugger work.

Faked synchronous queries of the common lisp system

While this is in actuality an asynchronous RPC, the vim nekthuth frameworks fakes it by blocking for a maximum of ten seconds waiting for a response. You could recreate this using the two types of communication from above, however for convenience a single means is available through the registerCommand and register-command functions, in vim and lisp respectively.

When executed, errors will be ignored as this is happening from the main common lisp thread. If you do encounter an error, a string telling you so will be returned, rather than launching into a debugger. While this is not the best development cycle in the world, it has been 'good enough' for the author as there is usually not a lot of code involved.

Functions Available

To assist with plugin creating, a number of helper methods have been created and 'exported,' which is more philosophical than technical in python.

Python functions in vim

While these are not all the functions defined in nekthuth.vim, these are the ones whose APIs I will no change without updating the changelog, and providing a depreceation grace period.

lispSend (cmdChar msg) => null

Sends a message through the nekthuth without caring about the result.

getBufferNum => window number

Gets the nekthuth vim buffer. Useful for setting local properties on it.

bufferSend (msg) => null

Writes message out to the nekthuth vim buffer, then returns control to the current window

lispSendReceive (cmdChar msg) => string

Sends a psuedo blocking call throw the nekthuth, returning a string (whatever the corresponding lisp command returned after being converted to a string).

registerReceiver (cmdChar callback bell) => null

Registers a callback function for messages that will come asynchronously through the nekthuth.

getCurrentPos => dict

Gets the current position as a dictionary of "top", "line", "col" Top being the line number of the current top of the screen, with line and col being the zero indexed line numbers. Note that vim actually deals with one-indexed line and column numbers, but we use zero indexed for ease with indexing into vim.current.buffer.

gotoPos (pos) => null

Takes a dict (as returned by getCurrentPos()) and positions the cursor accurately.

getSexp (count) => string

Returns a string of the form containing the cursor. If 0, or 1, just the form is returned. If 2, the form containing that form is returned, and so on. If the number provided is greater than the level of nesting, returns the toplevel form.

All vim plugins are actually written in python which is then interpreted by vim. Currently python 2.3 or later is required, and any plugins you write would do well to also use that language.

In lisp

register-receiver (cmd-char func) => nil

Register a func to be called when a message comes through the nekthuth

register-sender (cmd-char func) => nil

Register a func to be called every time the repl is evaluated, or some command happens

register-command (cmd-char func) => nil

Register a command for an asynchronous vim call

Common gotchas

Example plugins

NekthuthDescribe

Synchronousely sends the word under the cursor through the nekthuth as an argument of describe, then outputs the response into the repl buffer. Download the plugin.

Vim code

command! -nargs=1 NekthuthDescribe python nekthuthDescribe('<args>')
map <Leader>d :NekthuthDescribe <C-R><C-W><CR>

python << EOF
def nekthuthDescribe (form):
  sendToBuffer('> (describe \'' + form + ')')
  sendToBuffer(lispSendReceive('e', form))
  sendToBuffer('')
EOF

Lisp code

(nekthuth:register-command #\e
                            (lambda (msg)
                             (with-output-to-string (*standard-output*)
                              (describe msg))))

Load current file's function definitions with no output

This is a sender from vim, which will send the entire contents of the file, and a corresponding receiver on the lisp side which will only evaluate forms starting with defun or defmacro. Download the plugin.

Vim code

map <Leader>l :python loadFileFunctions()<CR>
python << EOF
import vim
def loadFileFunctions():
  vim.command ("1,$y f")
  fileStr = vim.eval("@f")

  vim.command ("let @f=@_")
  lispSend ("f", fileStr)
EOF

Lisp code

(defun eval-defuns (&rest forms)
 (mapcar
  (lambda (form)
   (when (and (consp form) (or (eql 'defmacro (car form)) (eql 'defun (car form))))
         (eval form)))
  forms))

(nekthuth:register-receiver #\f #'eval-defuns)

LispDo Command

Send a string through the nekthuth, with the return printed in the message area. Downside of this is that you can't send the string (print "HI"), as the interpolation of <args> gets messed up. Download the plugin.

Vim code

command! -nargs=* NekthuthDo python print lispSendReceive('d', "<args>")

Lisp code

(nekthuth:register-command #\d (lambda (msg) (eval msg)))

Alert vim when the package has changed, set it in the status line

Here we want to have Lisp continually send us updates on the current package. We'll take that information and set the statusline of our lisp buffer in vim. This is a push call from lisp, so we will need to register a sender on the lisp side and a corresponding receiver on the vim side. Download the plugin.

Vim code

python << EOF
import vim
# Save the current window number, get the buffer number, move to it, do our work, move back
def setStatus(msg):
  curwinnum = vim.eval("winnr()")
  bufwinnum = getBufferNum()
  vim.command (bufwinnum + "wincmd w")

  # Note that we coerce the first message out of the list
  vim.command ("setlocal statusline=Current\\ Package:\\ " + msg[0])
  vim.command (curwinnum + "wincmd w")

registerReceiver('s', setStatus, False)
EOF

Lisp code

(nekthuth:register-sender #\s (lambda () (package-name *package*)))