Emacs Theme Utility
Table of Contents
Here is yet another theme utility for defining themes. I made the wrapper extremely thin and didn’t use any macros. And most of the lines of my theme definition can fit in 70 columns (which matters!).
(font-lock-builtin-face (nil ,violet2)) (font-lock-comment-face (nil ,fg-weak)) (font-lock-comment-delimiter-face (font-lock-comment-face)) (font-lock-doc-face (font-lock-comment-face)) (font-lock-constant-face (nil ,violet1)) (font-lock-function-name-face (nil ,violet2)) (font-lock-keyword-face (nil ,red)) (font-lock-string-face (nil ,green)) (font-lock-type-face (nil ,yellow)) (font-lock-variable-name-face (nil ,violet2)) (font-lock-warning-face (warning)) ...
1 Expand face attributes
I want to save writing face attribute keywords over and over again. So I decide to use this form:
(face (inherit foreground background underline weight slant) other-attributes display)
which determines common attributes by position. Other uncommon
attributes are in OTHER-ATTRIBUTES
as a plist.
DISPLAY
allows specifying some display attributes
like terminal type or color. For example,
(theme-util-make-face-spec '(default (nil "white" "black" t bold) (:family "Charter" :height 150)))
returns
(default ((t :foreground "white" :background "black" :underline t :weight bold :family "Charter" :height 150)))
which is a valid face spec that you can feed to
custom-theme-set-faces
.
Let’s try another one with some DISPLAY
constraints:
(theme-util-make-face-spec '(default (nil "white" "black" t bold) nil ((type ns) (min-color 256))))
returns
(default ((((type ns) (min-color 256)) :foreground "white" :background "black" :underline t :weight bold)))
The expand function is simple:
(defun theme-util-make-face-spec (spec) (let* ((face (nth 0 spec)) (attr (nth 1 spec)) (rest-attr (nth 2 spec)) (display (nth 3 spec)) (inherit (nth 0 attr)) (fg (nth 1 attr)) (bg (nth 2 attr)) (underline (nth 3 attr)) (weight (nth 4 attr)) (slant (nth 5 attr))) `(,face ((,(or display t) . ,(remove nil (append (if inherit (list :inherit inherit)) (if fg (list :foreground fg)) (if bg (list :background bg)) (if underline (list :underline underline)) (if weight (list :weight weight)) (if slant (list :slant slant)) rest-attr)))))))
I could even use pcase-let
to pattern match the
form, but nth
is good enough.
2 Define a theme
To define the theme, we just transform each face spec and pipe
them to custom-theme-set-faces
:
(defun theme-util-set-faces (name spec) (apply #'custom-theme-set-faces name (mapcar #'theme-util-make-face-spec spec)))
In action, it looks like this:
(theme-util-set-faces 'light (cl-flet ((darken #'theme-util-darken) (brighten #'theme-util-brighten) (overlay #'theme-util-color-overlay)) (let* ((bg "#fafafa") (bg-alt (darken bg 0.05)) (fg "#2b3239") (fg-weak "#9a9ea2") (blue1 "#a0bcf8") (blue2 "#4078f2") (green "#50a14f") (orange "#da8548") (red "#e45649") (yellow "#986801") (violet1 "#b751b6") (violet2 "#a626a4") ;; Note that this is not a cons cell. (tty '((type nil)))) `(;; builtin faces (default (nil ,fg ,bg)) (region (nil nil ,(overlay bg violet1 0.1))) (highlight (nil "white" ,blue2)) (cursor (nil "white" "black")) (link (nil ,blue2 nil nil)) (match (nil ,green nil nil bold)) ... (font-lock-builtin-face (nil ,violet2)) (font-lock-comment-face (nil ,fg-weak)) (font-lock-comment-delimiter-face (font-lock-comment-face)) (font-lock-doc-face (font-lock-comment-face)) (font-lock-constant-face (nil ,violet1)) (widget-inactive (default))))))
Note that I used local functions and variables rather than defining macros. It’s tempting to use macros when you can, but many times you don’t really need it, and using macros in fact makes stuff more complicated (look at doom-themes.el).