SetupEl

setup.el provides a macro to ease repetitive configuration patterns in Emacs. This is done by providing context sensitive local macros, that expand to regular, pure EmacsLisp.

The package is available on GnuELPA and is currently maintained by PhilipKaludercic on SourceHut: https://git.sr.ht/~pkal/setup.

Setup is self-documented: To get an overview of all known local macros, just read the DocString for the setup macro.

Basics

You can use the setup macro in a InitFile with LexicalBinding. Each setup body may start with a name, that sets the context for the current body. Without this, no default context is set. Within the setup body, local-macros (i.e. macros that are only defined in the setup body) may want to make use of the context, to generate code.

E.g.

(setup foo-mode
  (:hook bar-mode))

expands to

(add-hook 'foo-mode-hook #'bar-mode)

As the context says the current mode is “foo-mode”, and it’s hook is “foo-mode-hook”. The :hook macro uses this, to generate an add-hook call for “bar-mode”.

Some configuration must wait for a feature to be loaded. Setup takes care of this automatically, e.g. when binding commands to a mode-map, the :bind macro uses WithEvalAfterLoad:

(setup foo-mode
  (:bind "C-'" quux))

expands to

;; The macroexpansion has been slightly prettified for legibility 
(with-eval-after-load foo-mode
  (define-key foo-mode-map (kbd "C-'") #'quux))

The context can be changed, using macros like :with-feature, :with-mode, :with-hook, etc.:

(setup foo-mode
  (:with-mode baz-mode
    (:hook qux-mode))
  (:hook bar-mode))

expands to

(progn
  (add-hook 'baz-mode-hook #'qux-mode)
  (add-hook 'foo-mode-hook #'bar-mode))

In fact, whenever a context is set as the first argument to setup, this could be seen as the equivalent code to:

(setup 
  (:with-feature foo-mode
    (:hook bar-mode)))

Note how setup still works fine, even though the first element is no a symbol. Generally, the first element doesn’t have to be a symbol, if the local macro provides a “shorthand” to extract a context. For example the :package keyword (that ensures a package is installed), can be used as the first form, and it uses the package name as the context:

(setup (:package foo-mode)
  (:hook bar-mode))

is equivalent to

(setup foo-mode
  (:package foo-mode)
  (:hook bar-mode))

This demonstrates what the phrase “context sensitive local macro” means: The macros :hook, :bind, etc. can all only be used within a setup body, and they make use of whatever the surrounding code defines, to infer what hook a function should be added to or what map to bind keys into.

All local macros are defined using the function “setup-define”. If you want to inspect the definition of a macro, just use, setup provides Xref integration so that M-. should just work.

Examples

Configure FlyMake, by binding a few commands to the local mode, and hooking flymake-mode into prog-mode-hook:1

(setup flymake
  (:bind "M-n" #'flymake-goto-next-error
         "M-p" #'flymake-goto-prev-error)
  (:hook-into prog-mode))

Configure DirEd to load dired-x when started, prepend a value to dired-guess-shell-alist-user, customize dired-dwim-target (both using the customization interface) and then add auto-revert-mode to the mode hook.

(setup dired
  (:also-load dired-x)
  (:option (prepend dired-guess-shell-alist-user) '("" "xdg-open")
           dired-dwim-target t)
  (:hook auto-revert-mode))

This expression will be ignored if Avy is not installed. Otherwise it configures an option and binds a command to a global key, depending on whether or not Emacs is being used in the graphical mode.

(setup (:if-package avy)
  (:option avy-single-candidate-jump nil)
  (if (display-graphic-p)
      (:global "M-z" #'avy-goto-word-1)
    (:global "C-z" #'avy-goto-word-1)))

Alternatively, the check may also be pulled into the :global macro. In that case it is necessary to call kbd manually:

(setup (:package avy)
  (:option avy-single-candidate-jump nil)
  (:global (kbd (if (display-graphic-p) "M-z" "C-z"))
           #'avy-goto-word-1))

Also note that in this case :package is used instead of :if-package. This macro installs avy if it is not installed yet, before the body is evaluated further.

Tips and Ideas

Alternative macro definer

If you wish to define you own macros, use setup-define. In case the syntax is too cumbersome, you can use a macro like this:

(defmacro defsetup (name signature &rest body)
  "Shorthand for `setup-define'.
NAME is the name of the local macro.  SIGNATURE is used as the
argument list for FN.  If BODY starts with a string, use this as
the value for :documentation.  Any following keywords are passed
as OPTS to `setup-define'."
  (declare (debug defun))
  (let (opts)
	(when (stringp (car body))
	  (setq opts (nconc (list :documentation (pop body))
						opts)))
	(while (keywordp (car body))
	  (let* ((prop (pop body))
			 (val `',(pop body)))
		(setq opts (nconc (list prop val) opts))))
	`(setup-define ,name
	   (cl-function (lambda ,signature ,@body))
	   ,@opts)))

To declare local macros more like defun or defmacro. Here is how the definition for :package could be rewritten:

(defsetup :package (package)
  "Install PACKAGE if it hasn't been installed yet."
  :repeatable t
  :shorthand #'cadr
  `(unless (package-installed-p ',package)
	   (package-install ',package)))

setup-based Macro

The first element of a `setup` body can but does not have to be a name. That can be exploited to use `setup` in your own macros. I have this macro in my personal configuration, when I’m only interested in modifying user options:

(defmacro setc (&rest args)
  "Customize user options using ARGS like `setq'."
  (declare (debug setq))
  `(setup (:option ,@args)))

Additional Keywords

Here are a few examples of how setup-define can be used:

Unconditional quit

By default, :only-if, :if-host, etc. can be used to abort the evaluation of a setup body. In some cases one might want to unconditionally abort, e.g. when using a complex conditional construct. In that case, :quit generates the right code:

(setup-define :quit
  #'setup-quit
  :documentation "Unconditionally abort the evaluation of the current body.")

Bound unconditional quiting

In case one doesn’t want the entire setup body from being aborted because of :only-if, :if-host, :quit, etc. you can use this following to limit its scope:

(setup-define :with-local-quit
    (lambda (&rest body)
      `(catch ',(setup-get 'quit)
         ,@body))
  :documentation "Prevent any reason to abort from leaving beyond BODY."
  :debug '(setup))

Deferred Loading

If you want to load the current feature after a series of other features, this macro can help automatically define the code to do so:

(setup-define :load-after
    (lambda (&rest features)
      (let ((body `(require ',(setup-get 'feature))))
        (dolist (feature (nreverse features))
          (setq body `(with-eval-after-load ',feature ,body)))
        body))
  :documentation "Load the current feature after FEATURES.")

Note that multiple arguments can be passed to :after-load, leading to multiple encapsulated with-eval-after-load blocks.

Remove the current mode from the mode line

The ModeLine uses `minor-mode-alist` to map active minor modes to indicators. This macro makes manipulating the variable easier:

(setup-define :hide-mode
    (lambda (&optional mode)
      `(setq minor-mode-alist
	     (delq (assq ,(or mode (setup-get 'mode)) minor-mode-alist)
		   minor-mode-alist)))
  :documentation "Hide the mode-line lighter of the current mode.
Alternatively, MODE can be specified manually, and override the
current mode."
  :after-loaded t)

Add advice

The following macro simplifies the definition of Advice:

(setup-define :advise
    (lambda (symbol where arglist &rest body)
      (let ((name (gensym "setup-advice-")))
	`(progn
	   (defun ,name ,arglist ,@body)
	   (advice-add ',symbol ,where #',name))))
  :documentation "Add a piece of advice on a function.
See `advice-add' for more details."
  :after-loaded t
  :debug '(sexp sexp function-form)
  :indent 3)

Check for executables

If you wish to only configure a system when a executable is installed, the following macro will ensure that the evaluation is interrupted, when this is not the case:

(setup-define :needs
    (lambda (executable)
      `(unless (executable-find ,executable)
         ,(setup-quit)))
  :documentation "If EXECUTABLE is not in the path, stop here."
  :repeatable 1)

Hostname checking

You sometimes want to configure a system only on one host. The following macro can be used to easily check your hostname, and prevent evaluation of a setup body, if you are on the “wrong” host:

(setup-define :if-host
    (lambda (hostname)
      `(unless (string= (system-name) ,hostname)
         ,(setup-quit)))
  :documentation "If HOSTNAME is not the current hostname, stop evaluating form.")

Manual <code>load-path</code> manipulation

If you want to add a directory to your load-path the following macro can do so. Additionally, it will abort the evaluation if the directory doesn’t exist and can be used as the head element.

(setup-define :load-from
    (lambda (path)
      `(let ((path* (expand-file-name ,path)))
         (if (file-exists-p path*)
             (add-to-list 'load-path path*)
           ,(setup-quit))))
  :documentation "Add PATH to load path.
This macro can be used as NAME, and it will replace itself with
the nondirectory part of PATH.
If PATH does not exist, abort the evaluation."
  :shorthand (lambda (args)
               (intern
                (file-name-nondirectory
                 (directory-file-name (cadr args))))))

Comparison to UsePackage

The most popular configuration macro for Emacs is use-package by John Wiegley. The intention is similar, but usage is noticeably different. While use-package to specifies information about package and it’s usage, setup is less declarative and just provides a set of context-sensitive local macros, that are simply substituted.

The higher level of abstraction allows use-package to hide some of the complexity, by transparently reordering everything into the right order. The downside is that it is easy to get lost or forget what use-package is doing. Each setup macro on the other hand intends to encapsulate one common configuration action, and simplify the syntax. Take the FlyMake example from above. That is expanded into the following code:

(progn
  (eval-after-load 'flymake
    (function
     (lambda nil
       (progn
         (define-key flymake-mode-map
           [134217838]
           (function flymake-goto-next-error))
         (define-key flymake-mode-map
           [134217840]
           (function flymake-goto-prev-error))))))
  (add-hook 'prog-mode-hook
            (function flymake-mode)))

The macro :bind wraps define-key calls in a eval-after-load block, and automatically parses multiple arguments in a setq-like fashion. Likewise, :hook-into knows to deduce that flymake-mode should be added to prog-mode’s hook (prog-mode-hook).

Furthermore use-package might be said to be less flexible, because the macro assumes a certain kind of usage. Each expression should define one package. This is not assumed for setup: Each body can just as easily configure no to as many packages as one wants.

(setup (:package company company-math)
  (:bind "TAB" company-complete))

would install both company and company-math. The context is set to company, because the :package call is the first element of the list, and company is it’s first argument.

An attempt is also made by setup.el to ease extensibility. Each macro is defined using the setup-define function. Combined with further attributes, it allows for the simple definition of local and context sensitive macros, that are debug-able, can be composed and deferred.


I didn’t realize that you had created this library, which has the same name as my library setup.el (in the EmacsWiki ElispArea), which has existed since 1996 and been on the wiki since 2004.

I’m hoping you’re rename yours that uses the same name. (Admittedly, the name is not a great one nowadays, in any case; it’s been around a long time.)

Thx - DrewAdams

I found out about your setup.el recently, but sadly too late, as I and others have been using it for a while now. Should be maybe add Wikipedia style “Disambiguation” links to avoid confusion?

“for a while now”

You certainly haven’t been using it as long I and others have been using my setup.el. Which one is it “too late” to rename? Which library should keep the name, do you think?

Where do we end, if this kind of thing goes on – no respect/cooperation wrt precedent? What’s to stop others from doing the same (to you)? Is that the kind of environment/community you want to foster?

If you won’t rename, then I guess I’ll have to. “Sadly”, indeed. (Not the end of the world – but this shouldn’t be necessary on my part.)

[I have to say that I think there was a lot more cooperation and mutual respect in the “old days”. People didn’t hesitate to acknowledge/reference the slightest borrowing/inspiration etc. That seems rare now, as if every idea or implementation springs sui generis from thin air by the artist. (To be clear: I’m not saying you borrowed anything without attribution. Just making a more general point about the degree of cooperation I perceive nowadays.)]

DrewAdams

I guess what I don’t understand, is why anything has to be renamed. What is the issue with two files coexisting on the internet with the same name? Obviously I don’t want to burden (let alone disrespect) you or your users, I just don’t get the real issue of having a name conflict, if one package is on EmacsWiki and the other one is on ELPA.

I think the answer is obvious. Just imagine if there were multiple libraries of any other name - available from different sources, whether different GIT repositories or any other locations.

Even you acknowledged such name conflict as “sad” - and indeed it is. Fortunately, it can be fixed, given the will. Why did you say that, if you really thought there is no issue?

People have played very well with each other about this kind of thing in the past. No one tries to name their library the same as another – in fact, just the opposite.

 -- DrewAdams