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 thedkey.
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.