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 indotspacemacs/additional/packages
should have one and only one owner. It callsuse-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
andlayer-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.