SLIME and in-package

Pascal J. Bourguignon pjb at informatimago.com
Sat Feb 6 09:53:55 UTC 2016



On 06/02/16 08:39, Peter Keller wrote:
> Hello,
>
> I have a question, hopefully easy, but I can't seem to find the answer.
>
> I'd like to evaluate a region which contains something like this:
>
> (asdf:load-system '#:foo)
> (in-package #'foo)
>
> and have my slime REPL actually change its current package.
Slime evaluates expressions coming from emacs in a separate thread in 
the inferior lisp.
This thread cannot modify the state of the REPL thread, unless there is 
specific code (that you should write), to have those threads communicate 
and change the binding of *package* in the REPL thread (suppedly, at 
some specific points, you wouldn't want to do that at random in the 
middle of the evaluation (or worse, of the reading) of an expression in 
the REPL).
> I also realize that slime may be LOADing the file, which preserves the
> current package. Since I get a => <PACKAGE "FOO"> in my *slime-events*
> buffer I think that may be also happening.

Yes, this is how LOAD is specified.  It binds *package* and *readtable*, 
so that modifications of those bindings inside a loaded file won't 
affect the caller.  However, the loaded files can still _mutate_ the 
current package and readtable.  Not that I would advise that as a 
solution, but theorically, you could "copy" the current package, then 
transform it into the package you want for the REPL! :-)

Actually, it might be easier to do something like that conformingly, 
than dealing with the REPL thread, only, in the case of slime, the RELP 
thread is not the native REPL either, it's a thread created by swank, so 
that it would only involve modifying swank.

If you wanted to mutate the package, you would have to be careful to 
_move_ the symbols to the new package, and to update the use 
dependencies in the other packages.  Only direct references to the old 
package object couldn't easily be updated everywhere, so it would not be 
a good 100% solution, but it could work 99% of the time, if you consider 
that the original package would be CL-USER, and that there is probably 
no code keeping direct references to CL-USER.  Notice that there's no 
constraint imposed by the standard on the CL-USER package, you can do 
whatever you want with it.  On the other hand, you couldn't do that if 
the current package was CL (and some people do (in-package :CL), so this 
may occur).

> Is there any way to change the current REPL package by using any SLIME
> evaluation methods that involve evaluating a region? Or must I either manually
> type (in-package :foo) in the REPL or use the ,in-package SLIME command helper.
What "current" REPL?  You can have any number of REPLs with slime/swank!
Do you want to change the current package in all the REPLs?
This would be very inconvenient, one reason to use multiple REPLs is to 
have different bindings for *package*!
How would you associate a buffer with a REPL?
(I'll let you write the function to do that).

> I'm just about to write some elisp to take the region, find the slime repl
> buffer, manually append the region into the REPL buffer, and enter a return to
> execute it! There must be a better way!
So the problem is actually about the swank REPL.

(If you wanted to have the REPL package changed by loading a file, in a 
native REPL, then some implementations provide a hook eg. when 
displaying the prompt, that you could use to set the current package).

In the case of swank, the repl thread is spawn by 
swank-repl::spawn-repl-thread which calls repl-loop which calls 
swank::handle-requests.  The later uses swank::eval-for-emacs, which 
runs *pre-reply-hook* after having evaluated the form. Unfortunately, 
changing the package after evaluating a form means that it would be one 
expression late.

(in-package :foo) in slime
*package* in repl --> :old-package
*package* in repl --> :foo

We would need a *pre-eval-hook*.  So you will have to patch 
swank::eval-for-emacs adding (run-hook *pre-eval-hook*) before the call 
to eval.

Then you can install a function in the *pre-reply-hook* to save the 
*package*, and another function in *pre-eval-hook* to set *package* to 
the saved value.

Now, there would be little surprises, like:

(in-package :foo) in slime
in repl, the prompt would be: CL-USER>
*package* in repl --> :foo
now the prompt is FOO>



Now looking further into it, we notice that in swank-repl.lisp, there is 
a a repl-eval, stored in *listener-eval-function*, called by the slime 
function listener-eval.  We can see in contrib/slime-repl.el that 
listener-eval the function that is requested to be called from slime 
when you evaluate things in the REPL.  There's no hook here.  In 
swank-listener-hooks.lisp there is an alternative %listener-eval that 
could be used instead of repl-eval, that has a hook, 
*slime-repl-eval-hooks* but it is evaluated after evaluating the forms 
sent by emacs, so it doesn't help either.



However, we notice in swank-repl.lisp that changes to *package* are 
tracked with the macro track-package which sends a message to slime with 
the new package and the new prompt to be displayed in the REPL.

lse> (let ((*package* (find-package :keyword)))
        (swank::send-to-emacs (list :new-package (package-name *package*)
                                    (swank::package-string-for-prompt 
*package*))))
nil
keyword> cl:*package*
#<Package "KEYWORD">
keyword>

But this cannot be used, because it's not the sender who decides what 
slime-repl buffer should be updated. Since emacs is single-threaded, 
there's a single current slime-repl buffer handling those :new-package 
messages at a time.


So it looks like you would need a new message type like 
:new-package-for-repl that would add a handle to the slime-repl 
buffer(s) corresponding to the swank repl thread(s) for which the new 
package is saved.

Also, notice that I've not considered the various modes of operations of 
swank, it can run without multiple threads too, and on the slime side, 
there's slime-repl and slime-mrepl.



A last note: what I do to help the REPL user when loading a file, is 
that sometimes I add a (print '(in-package :this-package)) at the end of 
the file. So that when it's loaded, the user can just copy-and-paste the 
in-package form in the REPL, if he wants to switch to this package.

-- 
__Pascal J. Bourguignon__
http://www.informatimago.com/




More information about the slime-devel mailing list