Using ELPA already will simplify your config file. You no longer have to change the
load-path
for each package. All ELPA packages will be
on the load-path
automatically.
Furthermore, some packages are specifically designed to give you a sane startup environment, notably the
starter-kit
package. Just
downloading the package from ELPA will get you its extra
initialization.
However, it'd be best to not have to do any manual downloading for core packages that make up the customization we want. It'd be nice if we can set up our
~/.emacs.d/
directory on a new machine from git, and
just start emacs and have everything set up for us.
Right now, in my initialization file, I have the following code that will set up packages on a new machine:
(require 'package) (setq package-archives '(("ELPA" . "http://tromey.com/elpa/") ("gnu" . "http://elpa.gnu.org/packages/") ("melpa" . "http://melpa.milkbox.net/packages/") ("marmalade" . "http://marmalade-repo.org/packages/") ("technomancy" . "http://repo.technomancy.us/emacs/"))) ;; Package setup, taken from ;; https://github.com/zane/dotemacs/blob/master/zane-packages.el#L62 (setq ash-packages '(ace-jump-mode anaphora autopair bang color-theme-solarized cppcheck dart-mode dynamic-fonts expand-region fill-column-indicator flex-autopair flymake-cursor flyspell-lazy font-utils idle-highlight-mode ido-ubiquitous jabber js2-mode key-chord list-utils magit oauth2 paredit rainbow-delimiters rainbow-mode smex starter-kit undo-tree yasnippet )) (package-initialize) ;;; install missing packages (let ((not-installed (remove-if 'package-installed-p ash-packages))) (if not-installed (if (y-or-n-p (format "there are %d packages to be installed. install them? " (length not-installed))) (progn (package-refresh-contents) (dolist (package not-installed) (package-install package))))))First, this code loads the package code with a
require
statement,
then sets the list of ELPA archives I use. Then, it sets up a list of
packages I'd like to install in the variable ash-packages
. After a
necessary initialization, it looks at each of the packages, and for
each one that isn't already installed, it installs it from ELPA. As
the comment mentions, I took this whole thing from
zane's dotemacs repository. As I mentioned in an earlier post, stealing
other people's customizations is a time-honored emacs tradition.
Further down in the file, you will see that for some of these packages, some basic configuration happens. I try to group all configuration for a package together. It would be nice if we could specify the package for download and the configuration together, since then we could remove an unwanted package and all configuration at once. I don't think this is worth it, though, at least right now. Maybe I'll pursue this in a further blog post.
Finally, it's worth noting that I do all my customization in
eval-after-load
functions. The purpose of those functions is that
the contents of them are loaded only when the package is required or
autoloaded, which avoids issues with missing functions or variables.
If the package is already loaded, then everything is evaluated
immediately. It's also important note that these are written with the
function to execute quoted (since we don't want to execute it right
away).
For example, here's something that I use to setup the
key-chord
package:
(eval-after-load 'key-chord '(progn (key-chord-mode 1) (key-chord-define-global "jk" 'dabbrev-expand) (key-chord-define-global "l;" 'magit-status) (key-chord-define-global "`1" 'yas/expand) (key-chord-define-global "-=" (lambda () (interactive) (switch-to-buffer "*compilation*"))) (key-chord-define-global "xb" 'recentf-ido-find-file) (key-chord-define-global "xg" 'smex) (key-chord-define-global "XG" 'smex-major-mode-commands) (key-chord-define-global "fj" 'ash-clear)))
When a file that provides
key-chord
is loaded, this will execute. If
key-chord
is already loaded when this eval-after-load
is
evaluated, it will execute immediately.
Sometimes you want to have only load things when two packages are simultaneously loaded. Then you can nest one
eval-after-load
in
another. I do that in that following elisp:
(eval-after-load 'multiple-cursors '(progn (global-set-key (kbd "C-c m m") 'mc/edit-lines) (global-set-key (kbd "C-c m a") 'mc/edit-beginnings-of-lines) (global-set-key (kbd "C-c m e") 'mc/edit-ends-of-lines) (global-set-key (kbd "C-c m r") 'mc/set-rectangular-region-anchor) (global-set-key (kbd "C-c m =") 'mc/mark-all-like-this) (global-set-key (kbd "C-c m n") 'mc/mark-next-like-this) (global-set-key (kbd "C-c m p") 'mc/mark-previous-like-this) (global-set-key (kbd "C-c m x") 'mc/mark-more-like-this-extended) (global-set-key (kbd "C-c m u") 'mc/mark-all-in-region) (eval-after-load 'key-chord '(progn (key-chord-define-global "zm" 'mc/edit-lines) (key-chord-define-global "za" 'mc/edit-lines) (key-chord-define-global "ze" 'mc/edit-lines) (key-chord-define-global "zr" 'set-rectangular-region-anchor) (key-chord-define-global "z=" 'mc/mark-all-like-this) (key-chord-define-global "i\\" 'mc/mark-all-like-this) (key-chord-define-global "zn" 'mc/mark-next-like-this) (key-chord-define-global "zp" 'mc/mark-previous-like-this) (key-chord-define-global "zx" 'mc/mark-more-like-this-extended) (key-chord-define-global "zu" 'mc/mark-all-in-region)))))
This is important! If we were to to put all the
key-chord-define-global
in the top-level eval-after-load
, then if
key-chord
were not downloaded from ELPA, or not loaded for some
reason, this would break, throwing an error that
key-chord-define-global
is undefined. Doing things this way is much
safer.
However, you can't do this all the time. If we were to take this statement:
(eval-after-load 'key-chord '(progn (key-chord-mode 1) (key-chord-define-global "l;" 'magit-status)))and turn it into this:
(eval-after-load 'key-chord '(progn (key-chord-mode 1) (eval-after-load 'magit '(key-chord-define-global "l;" 'magit-status))))
That would pose an issue. It would protect us against the case where the
magit
package isn't installed, and therefore we shouldn't have a
keychord defined. However, if it was installed, we wouldn't have a
keybinding, and unless we run (require 'magit)
, this keybinding
wouldn't appear. We don't really want to run that, though, because an
autoload is more efficient, and magit-status
is an autoload. That
means that the magit
package may be unloaded, but we still have
the entry functions from the package. When the user invokes any of
those commands, then magit
is loaded at that point.Autoloads make our initialization files load much quicker. I think it's better to use them then to
require
everything, so I don't have
nesting eval-after-load
statements for anything autoloaded.
Here's the lessons we can take from this:
- Set up your ELPA such that you have a good set of
package-archives
. - Set things up so that you have a list of must-have ELPA packages, which are downloaded when they don't exist.
- Customize through
eval-after-load
, and keep all your customization for a package in one place. - When you are customizing things that only exist when two packages
are loaded at the same time, use nested
eval-after-loads
. - The exception to the above rule is autoloads. There is no need
to nest an
eval-after-load
for an autoloaded function. You can usually just assume that any major function in a package is autoloaded, but if you want to check, jump to the source and look for a comment above that says;;;###autoload
.
4 comments:
is there a way to customize where the elpa generated files (for example, foobar-autoloads.el) go? I tried putting my Emacs setup in github, and the generated autoloads files keep causing git conflicts (I generate them on machine 1, generate them on machine 2, and some timestamps are making git complain
I wouldn't check in your "elpa" directory in your github repository. There's no point with a setup like I explain here: each new computer will retrieve all the ELPA repositories you want installed.
You say "stealing other people's customizations is a time-honored emacs tradition".
I'm carrying it on and stealing yours. Thanks for posting.
I'm not a big fan of keeping a single list of desired packages: I personally prefer to have a require-package function which I can place next to the code which configures that package, as seen here.
Post a Comment