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.