Emacs And Virtualenv

Mon May 11, 2020

Minor 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 venvs, 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

  1. The default color scheme when I'm outside of a virtualenv
  2. A different background color for each env when I launch in one
  3. Readable text no matter what color my background is set to

Which is basically everything I was hoping to get out of this.

  1. 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 my nix installation with a guix one even though I likely won't have much time to pour in it.


Creative Commons License

all articles at langnostic are licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License

Reprint, rehost and distribute freely (even for profit), but attribute the work and allow your readers the same freedoms. Here's a license widget you can use.

The menu background image is Jewel Wash, taken from Dan Zen's flickr stream and released under a CC-BY license