/space-theming

:sparkles: A port of Spacemacs’ theming layer to vanilla Emacs

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

space-theming

Port of Spacemacs theming layer (code) to vanilla Emacs.

This package allows to easily override theme faces by configuration.

For more context, read the accompanying blog post.

Part of this documentation comes from the original layer documentation.

Installation

Not yet on Melpa.

For now, the recommended way to install is with use-package, quelpa and quelpa-use-package.

(use-package space-theming
  :quelpa (space-theming :fetcher github :repo "p3r7/space-theming"))

Usage

General

After the package is loaded, you need to put the following in your configuration (init.el):

(space-theming-init-theming)

If a theme gets loaded before space-theming-init-theming gets called, you'd need to tell space-theming about it like so:

(setq space-theming--current-theme <theme-loaded-at-startup>)
(space-theming-update-current-theme)

Starting on Emacs 27, you might have to set the following (to revert a change of behavior for underlying function custom--inhibit-theme-enable):

(setq custom--inhibit-theme-enable nil)

Basic

Individual attributes of theme faces can be overridden with var space-theming-modifications. It should be a list of the following form:

((theme1 (face1 attributes...)
         (face2 attributes...)
         ...)
 (theme2 (face1 attributes...)
         (face2 attributes...)
         ...)
 ...)

This will apply the given attributes to the relevant faces whenever the appropriate theme is loaded. To update without changing the theme, use command space-theming-update-current-theme.

Headers

In addition, this package provides vars to ease overriding header faces.

Those vars take a list of themes (or the special value 'all).

var description
space-theming-headings-inherit-from-default Themes for which headers should inherit the default face
space-theming-headings-same-size Themes for which headers should all have the same size as regular text (:height attribute)
theming-headings-bold Themes for which headers should be bold (:weight set to bold)

The list of faces to consider as headers is defined with var space-theming--header-faces.

Example configuration

Assuming we're using use-package:

(use-package space-theming
  ;; ...
  :demand

  :init
  (setq space-theming-modifications
        '((white-sand
           (cursor :background "#585858")
           (region :background "#a4a4a4" :foreground "white")
           (form-feed-line :strike-through "#a9a9a9"))
          (tango
           (hl-line :inherit nil :background "#dbdbd7")
           (form-feed-line :strike-through "#b7b8b5")
           (font-lock-comment-face :foreground "#b7b8b5"))))
  (setq space-theming-headings-inherit-from-default 'all)
  (setq space-theming-headings-same-size 'all)

  ;; make it work under emacs 27
  (setq custom--inhibit-theme-enable nil)

  :config
  (space-theming-init-theming)

  ;; NB: assuming theme white-sand got applied before space-theming got loaded
  (setq space-theming--current-theme 'white-sand)
  (space-theming-update-current-theme))

About faces and attributes

An Emacs theme is analogous to a CSS stylesheet.

It is composed of a list of faces (similar to CSS classes) to which properties called attributes are associated.

Some of the more common attributes you might want to tweak are the following:

attribute description
:inherit name of a face to inherit attributes from
:foreground / :background hexadecimal color strings
:height a floating point number (1.0 to inherit from parent)
:weight typically either normal or bold
:underline typically nil or t
:slant typically normal, oblique or italic
:box set to t to draw a box around characters in the foreground

For more up-to date information, you can take a look at the face attributes section in the Emacs manual.

To get the list of available faces, run command list-faces-display.

To get the list of attributes values for a specific face, run command describe-face <face_name>.

The most primitive faces (from which themes generally make other faces inherit) are:

  • those from group basic-faces from faces.el, which are super generic: default, highlight, link...
  • those from group font-lock-faces from font-lock.el, which correspond to syntax highlighting in source code: font-lock-keyword-face, font-lock-comment-face, font-lock-string-face...

Edge-case: applying to new frames

The package fails to apply our overrides to new frames associated to the same Emacs instance.

The way I dealt with this issue is by creating file ~/.emacs.d/client-init.el with the following content:

(mapc
 (lambda (frame)
   (select-frame frame 't)
   (space-theming-update-current-theme))
 (frame-list))

And then creating new frames with:

$ emacsclient -c -a "" -e "(load-file \"~/.emacs.d/client-init.el\")"

I also tried doing the following in init.el but this did not work:

(add-hook 'after-make-frame-functions
            (lambda (_current-frame)
              (space-theming-update-current-theme)))

Implementation details

Naming differences with original layer

Public symbols:

type of symbol name in original layer name in space-theming
function theming/init-theming space-theming-init-theming
function (command) spacemacs/update-theme space-theming-update-current-theme
var theming-modifications space-theming-modifications
var theming-headings-inherit-from-default space-theming-headings-inherit-from-default
var theming-headings-same-size space-theming-headings-same-size
var theming-headings-bold space-theming-headings-bold

Private symbols:

type of symbol name in original layer name in space-theming
function spacemacs//theming space-theming--theming
function spacemacs//in-or-all space-theming--in-or-all
var spacemacs--theming-header-faces space-theming--header-faces

Please note that var spacemacs--theming-modified-faces has not been ported. It's use-case seems to be to undo modifications to current theme when the user manually calls spacemacs/update-theme after changing overrides for current theme (theming-modifications) without having changed theme in the meantime.

It added quite an overhead to a use-case that could be dealt with simply re-applying current theme.

Current theme tracking

I made the package track the current theme being used, into var space-theming--current-theme. This is done by adding function space-theming--track-theme as an :after advice to load-theme.

Originally, the Spacemacs layer was tracking it with var spacemacs--cur-theme that gets set in the core theme switcher.

Legibility

This code uses form feeds (^L character) as separators.

Package form-feed makes them appear as intended.