Nekthuth is simultaneously a plugin for vim and library for Common Lisp that allows developers to interact with a running interpreter from inside their editor.
cd ~/.vim wget "http://nekthuth.com/releases/nekthuth-vim_latest.tar.gz" tar zxf nekthuth-vim_latest.tar.gz
Define the *hyperspec-location* to point to that directory in your ~/.nekthuthrc.lisp.
cat << EOF > ~/.nekthuthrc.lisp (defvar *hyperspec-location* "/path/to/hyperspec/") EOF
Test to see that the directory is correct in sbcl (so that nekthuth won't error on you)
CL-USER> (load (merge-pathnames #p".nekthuthrc.lisp" (user-homedir-pathname))) T CL-USER> (probe-file (merge-pathnames #p"HyperSpec/Front/X_AllSym.htm" (pathname *hyperspec-location*))) #P"/path/to/hyperspec/HyperSpec/Front/X_AllSym.htm"
Import nekthuth into a running session
CL-USER> (asdf:oos 'asdf:load-op 'nekthuth) ; Redacted comments and warnings NIL
Start up the nekthuth listener
CL-USER> (nekthuth:start-remote) #<SB-THREAD:THREAD "Main Remote Thread" RUNNING {ADE9AD9}>
Please note that local execution of code is actually more error prone because it has to do all the error checking that you would do manually with remote start (bsd-sockets installed, nekthuth installed, etc.) on boot, so it may be easier to start with the remote setup as listed above.
Setup vim to know where the sbcl executable is (defaults to /usr/bin/sbcl):
echo 'let g:nekthuth_sbcl = "/path/to/sbcl"' >> ~/.vimrc
Test your installation by editing a .lisp file and adding a simple line such as (print "Hello world"). To start the interpreter in a second buffer, which should appear on the left, use either the :NekthuthRemote or :NekthuthOpen command, if doing remote execution or local execution, repsectively. Position your cursor over the opening brace and use the :NekthuthSexp command to send the print statement to the nekthuth. If all goes well, your screen should look like the following:
To upgrade, in lisp re-running asdf-install will suffice, while in vim running the above download and tar command will suffice, as I will not remove any files in the foreseeable future.
The basic function of nekthuth is to allow you to send expressions to the interpreter without leaving your vim window. From any open buffer, by positioning your cursor inside some list, you can use the NekthuthSexp command. If you are over an atom that is not inside a list, just the atom will be sent. The intepreter will use the current body of functions and symbols you have defined to executes as if running at the top level. If your s-expression involves some long operation (or a sleep), vim will not be able to immediately print your response. You will see a printline show up in the message area telling you that a response from lisp is waiting. At that time, simply move your cursor to trigger the buffer dump function.
In this example, we'll create a long running command via a sleep statement, and send it through the nekthuth.
After the 5 seconds are up, a message appears at the bottom.
Moving the cursor will let the vim loop print the waiting response.
In this way, even if there is a long running process going, you can still move around vim, or send an interrupt (discussed below).
The NekthuthTopSexp command will send the top level S-expression to the interpreter. By using this as well as NekthuthSexp, you can test out individual expressions inside function definitions before sending the whole thing. In the following code sample, positioning your cursor over the first print statement, the let block, and the defun block will result in different output using NekthuthSexp, but will all do the same thing when using NekthuthTopSexp
(defun say-hello () (let ((my-var "world")) (print "Hello") (print my-var)))
Note that if you position your cursor over "(print my-var)", and use NekthuthSexp, you would get an error as my-var is not bound at the top level.
Typing the previous commands whenever you want to interpret somthing will become laborious quickly. In my normal setup, I have bound <C-J> to NekthuthSexp and <C-E> to NekthuthTopSexp:
au FileType lisp map <C-J> :NekthuthSexp<CR> au FileType lisp map <C-E> :NekthuthTopSexp<CR>
When done in this way, you can preface <C-J> with a number, and it will evaluate that many s-expressions above the one your cursor is in. For instance, in the code sample above, if you were to position your cursor over (code (print "Hello")), and use <C-J>, you would see "Hello" printed to the interpreter buffer. However, if you were to us 2<C-J>, the entire let block would get sent.
When your cursor is over an s-expression for which you would like to see the macroexpansion (under macroexpand-1), you can use the :NekthuthMacroExpand command to have vim print the expanded form in the message window below your buffers. In this manner, you can easily see how your code is getting expanded without crowding your interpreter buffer.
Like NekthuthSexp, when a shortcut key is bound to NekthuthMacroExpand (I bind <C-M>), a number can preface the command to tell nekthuth how many levels up you would like sent to macroexpand-1.
Nekthuth asks the running lisp system for vim style tab completion. Using the omnifunc setting (access with <C-X><C-O>), vim will provide a menu of possible functions/symbols that have currently been defined. You can then select which one you would like to use normally. You can use hyphen syntax such that "m-v-l" can expand to "multiple-value-list" and "multiple-values-limit."
Tab completion supports both functions in the current *package* (and packages that transitively uses), but also external symbols of defined packages in your system. You can use hyphen syntax for those as well, so for instance "s-i:w-r" will expand to both "sb-int:with-rebound-io-syntax" and "sb-introspect:who-references".
When the standard lisp syntax file for vim is used, all common lisp symbols will be highlighted (usually in yellow, but it depends on your theme). Nekthuth introduces the lispLocalFunc syntax group, which is aliased to the Function group (usually shows up as Cyan). Whenever a local function/macro is defined in your top level, all occurences of that symbol will be marked as a Function, giving you greated confidence that you have avoided a typo.
This highlighting is extended to the external functions and macros of all packages loaded up on the system. Therefore, if you use asdf to load up something like 'cl-ppcre, then functions such as cl-ppcre:scan-to-strings will be highlighted in blue. For the current *package*, internal functions will also be highlighted as above.
The Clhelp command is used to look up a function in the hyperspec. Tab completion from the tags file is available for Clhelp at the vim command line. You can further use a vim mapping to look up the symbol currently under your cursor:
au FileType lisp map <C-H> :Clhelp <C-R><C-W><CR>
When an error is signaled inside your lisp code, the message window will come up with available restarts, as well as a limited backtrace. You can choose between these, which will get sent to the interpreter such that you can move on. The default, and always available, restart is nekthuth-abort, which will return the REPL to the nekthuth top level.
The debugger is currently the trickiest part of the entire library. Mainly due to shortcomings in the author, the following may have issues:
When you enter a long running, or infinite lisp expression, you can use the NekthuthInterrupt command to cause the thread to become interrupted and you to be prompted with a debugger. At that time you can use the normal 'nekthuth-abort restart, or continue on with the 'soldier-on restart. For ease of use, I map this command to <C-C>.
When your cursor is over a symbol, you can use NekthuthSourceLocation to open up the file and location that the function/macro was defined in. If your symbol was defined in the top level through the REPL (or using the load function, rather than compiling and loading the fasl), sbcl cannot collect enough information for Nekthuth to open up the correct file. For ease of use, i map this command to <C-]>.
When using the following commands, you can enable an already running lisp to start listening for connections from vim instances. If you would like to have two different lisp instances listening, you will need to start the second up with a different port as discussed below. Each listening thread will aaccept multiple connections from however many vim instances you would like to have connected to it, and all of the syntax highlighting should just work between all those instances. Currently localhost is hardcoded into the system.
To start a thread that listens for remote nekthuth connections inside a running vim, run the following in your lisp instance. Please note that you can only have one of these threads going at once in that running lisp (but why would you want two?).
CL-USER> (asdf:oos 'asdf:load-op 'nekthuth) ; Redacted comments and warnings NIL CL-USER> (nekthuth:start-remote) #<SB-THREAD:THREAD "Main Remote Thread" RUNNING {ADE9AD9}>
To then stop the thread the thread, use the following.
CL-USER> (nekthuth:stop-remote) NIL
The default remote port is 8532, and can be used only in only one common lisp session at a time, though many vim windows can connect to it.
In your ~/.nekthuthrc.lisp, just set the nekthuth variable *remote-port*
(defvar *remote-port* 31337)
In your vimrc, you need to set it accordingly
let g:nekthuth_remote_port = 31337
If you would like to boot up a second lisp to connect to with a vim, you can specify which port to use in the startup functions on both sides. Please note, if you want to connect multiple vims to one lisp, that's supported out of the box and you can just do things as normal.
CL-USER> (nekthuth:start-remote 31337) #<SB-THREAD:THREAD "Main Remote Thread" RUNNING {AAE4241}>
And when inside your vim session:
:NekthuthRemote 31337
~/.vim/ftplugin/lisp/nekthuth/install.sh randomplugin.nek
By default the nekthuth home directory where plugins will be installed is in $HOME/.nekthuth/. In that directory two subdirectories will be created: vim/ and lisp/ These are for the vim and lisp components of a plugin respectively. If you would like to change that installation, set your NEKTHUTH_HOME environment variable
echo "export NEKTHUTH_HOME=/path/to/some/dir/" >> ~/.bashrc
All nekthuth configuration is set using defvar in your ~/.nekthuthrc.lisp. Note that all the variables you set and functinos you define will be executed in the package nekthuth.
I use the following vim mappings with Nekthuth for my ease of use.
inoremap <C-L> <C-X><C-O> imap <Tab> <C-N> " Can prefix with number au FileType lisp map <C-J> :NekthuthSexp<CR> au FileType lisp map <C-E> :NekthuthTopSexp<CR> " Can prefix with number au FileType lisp map <C-K> :NekthuthMacroExpand<CR> " Close and re-open the nekthuth au FileType lisp map <C-I> :NekthuthClose<CR>:NekthuthOpen<CR>:redraw!<CR> " Get word underneath cursor au FileType lisp map <C-H> :Clhelp <C-R><C-W><CR> au FileType lisp map <C-C> :NekthuthInterrupt<CR> " Find the source location of the symbol au FileType lisp map <C-]> :NekthuthSourceLocation<CR> au BufRead *.asd set ft=lisp " Dropdown selection colors highlight TabLine ctermfg=gray highlight TabLineFill ctermfg=black highlight TabLineSel ctermfg=white highlight Pmenu ctermbg=Black highlight PmenuSel ctermbg=Red set completeopt=longest,menuone inoremap <expr> <cr> pumvisible() ? "\<c-y>" : "\<c-g>u\<cr>" let g:lisp_rainbow = 1