Skip to content

Instantly share code, notes, and snippets.

@aheaume
Last active February 28, 2019 15:52
Show Gist options
  • Save aheaume/5047f46f95ed263ffbf948c5f2d9ee34 to your computer and use it in GitHub Desktop.
Save aheaume/5047f46f95ed263ffbf948c5f2d9ee34 to your computer and use it in GitHub Desktop.

use-layer

What is it?

use-layer is a nicer way to write your .spacemacs configuration file. Under the covers, it is a use-package rip-off, all the credit should go to John Wiegley and contributors to the use-package project. I merely copy-pasted the core macro and tweaked it a bit.

The main goal is to provide a way to group all related configuration for a layer in one single form, instead of spreading it out all over the .spacemacs file, much like what use-package does for packages.

Another goal is to avoid disrupting the way the layer system currently works, this is why use-layer does not hook itself into any Spacemacs code, and must explicitly be wired into your own configuration (see below for instructions).

Project status

It is a proof of concept, don’t use it in your own config just yet. I am currently gathering feedback to see if this a useful addition to the spacemacs repertoire, or merely just a personal preference.

What does it look like?

A simple example

Adding a layer to your Spacemacs configuration is currently

dotspacemacs-configuration-layers '(better-defaults)

With use-layer:

(use-layer better-defaults)

This isn’t much better than what Spacemacs already does, honestly, but read on for more.

Using :def

Spacemacs already has a way to configure layers with :variables.

dotspacemacs-configuration-layers
'(
  (auto-completion :variables
                   auto-completion-enable-sort-by-usage t)
  )

use-layer supports this through :def

(use-layer auto-completion
  :def
  (auto-completion :variables
                   auto-completion-enable-sort-by-usage t))

:def ensures use-layer is retro-compatible with Spacemacs: the :def form is exported as-is, and any future addition to the dotspacemacs-configuration-layers grammar is supported.

Using :user-config

Many packages do not have :variables, but instead require you to put some code in dotspacemacs/user-config.

dotspacemacs-configuration-layers '(mu4e)

(defun dotspacemacs/user-config ()
  ;; configure mu4e, cf. http://develop.spacemacs.org/layers/+email/mu4e/README.html
  (with-eval-after-load 'mu4e-alert
    ;; Enable Desktop notifications
    (mu4e-alert-set-default-style 'notifications)))

This a bit unfortunate, as now you have two places in your configuration that needs to stay in sync.

(use-layer mu4e
  :user-config
  (with-eval-after-load 'mu4e-alert
    ;; Enable Desktop notifications
    (mu4e-alert-set-default-style 'notifications)))

Now, use-layer does not help with the mu4e layer’s expectations about when functions should execute, but at least all the configuration is pulled together under one form.

Using :excluded-packages

If a layer pulls in a package you don’t want, in Spacemacs you need to add it to add it to dotspacemacs-excluded-packages.

dotspacemacs-configuration-layers
'(eshell)

dotspacemacs-excluded-packages
'(vi-tilde-fringe)

Unfortunately, this means that some configuration, related to the installation and configuration of the eshell layer, is done somewhere else. If you ever change your mind about using eshell or excluding vi-tilde-fringe, will you remember why?

(use-layer eshell
  :excluded-packages (vi-tilde-fringe))

This way, it is easy to remember months after writing this code that you decided to exclude vi-tilde-fringe when installing the eshell layer.

Real-world example

Let’s say I want to install the eshell layer, but not the eshell-z package, and configure the fasd layer to provide an alternative to z.

In Spacemacs, this is configured in 3 different parts of the config file

(defun dotspacemacs/layers ()
  (setq-default
   dotspacemacs-configuration-layers '(eshell fasd) ;; <--- here (1)
   dotspacemacs-excluded-packages ;; <--- here (2)
   '(
     ;; exclude `eshell-z' because fasd has an implementation of `z' I like better
     eshell-z
     )))

(defun dotspacemacs/user-config () ;; <-- and here (3)
  (defun eshell/z (&rest args)
    (when args
      (let ((cmd (s-join " " (-concat (list "fasd" "-dl1") args))))
        (cd (s-chomp (shell-command-to-string cmd)))))))

But all of those variables and functions may contain unrelated configuration for other layers, and it starts to become a mess. I guess you could create your own eshell+fasd layer and use that, but I don’t think it’s worth it maintenance-wise. use-layer does not aim to replace layers, it makes it easier to configure them in a maintainable way while still benefiting from the community-configured aspects of Spacemacs. Or at least, that’s the objective :)

Here is the same configuration using use-layer:

(use-layer eshell)

(use-layer fasd
  :excluded-packages (eshell-z)
  :user-config
  (defun eshell/z (&rest args)
    (when args
      (let ((cmd (s-join " " (-concat (list "fasd" "-dl1") args))))
        (cd (s-chomp (shell-command-to-string cmd))))))

Which I believe is a better representation of the intent: when installing fasd, override eshell-z with a different, user-defined eshell/z function.

How do I give it a test run?

I made the conscious choice not to mess around with dotspacemacs-* variables, so wiring use-layer in your configuration is a bit more involved than your typical package or layer install.

One issue is that this cannot be packaged as a MELPA recipe, because we need to load use-layer before Spacemacs reads it’s own config, a classic chicken-and-egg situation.

Review the code and check that it does not do anything sketchy! this is the internet

Paste the code somewhere (I’ll assume in ~/.spacemacs.d/use-layer.el)

Require the feature into spacemacs early in your config

;; This file is loaded by Spacemacs at startup.
;; It must be stored in your home directory.

(add-to-list 'load-path dotspacemacs-directory)
(require 'use-layer)

Configure dotspacemacs-configuration-layers to read use-layer-configuration-layers

dotspacemacs-configuration-layers
(append use-layer-configuration-layers
        '(
          ;; all your layers currently configured
          helm
          better-defaults
          (auto-completion :variables
                           auto-completion-enable-sort-by-usage t)
          eshell
          fasd
          ))

Configure dotspacemacs-excluded-packages to read use-layer-excluded-packages

dotspacemacs-excluded-packages
(append use-layer-excluded-packages
        '(
          ;; all your packages currently excluded
          eshell-z
          ))

Configure dotspacemacs/user-* to run the proper hooks

(defun dotspacemacs/user-init ()
  ;; your current user-init code goes here
  (run-hooks 'use-layer-init-hook)
  ;; or here
  )

(defun dotspacemacs/user-config ()
  ;; your current user-config code goes here
  (run-hooks 'use-layer-config-hook)
  ;; or here
  )

You are now ready to migrate your layers one by one, without breaking your config.

(add-to-list 'load-path dotspacemacs-directory)
(require 'use-layer)

(use-layer better-defaults)

(use-layer auto-completion
  :def
  (auto-completion :variables
                   auto-completion-enable-sort-by-usage t))
;; ...

(defun dotspacemacs/layers ()
  ;; ...
  dotspacemacs-configuration-layers
  (append use-layer-configuration-layers
          '(
            helm
            eshell
            fasd
            ))
  )

;; ..


(defun dotspacemacs/user-init ()
  ;; ...
  )

(defun dotspacemacs/user-config ()
  ;; ...
  )

What’s next?

Are you interested? I want your feedback! :)

Not all of the use-package niceties are supported

There are not statistics, as it is assumed that Spacemacs should handle this. use-layer requires explicit use of progn in :user-config and :user-init. Etc.

Do we need more keywords?

I think the current implementation covers a fair bit of my use-cases, but what do you need? Currently, there is

:disabled -> disable the whole layer
:def -> dotspacemacs-configuration-layers forms
:user-init -> run code from dotspacemacs/user-init
:user-config -> run code from dotspacemacs/user-config
:excluded-packages -> dotspacemacs-excluded-packages
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment