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.
#!/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
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:
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
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
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.
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.
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.
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.
To assist with plugin creating, a number of helper methods have been created and 'exported,' which is more philosophical than technical in python.
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) => nullSends a message through the nekthuth without caring about the result.
getBufferNum => window numberGets the nekthuth vim buffer. Useful for setting local properties on it.
bufferSend (msg) => nullWrites message out to the nekthuth vim buffer, then returns control to the current window
lispSendReceive (cmdChar msg) => stringSends 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) => nullRegisters a callback function for messages that will come asynchronously through the nekthuth.
getCurrentPos => dictGets 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) => nullTakes a dict (as returned by getCurrentPos()) and positions the cursor accurately.
getSexp (count) => stringReturns 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.
Register a func to be called when a message comes through the nekthuth
register-sender (cmd-char func) => nilRegister a func to be called every time the repl is evaluated, or some command happens
register-command (cmd-char func) => nilRegister a command for an asynchronous vim call
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
(nekthuth:register-command #\e (lambda (msg) (with-output-to-string (*standard-output*) (describe msg))))
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
(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)
command! -nargs=* NekthuthDo python print lispSendReceive('d', "<args>")
(nekthuth:register-command #\d (lambda (msg) (eval msg)))
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
(nekthuth:register-sender #\s (lambda () (package-name *package*)))