Emacs And Virtualenv
Mon May 11, 2020Minor diversion into Emacs Lisp.
So, at work I'm one of the few dinosaurs who still uses Emacs for basically everything. You can see evidence of this in my machine setup routine. I one hundred percent recommend you do the same, especially if you have any interest in any Lisps at all.
My work mostly isn't Lisp, but Python. And one of the things I end up doing is running emacs -nw
in a bunch of different virtualenv
-enabled directories1. This has been annoying in one noticeable way; it's hard to juggle so many instances of the editor mentally, and I've found myself getting confused about which one is getting called where.
So, elisp
to the rescue...
Recoloring Emacs Backgrounds by virtualenv
Source
What I'd like to do is make sure that my editor window is colored differently, depending on which virtual environment I come from. For the setup I'm using, it's enough to check the environment variable VIRTUAL_ENV
. I generally use Emacs from outside any venv
s, because most of my personal hacking still happens outside of Python, so I still want that to be a working use case.
First off, getting the name is trivial. We want to check that environment variable, and take its last path element.
(defun virtual-env-name ()
(if-let venv (getenv "VIRTUAL_ENV")
(file-name-nondirectory venv)))
If that var is unset, we want to return nil
. If it isn't, we want to get its file-name-nondirectory
. Of course, in order to express that elegantly, we need to define a utility macro.
(defmacro if-let (name test then &optional else)
(let ((tmp (gensym)))
`(let ((,tmp ,test))
(if ,tmp
(let ((,name ,tmp)) ,then)
,else))))
Or you could probably include it from somewhere. I don't know. Still fairly straight-forward.
Next up, we need to make a color out of an incoming string. Also, straightforward.
(defun color-of (input)
(concat "#" (substring (secure-hash 'md5 input) 0 6)))
Ok, last bit. I want to set the background color of my editor to that color. But also, if the color is dark enough, I might need to reset the foreground color so that things don't get hard to read. This turns out to be non-trivial, but still fairly easy. There's a stock package called color
that lets you convert between RGB
and HSL
color models. It takes input as float-tuples rather than strings though, and we need to ultimately feed this color to set-*-color
in CSS format though. There doesn't seem to be an obvious parsing function around, so that's something we need too.
Parsing first.
(defun hex->rgb (hex-string)
(mapcar
(lambda (h)
(/ (float (string-to-number h 16)) (float 255)))
(list
(substring hex-string 1 3)
(substring hex-string 3 5)
(substring hex-string 5))))
I make the assumption that input is going to be in the format #rrggbb
rather than rrggbb
or any of the other options, and that simplifies things quite a bit here. Ok, now we're ready for the magic trick...
(defun set-colors-by (input)
(when input
(let* ((color (color-of input))
(lightness (third (apply #'color-rgb-to-hsl (hex->rgb (color-of input))))))
(set-background-color color)
(set-foreground-color
(if (>= lightness 0.5) "white" "black"))
(list color lightness))))
With that in place, and having added (set-colors-by (virtual-env-name))
to my .emacs
, I now get
- The default color scheme when I'm outside of a
virtualenv
- A different background color for each env when I launch in one
- Readable text no matter what color my background is set to
Which is basically everything I was hoping to get out of this.
- This is mainly because, despite my attempts at doing so, I have not yet read up on and practiced enough with
guix
to seriously recommend it as the one package manager to rule them all. I'm getting dangerously close. What I've read and seen so far has had me replacing mynix
installation with aguix
one even though I likely won't have much time to pour in it.↩