I’m gradually working on a custom theme for emacs, and I wanted to make it easier to update and read. (It’s not quite ready to share, but I look forward to doing just that when it is.) Surprising no-one, the theme format on emacs is not incredibly … “clean.” It’s basically a command that passes in a huge list of lists (of lists… this is lisp, after all). That command is custom-theme-set-faces
.
(custom-theme-set-faces 'my-cool-theme ;theme name '( (FACE SPEC) (FACE SPEC) ))
FACE
is the symbol for a particular element, for example default
for default text or error
which is used for error messages. Easy. SPEC
, however is a bit more complicated. SPEC
is a list of DISPLAY
ATTS
pairs, each list of attributes pertaining to its paired display. If this were CSS, I think it would be similar to including the @media
selector for each rule.
(DISPLAY
can simply be default
to apply to all displays, but I haven’t seen this used much and am trying to follow suit.)
So now we have this.
(custom-theme-set-faces 'my-cool-theme ;theme name '( (FACE ((DISPLAY ATTS) (DISPLAY ATTS))) (FACE ((DISPLAY ATTS) (DISPLAY ATTS))) ))
ATTS
is property list, where you can specify the width, height, etc. for the font face, on that display.
So plugging in some actual values, we have the following:
(custom-theme-set-faces 'my-cool-theme ;theme name '( ;; error face, using default DISPLAY (simpler) (error ( (default (:foreground "red" :background "black")) ) ) ;; default face using DISPLAY specified by a a list of conditions (less simpler) ;; In this case, a color terminal that supports at least 89 colors. (default ( ( ((class color) (min-colors 89)) (:foreground "white" :background "black")) ) ) ))
I was working with a bunch of lines that basically looked like the second case. There was some substitution, but it was still painful to see all that repetition.
(let ((class '((class color) (min-colors 89)))) ;; ... `(default ((,class (:foreground "white" :background "black")))) ;; ... )
I wanted to remove the DISPLAY
component completely and just add it later using code. This would mean a cleaner and easier to maintain theme. The excellent emacs-doom-themes has a very elegant solution, but I didn’t quite understand all the elisp and thought I could roll a simpler version myself as a good learning experience.
Lots of fumbling around elisp ensued, but I now have something I’m happy with.
;; the simple-faces are bound to a list `two-fifteen-simple-faces' (default (:background ,bg1 :foreground ,fg1)) (error (:foreground "red" :background "black")) ;; ... ;; Add the display to the simple-faces (while two-fifteen-simple-faces (let* ((simple-face (car two-fifteen-simple-faces)) (fname (car simple-face)) (fattrs (cadr simple-face))) (setq two-fifteen-simple-faces (cdr two-fifteen-simple-faces)) (setq built-faces (append built-faces `((,fname ((,class ,fattrs)))))))) ;; apply custom-theme-set-faces to built-faces (apply #'custom-theme-set-faces 'two-fifteen built-faces)
Next up is making a palette with re-usable colours, which I’ve started w/the bg1
and fg1
.
Here are some functions and modes I’m finding extremely helpful when working on the theme:
describe-face
: Describe the face at the point (cursor)list-faces-display
: List faces in use on displayrainbow-mode
: Display colour values using the actual colourlist-colors-display
: visually browse colours names and codesalign-regexp
: Align attribute blocks of the theme to make it easier to readsort-lines
: Sort sections alphabeticallyIedit
andnarrow-to-region
: Refactoring- Keyboard macros, OF COURSE. : start (
C-x (
), stop (C-x )
), run (C-x e
)
There are moments where I do something and a big fat grin creeps onto my face, appreciating what I just did using Emacs. You know what I’m talking about! 🙂
Oh right, here’s a picture of what the theme currently looks like. It’s called two-fifteen.