For several years now I've been using color in my Emacs buffers to mark whether it's a "work" project or a "personal" project. I think I first showed this in a blog post I wrote in 2017, when I had switched to spaceline
's mode line. Since then I've extended that color to also work on the tab line, current line, and header line. I wanted to make it work with the cursor color but couldn't figure that out.
I saw a question on stack exchange today that made me realize I could extend this to have one color per project, similar to how rainbow-identifiers works.

How does this work? The magic function is face-remap-add-relative
, which lets you change a face for one buffer only. I call this from several hooks that run rarely:
(defun amitp/set-modeline-color () "Set mode line color based on current buffer's project" (let ((color (project-color-background))) (face-remap-add-relative 'tab-line-tab-current :background color :foreground "white") (face-remap-add-relative 'mode-line-active :background color :foreground "white") (face-remap-add-relative 'line-number-current-line :background color :foreground "white") )) (add-hook 'find-file-hook #'amitp/set-modeline-color) (add-hook 'dired-mode-hook #'amitp/set-modeline-color) (add-hook 'change-major-mode-hook #'amitp/set-modeline-color) (add-hook 'temp-buffer-setup-hook #'amitp/set-modeline-color)
But how do I set the color? I hash the project root and then separate that hash out into hue, saturation, and lightness to construct an HSL color. Every file/buffer in that project gets the same color theme:
(require 'project) (require 'color) (defun project-color-background () "Return a background color for the project containing this directory" (let* ((project (project-current)) (dirhash (sxhash (if project (project-root project) default-directory))) (hue (/ (mod dirhash 1000) 1000.0)) (saturation (+ 0.3 (* 0.1 (mod (/ dirhash 1000) 3)))) (lightness (+ 0.4 (* 0.05 (mod (/ (/ dirhash 1000) 3) 4)))) (rgb (color-hsl-to-rgb hue saturation lightness))) (color-rgb-to-hex (nth 0 rgb) (nth 1 rgb) (nth 2 rgb) 2)))
If there's no current project, I'll use the current directory. I tried to pick medium saturation and lightness for the colors. Most of them are pleasing but I need to tweak the greens and yellows to be a little bit darker, or I need to switch to using oklch colors.

An additional tweak not shown here is that I still want to distinguish work and personal projects, so I use one color range for work projects and another color range for personal projects.
I've put the code up in a github gist.
Labels: emacs
Post a Comment