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:
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.