While I mostly use SBCL for Common Lisp development, I regularly switch to CCL or even ECL to run tests.
This is how I do it with SLIME.
Starting implementations
SLIME lets you configure multiple implementations using the
slime-lisp-implementations
setting. In my case:
(setq slime-lisp-implementations
'((sbcl ("/usr/bin/sbcl" "--dynamic-space-size" "2048"))
(ccl ("/usr/bin/ccl"))
(ecl ("/usr/bin/ecl"))))
Doing so means that running M-x slime
will execute the first implementation,
i.e. SBCL. There are two ways to run other implementations.
First you can run C-u M-x slime
which lets you type the path and arguments
of the implementation to execute. This is a bit annoying because the prompt
starts with the content of the inferior-lisp-program
variable, i.e. "lisp"
by default, meaning it has to be deleted manually each time. Therefore I set
inferior-lisp-program
to the empty string:
(setq inferior-lisp-program "")
Then you can run C-- M-x slime
(or M-- M-x slime
which is easier to type)
to instruct SLIME to use interactive completion (via completing-read
) to let
you select the implementations among those configured in
slime-lisp-implementations
.
To make my life easier, I bind C-c C-s s
to a function which always prompt
for the implementation to start:
(defun g-slime-start ()
(interactive)
(let ((current-prefix-arg '-))
(call-interactively 'slime)))
Using C-c C-s
as prefix for all my global SLIME key bindings helps me
remember them.
Switching between multiple implementations
Running the slime
function several times will create multiple connections as
expected. Commands executed in Common Lisp buffers are applied to the current
connection, which is by default the most recent one.
There are two ways to change the current implementation:
- Run
M-x slime-next-connection
. - Run
M-x slime-list-connections
, which opens a buffer listing connections, and lets you choose the current one with thed
key.
I find both impractical: the first one does not let me choose the implementation, forcing me to run potentially several times before getting the one I want. The second one opens a buffer but does not switch to it.
All I want is a prompt with completion. So I wrote one.
First we define a function to select a connection among existing one:
(defun g-slime-select-connection (prompt)
(interactive)
(let* ((connections-data
(mapcar (lambda (process)
(cons (slime-connection-name process) process))
slime-net-processes))
(completion-extra-properties
'(:annotation-function
(lambda (string)
(let* ((process (alist-get string minibuffer-completion-table
nil nil #'string=))
(contact (process-contact process)))
(if (consp contact)
(format " %s:%s" (car contact) (cadr contact))
(format " %S" contact))))))
(connection-name (completing-read prompt connections-data)))
(let ((connection (cl-find connection-name slime-net-processes
:key #'slime-connection-name
:test #'string=)))
(or connection
(error "Unknown SLIME connection %S" connection-name)))))
Then use it to select a connection as the current one:
(defun g-slime-switch-connection ()
(interactive)
(let ((connection (g-slime-select-connection "Switch to connection: ")))
(slime-select-connection connection)
(message "Using connection %s" (slime-connection-name connection))))
I bind this function to C-c C-s c
.
In a perfect world, we could format nice columns in the prompt and highlight
the current connection, but the completing-read
interface is really limited,
and I did not want to use an external package such as Helm.
Stopping implementations
Sometimes it is necessary to stop an implementations and kill all associated
buffers. It is not something I use a lot; but when I need it, it is
frustrating to have to switch to the REPL buffer, run slime-quit-lisp
, then
kill the REPL buffer manually.
Adding this feature is trivial with the g-slime-select-connection
defined
earlier:
(defun g-slime-kill-connection ()
(interactive)
(let* ((connection (g-slime-select-connection "Kill connection: "))
(repl-buffer (slime-repl-buffer nil connection)))
(when repl-buffer
(kill-buffer repl-buffer))
(slime-quit-lisp-internal connection 'slime-quit-sentinel t)))
Finally I bind this function to C-c C-s k
.
It is now much more comfortable to manage multiple implementations.