onilog

Combining Shell and Lisp in Eshell

11 September 2021 11:03 AM (emacs | eshell | perforce | vc-p4)

The code in this post is entirely useless since Perforce already provides this feature out of the box, I just didn't know about it at the time. Still, I wanted to post something and this seemed as fun as anything else.

I have been working on vc-p4 off-and-on for a while to make working with Perforce more enjoyable in Emacs. I have some plans for that package. One of the bigger things that I have done so far was add the option to specify the client in the .dir-locals.el.

So in my .dir-locals.el I would have something along the lines of the following:

((nil . (vc-p4-client . "SOME-CLIENT-NAME")))

This would let Emacs switch automatically between the different clients.

I wanted to use the p4 command-line rather than P4V more, but a thing that bugged me was that this didn't allow me to automatically pick the client, and I didn't like having to type in p4 -c SOME-CLIENT-NAME ... all the time. I felt like with Emacs, Eshell, and the feature I added to vc-p4 surely I should be able to do something about this.

The first problem I ran into is that Eshell doesn't load the directory-local variables when I change into a directory. So I wrote something to do that:

(defun oni-eshell-set-local-variables ()
  (dolist (elt file-local-variables-alist)
    (set (make-local-variable (car elt)) (default-value (car elt))))
  (setq file-local-variables-alist nil)
  (hack-dir-local-variables-non-file-buffer))

First it goes through all of the local variables that have been set before and resets them to their default value. This is so that any variables that are set locally don't hang around when you leave the directory. It then sets the list of currently set file local variables to nil so that it doesn't consider them cached and skips over giving them new values. Finally it calls the hack-dir-local-variables-non-file-buffer function that specifically exists to set directory-local values for variables in a buffer that isn't associated with a file.

(add-hook 'eshell-directory-change-hook #'oni-eshell-set-local-variables)

The function needs to run every single time the current directory changes, which Eshell has a hook for.

This would let me use a single variable essentially for specifying the current client to use. As long as there is a value this would work:

p4 --client $vc-p4-client ...

That's definitely easier than having to remember exactly which client I was using. It can still be better. I know that in Eshell shell-like constructs can be combined with Lisp easily by using either ${} or $(). So really I can just use a single command that checks whether there is a value for the client or not and calls p4 accordingly:

p4 $(when vc-p4-client (list "--client" vc-p4-client)) ...

This is very wordy though. Since this can be called any time it's nice to just make it an alias:

alias p4 'p4 $(when vc-p4-client (list "--client" vc-p4-client)) $*'

Now I can just call p4 and it'll specify the client for me automatically.

I haven't done a lot of stuff in Eshell, so I liked being able to write a fun little alias that combined some shell command with some simple Lisp code. Of course only after this I discovered (well, someone pointed out) that Perforce has the P4CONFIG environment variable, which names a file name to look for up the directory tree from the current directory (much like the .dir-locals.el works) and read settings from there. So I set that p4 set P4CONFIG=.p4config, and then specify the client in there.

P4CONFIG=SOME_CLIENT_NAME

And now I don't have to go through any of this, I can remove the whole feature I added to vc-p4 too.

Comments are closed.