Migrating to Spacemacs Layers

Spacemacs is referred for its evil integration, space-based bindings, and community contributed layers that collect, configure, and integrate groups of packages.

For how much they add to Emacs, motivations for personal layers are largely undocumented.

I introduce layers then discuss benefits, approaches, and gotchas with layer-based configurations.

I've migrated my entire dotspacemacs/user-config into personal layers - now 6 lines vs 1,500.

See https://github.com/ekaschalk/.spacemacs.d for my viewer-friendly configuration .

Introducing Layers

This section is not a replacement for http://spacemacs.org/doc/LAYERS.html.

Layers are directories containing up to 5 files and possibly additional packages.

In load order:

Layers.el

Layer dependencies to load first.

(configuration-layer/declare-layers '(theming))

packages.el

Packages added or configured by the layer.

(setq my-layer-packages
      '(a-pkg
        (github-pkg :location (recipe :fetcher github
                                      :repo "github-user/repo-name"))
        (my-pkg :location local)))
  • Owned Packages: A layer owns a package if it defines layer-name/init-pkg-name. All packages not defined in dotspacemacs/additional/packages should have one and only one owner. It calls use-package. Common options are :init for before load config, :config for after, :if for loading if eg. a certain OS or executable is installed, :after for enforcing load order, and :defer t for deferred loading.

(defun display/init-pretty-outlines ()
  (use-package pretty-outlines
    :after outshine
    :config
    (progn
      (add-hook 'outline-mode-hook 'pretty-outline-set-display-table)
      (add-hook 'outline-minor-mode-hook 'pretty-outline-set-display-table)
      (add-hook 'emacs-lisp-mode-hook 'pretty-outline-add-bullets))))
  • Unowned Packages: A layer that does not own a package can configure it with layer-name/pre-init-pkg-name and layer-name/post-init-pkg-name.

(defun config/pre-init-neotree ()
  (evil-global-set-key 'normal (kbd "M-p")
                       'neotree-find-project-root))

(defun config/post-init-neotree ()
  (setq neo-theme 'icons))
  • Local Packages: Personal packages at local/my-pkg/my-pkg.el.

funcs.el

Layer functions.

Package agnostic functions belong here.

(defmacro with-face (STR &rest PROPS)
  "Return STR propertized with PROPS."
  `(propertize ,STR 'face (list ,@PROPS)))

Guarding against particular packages being installed:

(when (configuration-layer/package-usedp 'some-pkg)
  (defun my-func ()))

config.el

Layer variables.

;; python/config.el
(defvar python-tab-width 4
  "Tab width value for python buffers")

;; init.el in dotspacemacs-configuration-layers
(python :variables python-tab-width 2)

Configuration defined here will be loaded before the package init functions are executed. Layer dependencies are actually loaded prior to config.el.

This can be used for eg. setting theme updates with the theming layer.

(setq theming-modifications
      `((solarized-dark (avy-background-face :foreground "#586e75")
                        (font-lock-doc-face :foreground "#2aa198"))
        (solarized-light ...)))

keybindings.el

Package-agnostic key-bindings.

(global-set-key (kbd "M-d") 'spacemacs/delete-window)

;; Evil will be loaded
(evil-define-key '(normal visual motion) outline-minor-mode-map
  "gh" 'outline-up-heading)

Personal Layers

Structure

While any organization can be used, I recommend at most these 5 layers covering common needs.

A Macros/Base Layer

A base layer that all personal layers inherit packages, macros, and common functions from with (configuration-layer/declare-layers '(base)).

I load dash-functional and define with-dir, with-face, and other useful utilities here.

Config

All packages and their configuration and key-bindings that don't fit into any neat grouping.

When any package's init gets large, consider a local package. I maintain my org-mode setup separately in a local org-config package.

Anything, excluding spacemacs toggles, can be setup here. For instance:

(setq config-packages '(evil ...))

(defun config/post-init-evil ()
  (setq evil-escape-key-sequence "jk")
  (setq evil-escape-unordered-key-sequence "true")
  (advice-add 'evil-ex-search-next :after 'config/scroll-to-center-advice)
  (advice-add 'evil-ex-search-previous :after 'config/scroll-to-center-advice))

I recommend this layer own all additional packages except themes, see gotchas.

Display

Theme updates and display packages like spaceline-all-the-icons.

Due to how Spacemacs loads themes, I highly recommend declaring the theming layer a dependency for theme updates. It is much more efficient should you configure multiple themes, like light and dark versions, and as it is a layer, it will be loaded prior to config.el for proper code isolation.

I integrate and configure my local pretty packages here:

  • pretty-code : Program with custom ligatures and symbols, see <a href='/post/prettify-mode/'>mathematical notation in emacs</a>

  • pretty-eshell : Customize eshell information and faces, see <a href='/post/custom-eshell/'>making eshell your own</a>

  • pretty-fonts : All the icons and Fira Code ligature integration.

  • pretty-magit : Commit leaders, see <a href='/post/pretty-magit/'>pretty magit - integrating commit leaders</a>

  • pretty-outlines : Fancy outline bullets and ellipsis, see <a href='/post/outline-bullets/'>fancy outline bullets</a>

Langs (optional)

I find it useful to separate programming language configuration out from the config layer, though it is not necessary.

Personal (optional)

All personal packages that aren't display related I maintain in a single personal layer. This is only relevant if you write your own packages.

I setup my blogging and outline-jump packages here.

Your init.el

Layers must be declared in your dotspacemacs-configuration-layers to take effect.

I've organized my layers into several sections:

(defvar dotspacemacs/layers/local
  '((macros :location local)
    (config :location local)
    (display :location local)
    (langs :location local)
    (personal :location local))
  "Local layers housed in '~/.spacemacs.d/layers'.")

(defvar dotspacemacs/layers/core
  '(better-defaults
    git
    org
    ...)
  "Layers I consider core to Spacemacs")

(defvar dotspacemacs/layers/langs
  '(emacs-lisp
    ...)
  "Programming and markup language layers")

(defvar dotspacemacs/layers/extra
  '(gnus
    graphviz
    ...)
  "Miscellaneous layers")

(defun dotspacemacs/layers ()
  (setq-default dotspacemacs-configuration-layer-path '("~/.spacemacs.d/layers/")
                dotspacemacs-configuration-layers
                (append dotspacemacs/layers/core
                        dotspacemacs/layers/langs
                        dotspacemacs/layers/extra
                        dotspacemacs/layers/local)
                ...))

Gotchas

Migrating was mostly painless. However when things go wrong you lose access to your setup, an annoying development cycle. I encountered several Spacemacs idiosyncrasies to be aware of when using layers to replace my user-config.

Non-obvious errors to avoid:

Naming

The naming scheme of setq layer-name-packages and defun layer-name/init-pkg-name is strict. Beware when refactoring that you adjust the layer name accordingly. Failure to do so will result in the package's configuration not being loaded or in the case of ownership, not being installed, rather than a direct error.

Spacemacs toggles

Some toggles like spacemacs/toggle-highlight-long-lines-globally-on do not belong in any layer and should be defined in your user-config. Six toggles are now all that compose my dotspacemacs/user-config.

This goes for some toggles not explicitly owned by Spacemacs - trying to setup fringe-mode failed for me even in a config/post-init-fringe block.

OS Configuration

I define is-linuxp and a few other OS utilities that conditionally setup dotspacemacs/init variables like font size. Layers load after these variables are set, so the utilities cannot be moved to a layer. Set them at the top of your init.el.

Additional Themes

Spacemacs layers load ordering causes issues for extra themes. Theme packages cannot be put in a layer. As a result, to use solarized I set:

;; ~/.spacemacs.d/init.el
(defun dotspacemacs/layers ()
  (setq-default dotspacemacs-additional-packages '(solarized-theme)
                ...))
(defun dotspacemacs/init ()
  (setq-default dotspacemacs-themes '(solarized-dark solarized-light)
                ...))

Spacemacs Core Layers

Without doing a deep dive into Spacemacs core, you can expect the following layers to always be loaded before all personal layers. This is how dash is always available and evil-define-key can be used in keybindings files.

Call g d or (spacemacs/jump-to-definition) in emacs lisp mode to jump to that layer's packages.el to check out its packages and configuration.

(configuration-layer/declare-layers
 '(spacemacs-base
   spacemacs-completion
   spacemacs-layouts
   spacemacs-editing
   spacemacs-editing-visual
   spacemacs-evil
   spacemacs-language
   spacemacs-misc
   spacemacs-modeline
   spacemacs-navigation
   spacemacs-org
   spacemacs-purpose
   spacemacs-visual))

These layers follow the same rules and principles as every other layer. If you have the curiosity, these layers make Spacemacs what it is.

Functionality provided here can be made use of by any layer, assuming those packages and layers are not explicitly excluded.

Benefits

Those that value organization and robustness will find Spacemacs layers to improve on other configuration management methods.

Following Spacemacs conventions leads to predictable, friendly configurations.

Once you've become familiar with its conventions, there is no overhead.

comments powered by Disqus