Lisp in Vim with Slimv or Vlime (2019)
susam.in
susam.in
If you've worked in Java, download the JRebel extension for more complete hot-reloading support, and that'll give you an approximation of the interactive development experience. It's especially useful for web apps where you can change the code for an endpoint (or used by several endpoints, whatever) and can then just test it out with new requests, no need to restart the server.
I had some fun doing "debugger driven development" with Lisp for the Synacor Challenge. Basically, you have to implement an interpreter/VM and there are some number of instructions (around 20?). Instead of trying to correctly implement all of them from the start, I had a core loop like this (not exactly, summarized):
(loop with pc = 0
for instruction = (aref memory pc)
do (ecase instruction
))
ecase will signal an error whenever there is no matching case, taking you to the debugger. I read what instruction caused the error, checked the spec, implemented it, and resumed the program. Throw in a few asserts as sanity checks to complete the process. I don't recall the instruction set, but suppose that "add a b c" means add the values in registers a and b and store in c, then maybe I'd do something like this: (10 ;; or whatever opcode meant ADD
(... logic for addition ...)
(assert (= c (+ a b))) ;; with whatever was needed to obtain these values correctly
If I implemented something incorrectly, I just rewrote the logic, recompiled, and restarted again. It was an enjoyable way to build the system, but of course it also relied on having a fairly comprehensive notion of what the end result would be and I did have to refactor at the end. But I had some good test cases by that point so refactoring wasn't bad.I did the same thing for another project where I had a variety of input files that exercised various parts of the system and just continuously iterated until I got the expected result for an input relying on the debugger (triggered by ecase and assert and (error 'unimplemented)) to detect where I hadn't implemented something or had implemented it incorrectly or incompletely (like maybe one input exercises a particular edge case that others didn't). Once a file was properly processed, I wrote that up as a proper test, and moved on to the next file. Which then made the refactoring easy because I had a comprehensive test suite.
I wrote myself a shell script that opens a new terminal tab running SBCL with the swank server, so the “connect” from vim is fully automated.
There are two wrapper scripts, for Terminal (`ritt`) and for iTerm (`riitt`), in addition to the `swank-lisp` script. Choose one of the wrapper scripts for your use case.
The relevant lines in ~/.vimrc, adjust your `sbcl` path:
let g:slimv_swank_cmd = '!ritt swank-lisp -d "' . getcwd() . '"'
let g:slimv_lisp = '/usr/local/bin/sbcl'
let g:slimv_impl = 'sbcl'
let g:slimv_ctags = 'ctags'
`ritt` (“run in Terminal tab”) for use with Terminal.app: #!/usr/bin/env osascript
on run arglist
if (count of arglist) > 0 then
-- The command to run is built from blank-separated script arguments.
set text item delimiters to {" "}
set theCommand to arglist as string
tell application "Terminal"
activate
-- Make a new Terminal tab just like the current one.
tell application "System Events" to keystroke "t" using {command down}
set newTab to the last tab of front window
-- Run the command in the new tab.
do script theCommand in newTab
end tell
end if
-- Try to cause a minimum of disturbance in the calling environment instead of outputting random AppleScript values.
return ""
end
This is `riitt` (“run in iTerm tab”) for use with iTerm.app: #!/usr/bin/env osascript
on run arglist
if (count of arglist) > 0 then
-- The command to run is built from blank-separated script arguments.
set text item delimiters to {" "}
set theCommand to arglist as string
tell application "iTerm"
activate
-- Make a new Terminal tab just like the current one.
tell application "System Events" to keystroke "t" using {command down}
set newTab to the last tab of front window
-- Run the command in the new tab.
write (current session in newTab) text theCommand
end tell
end if
-- Try to cause a minimum of disturbance in the calling environment instead of outputting random AppleScript values.
return ""
end
And here the `swank-lisp` script (adjust the `swankdir` to your install location): #!/bin/bash
swankdir="${HOME}/.vim/bundle/slimv.git/slime"
self=$(basename "$0")
umask 022
set -u
Usage()
{
cat <<EOD >&2
Usage: $self [-d <working-dir>]
EOD
exit 1
}
# Argument Scanning
if [ "$*" = "--help" ]
then
Usage
fi
devDir="${HOME}/Develop/Lisp"
while getopts "d:X" opt
do
case "$opt" in
(d)
if [ -n "$OPTARG" ]
then
devDir="$OPTARG"
fi
;;
(X)
set -x
;;
(?) # Unknown option
Usage
;;
esac
done
# Shift away parsed option flags
shift $((OPTIND - 1))
# Change to user's development directory, so files are easier to locate.
cd "$devDir" || exit $?
sbcl --dynamic-space-size 16384 --load "${swankdir}/start-swank.lisp"
echo # Newline before next shell prompt.Though the neovim community does go pretty heavy towards lua compared to the traditional vim community.
> the neovim community does go pretty heavy towards lua
And that's why I like them.
To be honest, when I am reading other people’s Common Lisp code, if there are many files in their projects, I prefer VSCode. And for my own hacking joy I like LispWorks, usually using the IDE, but sometimes using Emacs.
It is the same way with Clojure: IntelliJ+Cursive is just more facile for me than with Emacs.
On a tangent, I feel the popularity of vi style bindings is more of an indictment of the standard substandard terminal based keyboards we use. Emacs bindings make a lot more sense ergonomically on a space cadet style keyboard where control and meta are comfortably pressed with the thumb.
[1] Haha, only serious.
It's been pretty amusing watching the LSP landscape evolve for other languages, it's almost like swank for CL. But it's rather nice to have the server be embedded in the process itself. On my personal web server I have a compiled lisp binary running, but I shipped it with swank listening on a local port, so if I want to change something without rebuilding and redeploying I can just SSH in while forwarding port 4005, connect to the lisp image with my local editor, and recompile functions or whatever. At my last job I also inserted ABCL into the huge Java app on my dev box and had it start a swank server, letting me connect with vim and mess around -- it was mostly useful for quickly launching system tests which otherwise had a dedicated clunky browser UI, and writing some code to quickly extract or insert data. I had some designs to write some webdriver tests in Lisp and demo how the debugging experience when one fails can be much better (not having to restart the whole flow because a UI element changed its class name or whatever and threw an exception) so as to introduce the language to the broader company officially (and hopefully push for officially sanctioned use), but never got around to it before I left.