49
votes

I've decided to rewrite my .emacs from the ground up, and I want to setup something that's modular, in order to avoid a dreaded 1k+ LoC init.el file...

I think there are some basic concerns that each configuration needs to address :

  • global options
  • editing functions
  • navigation (frames & buffers)
  • keybindings
  • modes customizations

While I'm still tying to think the structure through, I'm looking for some pointers on how to achieve this.
I've looked at some .emacs on github and such, and there seems to be deifferent approaches, and no preferred way to go with this, which is a bit confusing.
I would be interested in reading some ideas regarding how to structure such a setup, and especially some related elisp code.


edit : Been caught up with things, and haven't had much time to play with this yet. Will try out the proposed methods in a few days, and see what's best, meanwhile thanks for all the recommendations !


edit2 : I've been using a literate init file with org-mode, and this is absolutely terrific !
I 'm not yet set on a specific load mechanism, I've been using this code, to recursively load my elisp directory, then require or whatever the setup instructions say.

  (if (fboundp 'normal-top-level-add-subdirs-to-load-path)
  (let* ((my-lisp-dir "~/.emacs.d/elisp/")
  (default-directory my-lisp-dir))
  (setq load-path (cons my-lisp-dir load-path))
  (normal-top-level-add-subdirs-to-load-path)))

I still need to polish this, maybe using autoload, and some byte-recompile if modified tricks ; would love to hear suggestions on that.

10

10 Answers

47
votes

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.

6
votes

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

4
votes

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.

2
votes

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)

2
votes

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.

1
votes

Have a look at the excellent emacs starter kit which is very nicely organised and also includes ELPA packages. I used the structure as inspiration for my own much simpler setup.

1
votes

Thanks to @JoeCasadonte for mentioning the EmacsWiki page. As I'm using Debian and Ubuntu, this particular snippet from it worked just well (to be put in .emacs.d/init.el):

(setq dotfiles-dir (file-name-directory (or load-file-name (buffer-file-name))))

(let ((user-site-start-dir (concat dotfiles-dir "/site-start.d")))
    (debian-run-directories user-site-start-dir))

Then, I just need to put my .el files inside ~/.emacs.d/site-start.d (e.g. ~/.emacs.d/site-start.d/50cedet.el) and they all get loaded automatically.

1
votes

Here is a library which allows you to install new modules simply by saving them in a designated directory. It can be downloaded and installed from http://github.com/tripleee/my-site-start/

    ;;; my-site-start.el --- set up personal .emacs.d/site-start.d/
    ;;
    ;; Copyright (C) era eriksson <http://www.iki.fi/~era/> 2008-2009
    ;; License: GPL v2
    ;; Version: see `my-site-start-version' below
    ;;
    ;;; Commentary:
    ;;
    ;; The purpose of my-site-start is to simplify maintenance of user libraries.
    ;; Instead of indefinitely tweaking your .emacs, just create a site-start.d
    ;; directory and add symlinks to the libraries you want to load into Emacs.

This architecture does impose some conventions which you might or might not like. See the rest of the commentary section near the top of the file for the full scoop.

I am the author of this code, and appreciate any feedback (though not directly via this public forum).

0
votes

What I have done is for each thing, X, that I might want to use, create a file called configure-X, and put a corresponding require in .emacs. This feature can then be removed by commenting out one line.

For example, to pick the simplest (if most poorly-named), I have configure-picture-and-artist-mode.el. This file is very simple:

(global-set-key (kbd "C-M-P")
                'picture-mode)

(provide 'configure-picture-and-artist-mode)

And in .emacs:

(require 'configure-picture-and-artist-mode)

After sorting this out I don't have anything in my .emacs except for a big list of requires and a few changes to load-path, a given package can be removed quite easily, and all the configuration for each package is in one place.

(I've also got a catch-all .el file that I use to store things that don't have any obvious home (my global keybindings, helper functions, etc.) but still ideally want to stay out of .emacs. This bit isn't very modular, and may introduce implicit dependencies on packages that my scheme is supposed to make unloadable easily, but in practice it doesn't seem to be a big deal.)

0
votes

You could try init-loader.el, is a loader of configuration files. init-loader.el loads configuration files from specified directory. It enables you to categorize your configurations and separate them into multiple files.

Fore more info, please visit init-loader GitHub Repo