How to modularize an emacs configuration?

My .emacs file loads ~/.emacs.d/init.el, which defines the following functions, written first for XEmacs, but working well enough for Emacs these days:

(defconst user-init-dir
  (cond ((boundp 'user-emacs-directory)
         user-emacs-directory)
        ((boundp 'user-init-directory)
         user-init-directory)
        (t "~/.emacs.d/")))


(defun load-user-file (file)
  (interactive "f")
  "Load a file in current user's configuration directory"
  (load-file (expand-file-name file user-init-dir)))

Then the rest of the file goes and loads lots of individual files with forms like this:

(load-user-file "personal.el")

My current set of files is as follows:

  • personal.el
  • platform.el
  • cygwin.el
  • variables.el
  • paths.el
  • mail-news.el
  • misc-funcs.el
  • bbdb.el
  • calendar.el
  • gnus-funcs.el
  • c-and-java.el
  • lisp.el
  • clojure.el
  • go.el
  • markdown.el
  • sgml-xml.el
  • tex.el
  • spelling.el
  • org.el
  • packages.el
  • fonts.el
  • color-theme.el
  • frame.el
  • server.el
  • keys.el
  • aquamacs.el

Some of them are much more specific in intent than others, as the names suggest. The more fine-grained the files, the easier it is to disable a cluster of forms when you're reinstalling a package or library. This is especially useful when "moving in" to a new system, where you drag your configuration files over but don't yet have all the supporting packages installed.


To split customizations into different files, you can use initsplit.el. Also, the wiki has a page on modular .emacs layout.


You can do (require 'foo) and that will load the first "foo.el" elisp finds in your load path, or (require 'foo "/home/user/experimental/foo.el") for something outside your load path. It will report an error if "foo.el" does not contain the expression (provide 'foo). In a perverse situation you could do (require 'foo "bar.el"), and this would work as long as "bar.el" had a (provide 'foo) clause.


What's wrong with using load-file?

Some additional bits you might find useful include

  • file-exists-p
  • file-expand-wildcards

Use C-h f <function> to get help, and you might find An Introduction to Programming in Emacs Lisp useful (or download the PDF)


If you want to split up your custom-set-variables, but want something lighter weight than initsplit.el, then try this:

(defmacro custom-set-variable (var value)
  "Call `custom-set-variables' with a warning shown
when customizing using the customize GUI.

XXX: does not support setting the optional NOW and
REQUEST (dependency) fields."
  (custom-set-variables
    ;; `load-file-name' is set by `load-file':
    ;; http://stackoverflow.com/a/1971376/470844
    `(,var ,value nil nil
      ,(format "!!! CAREFUL: CUSTOM-SET IN %s !!!" load-file-name))))

Now, put your configuration modules in separate files, and load them with load-file in your ~/.emacs, as others have suggested. In each configuration module, use

(custom-set-variable var value)

instead of

(custom-set-variables '(var value))

or

(setq-default var value)

when setting variables managed by customize. Then, if you ever need to customize the variable using the customize interface, you'll see a warning telling you which file the customization should be stored in:

"!!! CAREFUL: CUSTOM-SET IN <path to module file> !!!"

If you version control your config files, then a diff will tell which line of your ~/.emacs to move into the custom module.

Discussion of issues with naively using multiple custom-set-variables calls: http://www.dotemacs.de/custbuffer.html

Edit: Response to Alan's questions

The problem the custom-set-variable macro above solves

The customize interface in Emacs saves all custom set variables in your ~/.emacs whenever you edit any custom set variable. If you modularize your custom-set variables, by spreading them across multiple files, then you need to realize when emacs inserts duplicate settings in your .emacs, and remove them. The custom-set-variable macro above helps you do this.

A real example from my config files

I custom-set variables related to whitespace in a ~/.emacs.d/extensions/whitespace.el file that gets loaded by my ~/.emacs. For example, I set my search-whitespace-regexp variable like this:

(nc:custom-set-variable search-whitespace-regexp "[ \t\r\n]+")

Now, two things happen. First, when I come across that variable in the customization interface I get a warning:

Generated warning in Emacs' customization dialog

Second, if I edit any variables using the customization interface, my ~/.emacs makes it very obvious that I've shadowed some definitions. For example, after changing search-whitespace-regexp and global-whitespace-mode in the customization interface, I get:

$ svn di --diff-cmd "diff" -x "-U0" ~/v/conf
Index: /home/collins/v/conf/dot.emacs
===================================================================
--- /home/collins/v/conf/dot.emacs  (revision 267)
+++ /home/collins/v/conf/dot.emacs  (working copy)
@@ -55,0 +56 @@
+ '(agda2-include-dirs (\` ("." (\, (file-truename "~/local/opt/agda-std-lib/src")))) nil nil "!!! CAREFUL: CUSTOM-SET IN /home/collins/.emacs.d/extensions/agda.el !!!")
@@ -58,0 +60,3 @@
+ '(global-whitespace-mode t)
+ '(indent-tabs-mode nil nil nil "!!! CAREFUL: CUSTOM-SET IN /home/collins/.emacs.d/extensions/whitespace.el !!!")
+ '(indicate-empty-lines t nil nil "!!! CAREFUL: CUSTOM-SET IN /home/collins/.emacs.d/extensions/whitespace.el !!!")
@@ -72,0 +77 @@
+ '(search-whitespace-regexp "" nil nil "!!! CAREFUL: CUSTOM-SET IN /home/collins/.emacs.d/extensions/whitespace.el !!!")
@@ -74,0 +80 @@
+ '(tab-width 2 nil nil "!!! CAREFUL: CUSTOM-SET IN /home/collins/.emacs.d/extensions/whitespace.el !!!")

The macro doesn't help me remember that I changed search-whitespace-regexp, only that it's already set somewhere else, but it does help me know which variable settings I should remove from my ~/.emacs (all those with warnings), and which ones I should leave in my ~/.emacs, or move to other separate files (all those without warnings).

Conclusion

The macro is probably overkill, but once it exists it's easy to use, and makes it easier to avoid shooting yourself in the foot with shadowed custom set variables.