Ramblings in Art and Craft of Emacs init file configuration and Elisp.
This tool combines org-mode structuring with few macros to help configure Emacs and it’s third party packages. It is self-contained and requires no additional dependencies but Emacs itself. Tested with Emacs 27.1 and 28.0.50 on GNU/Linux and Microsoft Windows 10.
I consider this as an prototype in alpha stage, and have some further ideas for the tool itself and some more optimizations for the generated init file. I am not sure when I will have time to finnish them. The tool is usable though and it would be cool to get some thoughts and input on it. More details about the tool can be found in my blog post.
<2021-01-07 tor>
Quite few changes. The generator is now simplified. Prolog section is all gone. Instead there is a pseudo hook named early early-init. It makes it possible to work with content of early init file as if it was an ordinary Emacs hook, instead of thinking in terms of a file. We can only write elisp expressions that has to be executed in early init and generator will take care of rest. Automation comes at a small cost of little bit less customizability.
<2020-12-23 ons>
A slight fix for the case when generator.org is placed in user-emacs-directory (.emacs.d/). In my personal setup, I have generator.org in .emacs.d renamed to init.org. The goal is to be able to just run M-x generate-init-files and be done with it. Currently, M-x install-init-files still needs to be run after, in order to delete quickstart files; otherwise Emacs will load those before it loads the init file with baked-in autoloads which will perform double work and slightly slow down the init time. I will move deletion of quickstart files to proper place at some other time.
<2020-12-20 sön>
Early-init tangling is now properly done completely in temporary file, not messing with Early init section in the generator as I hacked it hastily some day ago. In mail conversation on emacs-devel, S. Monnier (writer of package quickstart feature) confirmed that it is safe to byte compile package-quickstart.el. It will be turned on by default in future versions of Emacs, so I assume that it is safe to keep baking this file into init file by default.
<2020-12-18 fre>
The tool can now bake `quickstart’ file (package-quickstart.el) into init file. In my own setup this makes for a drammatic difference in startup time. However byte-compiling package-quickstart.el file might not work in all setups, so this really has to be tested with your setup.
Baking of quickstart file is enabled by setting variable init-file-bake-autloads to t.
If this option is on, the tool will remove package-quickstart.el (and .elc if it exists) from the .emacs.d directory.
If this option is nil, the quickstart will be on, and tool will generate new package-quickstart.el and byte compile it. Both files will be installed in user’s Emacs directory (.emacs.d).
I have also implemented option to “unroll” key bindings in both with-hook and eval-after-load macros. The option is on by default, but can be disabled by setting init-file-unroll-key-bindings to nil.
There is a new macro to add a package with :pseudo option for the convenience.
BACKUP YOUR INIT FILE(S) BEFORE YOU USE THIS TOOL
Clone this repo, or download manually generator.org file. Open the downloaded file in Emacs and let it evaluate the startup section (the generator itself). Edit setup to suit your needs and taste. Once done, M-x generate-init-files and then M-x install-files to install generated files to your .emacs.d directory.
OBS: do not run C-c C-v t (org-babel-tangle). This tool generates the code by other means. You can read more in my blog post about deatils and reasons behind.
- self-contained
- optimize quickstart file (precompute and prepend package load paths)
- merge quickstart file with init file
- byte compile init files
- generate unrolled key binding definitions
- merge some generated lambdas where applicable
- out-of-session configuration and package administration
- very simplistic configuration options (only two macros)
- minimal overhead at runtime
- some of well-known hacks applied by default
- can’t be used with well-known tools like straight and use-package (it can, but there is no point)
- requires deeper knowledge of how Emacs works (mode hooks and eval-after-load)
- not well tested, buggy and probably erronoeus at the moment
- does lots of assumptions; not very general, nor flexible, nor robust
The tool is not ment to be used as a library or to be used on a file. It is a self-contained tool; i.e. it contains both a setup and code needed to generate init files. Starting a new setup means to simply copy generator.org, open that file and modify the relevant sections (mostly early init and packages section).
Each section is contained in it’s own header.
About - contains help, license and references that influenced the tool.
Generator - contains code for the tool itself, and is the only section that needs to be evaluated when the file is opened in Emacs.
Prolog - early init and part of init generated before main configuration starts
Packages - contains main configuration part, one subheading for each package
Epilog - some code generated after the main configuration ends.
Generally, only early init part and package sections are of interest when editing a configuration.
Early-init.el should be used mainly to setup some options for graphical components before graphical setup is initialized since it is wasteful to initialize them just to overwrite and redraw them later in the initialization process.
Subsection for configuring early-init.el is found in Prolog, named Early init. Any code put there is simply copy-pasted verbatim into early-init.el. It is not evaluated.
Init.el is where the main configuration is. In order to abstract away and save some typing, configuration is split in three parts: init, a subheading of Prolog, Packages and Epilog. Of those really Packages is what is interesting. Prolog and Epilog contain some boiler-plate code that implements some usual optimisations as found in H. Lissner’s excellent Doom Faq or elsewhere. Code in Prolog and Epilog is written to the file verbatim, as if copy-pasted, and hopefully does not need to be touched.
The most action happends in this section. It is here where both built-in and external packages are configured. The tool provides currently only two simple macros to configure the code:
with-package macro is used to generate code that will run in eval-after-load.
with-hook is used to add the code that will be run in some Emacs hook. Suffix -hook often found in Emacs such as in after-init-hook or dired-mode-hook and similar can be omitted.
All code in Packages section is evaluated, so those macros are actually self-inserting.
This is a deferred setup, so most generally we wish to run code in either eval-after-load or in some mode hook. Thus with-package is just a shorthand for with-eval-after-load macro, to make it little bit less verbose to type and less noisy to look at, albeit I might add some more optimizations to produced setup in this macro soon.
One reason for using self-inserting code is that it is easier to write the generator that way, at least what I think currently. Those macros are not written to the init file either, instead they are expanded and the final result is added to the file, in quest for less overhead.
with-key-map is a small macro for binding keys. I find it a bit verbose to type all that code to bind keys, so I have implemented a small macro, again in style of use-package, or rather bind-key, another Wiegley’s package.
Here I was playing with some optimisation, and unrolled the loop that results from the expansion of this macro, i.e. I generated all those define key statments in place of the loop. It can be tested by simply replacing following function:
(defun emit-sexp-to-init-file (sexp)
(append-to-init-file (prin1-to-string sexp)))
;; (if (equal (symbol-name (car sexp)) "with-key-map")
;; (emit-keymap (cdr sexp))
;; (append-to-init-file (prin1-to-string sexp))))
with:
(defun emit-sexp-to-init-file (sexp)
;; (append-to-init-file (prin1-to-string sexp)))
(if (equal (symbol-name (car sexp)) "with-key-map")
(emit-keymap (cdr sexp))
(append-to-init-file (prin1-to-string sexp))))
It will unroll loops defined in with-hook macro, but it seems to me that version with unrolled loops is actually slower than one with the loop. I am not sure but I think that my init file is too small so extra parsing probably adds more overhead than the loop itself.
BACKUP YOUR INIT FILE(S) BEFORE YOU USE THIS TOOL
genereate-init-files - generates early-init.el and init.el from the provided configuration.
install-init-files - install init.el and early-init.el into .emacs.d. Init.el will also be byte compiled. If native compiler is present it will be natively compiled too.
Care has to be taken when starting from scratch, to remove ~/.emacs as it is created by Emacs on a very first startup.
If you put generator.org in your .emacs.d directory, you don’t need to run install-init-files command, since the tool generates init files in same directory where the tool is.
While experimenting and writing a configuration, it is possilbe to make a misstake and end-up with a non-working init file. For this reason I always test the configuration by running another instance of Emacs, with M-& emacs or from the command line. If Emacs starts without problems I will then (maybe) restart my Emacs.
If you still end-up with an error in your init file, and don’t have a running Emacs process, then either run Emacs with –debug-init or -Q option and open the tool, edit the misstake and generate new init file(s).
Bind a shortcut to open your init file, or at least make a bookmark. It is really handy to just press a key and have your configuration open when you hack on your Emacs. If you check my personal configuration you can see I am using C-f i, to open the init file. My init file is placed in my .emacs.d directory and renamed to init.org
You can bind your init file to a key with following:
(global-set-key (kbd "C-f i" (lambda() (interactive) (find-file (expand-file-name "init.org" user-emacs-directory)))))
C-f is a prefix I have defined in my own Emacs, you can use some other combination or define C-f as a prefix.
It is sometimes useful to keep a configuration of a package despite not using it. Packages marked with :disable tag on it’s subheading are simply skipped. Observe also, since this is a generator; every change to the configuration require files to be re-generated. This is not a dynamic solution like use-package.
install-packages - downloads packages not tagged as :pseudo from the list.
Pseudo tag is needed for configuring built-in stuff like ‘Emacs’ or ‘Dired’ so we can configure them as if they were packages.
Currently I haven’t implemented things like updateing, pinning to an archive or uninstalling. For updates I am using auto-package-update, and I never uninstall packages anyway. It wouldn’t be hard to implement those things, but I don’t think I have time nor a need for the moment; maybe in some distant future.
One of the goals I had, was to be able to bootstrap all external packages once I download my configuration from the git repository. Again for the simplicity, I thought it would be nice if everything is self-contained. As org-mode is good at structuring, why not use the configuration itself as a list of packages to install? It adds some noise in turn by having some empty code blocks, but they are colapsed and thus not really in the way. Having every package listed also gives a nice overview of what packages are used. Since all code is in some macro, either in eval-after-load or in a hook, it means configuration for each package is well-structured and independent of each other so I can actually sort the list for even more order which makes it really easy to jump to things with helm-imenu for example. I had to write a small parser for the org file, but in Emacs it is almost a trivial thing to do. Take a look at get-package-list if you are interested.
Generator.org is an almost empty configuration containing just few packages, while my own setup is what I use personally and is more worked out example that shows how to use both early-init and packages sections (in case you are new to Emacs). Either just rename generator.org to something you will work with, and start by adding to it or use init.org from my own setup and remove what you don’t like and add your stuff in. My setup is brutally minimal when it comes to graphical components, so if you wish to turn them on, remove respective line in early init section.
If you find bugs, please either send a PR or a patch in email, or open an issue. I don’t promise I will fix it fast; we are currently waiting a baby so hacking is not my priority at the moment; but if I can, I’ll try to fix it as fast as I can.
Following articles have influenced me while creating this tool:
Faster Emacs Startup (Emacs developer list discussion)
Why is Doom Emacs so fast? (H. Lissner Github)
How is Doom Emacs so Fast (Reddit question)
Migrating Away From Use-Package
Copyright (C) 2020 Arthur Miller
Author: Arthur Miller <arthur.miller@live.com>
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.