Thing is, most of these packages are more like software libraries than programmes, having little to no effect out of the box; they provide all the Lego bricks, but leave it to the user to put them together. And like Lego, putting the bricks together is the fun part! The way we do that is by writing bits of Emacs Lisp.
For example, here are a few lines of Elisp you could write to configure one of my favourite packages, dired-subtree.
(setq dired-subtree-use-backgrounds nil) (let ((map dired-mode-map)) (define-key map (kbd "TAB") #'dired-subtree-cycle) (define-key map (kbd "M-^") #'dired-subtree-remove)))
However, there is a problem. What happens when you write a bit of Elisp to configure a certain package, but that package is not (yet) installed? You often get errors about undefined commands, files not found, and so on.
One way around this is to put package-specific configuration behind a conditional.
(when (package-installed-p 'dired-subtree) (setq dired-subtree-use-backgrounds nil) (let ((map dired-mode-map)) (define-key map (kbd "TAB") #'dired-subtree-cycle) (define-key map (kbd "M-^") #'dired-subtree-remove)))
This works, but what if you want to then go ahead and install all packages for which you have a conditional configuration? There is no record of the names of those packages.
You could scan through your
user-init-file by eye and manually
install each package by hand using
pkg RET. But that only gets more laborious the more
packages you want to have.
A more viable solution is to use a macro that does a similar thing and collects the package names into a list variable at the same time. That way, you could programmatically install all such packages by iterating over the list.
As it happens, such a variable is already defined and documented by
package.el and it goes by the name
What’s more, there exists a command
M-x package-install-selected-packages that asks the user for approval
before installing them all. Making use of this variable, our job
becomes very easy.
Each time this macro is called with the name of some package
pkg, we need to do three things:
requirethe package feature;
Reproduced below is just 5 (logical) lines of Emacs Lisp sufficient to
define this macro. I’ve named it
with-package, analogous to the
(defmacro with-package (package &rest body) "Add PACKAGE to ‘package-selected-packages’, then attempt to ‘require’ PACKAGE and, if successful, evaluate BODY." (declare (indent 1)) `(and (add-to-list 'package-selected-packages ,package) (require ,package nil 'noerror) (progn ,@body)))
declare expression is related to code formatting and is not
strictly necessary for the macro to work. Its purpose is to inform
Emacs that the body of the macro call should be indented by an
dired-subtree example from above, this time in
(with-package 'dired-subtree (setq dired-subtree-use-backgrounds nil) (let ((map dired-mode-map)) (define-key map (kbd "TAB") #'dired-subtree-cycle) (define-key map (kbd "M-^") #'dired-subtree-remove)))
Note that, even though this is a macro, the name of the package should be quoted. Otherwise Emacs treats it as a variable and tries to find its value, which is not necessarily defined.
All this is to neglect the elephant in the room, a highly popular
package by the name of use-package which provides a macro of the
same name that does what my little
with-package macro does and lots
more. For a long time and until recently, my
scattered with calls to
use-package for the external packages I
I even had a little snippet near the top that would install the
use-package package if it wasn’t present already. This practice is
almost as popular as the package itself, it seems.
There are several reasons why I ditched
use-package in favour of
There is a way to install all packages for which a
form has been evaluated, but it does not ask interactively for consent
from the user before doing so. Rather, the packages are installed at
startup without prompt nor warning.
use-package is a mighty big package that does way more than I need
it to do. Simpler is usually better when you can get away with it.
Among its more advanced features is a way to selectively defer the loading of packages until they’re needed, shortening startup times. That sounds great, but personally I’m happy with my startup time as it is.
The last reason is that, honestly, I was rather pleased with myself after writing this macro. And if you ask me, DIY programming is what Emacs is all about.
So yeah, a 5 line Lisp macro may be all you need to take your
init.el to the next level. If this post has inspired you to rip
apart your carefully crafted Emacs configuration as I did mine, do
send an email to firstname.lastname@example.org letting me know.
I ❤ Emacs!
The wide-reaching applications of GNU Emacs have slowly invaded my workflow over the past year and a half, and I have much more to write about it. To find out when new Emacs related content emerges on cosine.blue, subscribe via RSS or follow me on Mastodon.