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-loadfor 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