Today’s post will be quite technical, but it might be useful for someone, so I’m sharing what I have learned some time ago.
Assume that you are writing a command which should take two parameters. When called interactively, it should ask for both of them in the minibuffer. However (and here the tricky part comes), you want the prompt for the second one to include the first one. (If you can’t see any possible use for such behavior, try M-x copy-file
. That function, however, is written in C, not in Elisp, so studying its source code probably won’t help us too much.)
Of course, this won’t work:
(defun my-command-broken (arg1 arg2) (interactive (list (read-string "First arg: ") (read-string (format "First arg was %s. Second arg: " arg1)))) (message "First arg was %s, second arg was %s." arg1 arg2))
The reason is clear: arg1
and arg2
are bound at the end of the interactive
call, so when the prompt for the second argument is needed, arg1
is not yet bound.
Well, it is quite similar to the let=/=let*
distinction. If only there were something like interactive*
…
Well, there isn’t, but it turns out that it’s easy to simulate. Look at this:
(defun my-command (arg1 arg2) (interactive (let* ((arg1 (read-string "First arg: ")) (arg2 (read-string (format "First arg was %s. Second arg: " arg1)))) (list arg1 arg2))) (message "First arg was %s, second arg was %s." arg1 arg2))
The let*
form does its work of binding arg1
before the prompt for arg2
is computed. Then, we just construct the list of the two just-read arguments. (Notice also that I chose to use the same names within the let form as in the defun. It’s ok, though not necessary – they may be something completely different, say, spam
and larch
.) The point is that the form right after interactive
should return a list of two elements, and that is everything interactive
cares for – what the (local) variables inside let*
are called is none if its business. The reason I chose the very same names is convenience and readability, though YMMV.
Now I’m quite sure that you could cook up a macro interactive*
, which would wrap all this in a nice syntax. That, though, I will try some other day.