Emacs Main Initialization File
This is the main file of my personnal Gnu/Emacs config environment. It is mainly use to reference other files, where all the magic happens.
The complete source code of my configuration files, including the
Makefile
, responsible of the org-to-el compilation, are available on
its own git repository.
Global Options
Early init
We begin by setting all necessary preferences for native-comp to behaves as we prefer.
(when (boundp 'native-comp-eln-load-path) (defconst ed/eln-cache-dir (expand-file-name "temp/eln-cache/" user-emacs-directory) "Directory where to store native compiled files.") (unless (file-exists-p ed/eln-cache-dir) (make-directory ed/eln-cache-dir t)) ;; Set eln-cache dir ;; Does not work for now, but should around Emacs 29 (when (fboundp 'startup-redirect-eln-cache) (startup-redirect-eln-cache ed/eln-cache-dir)) (setq native-comp-async-query-on-exit t native-comp-async-report-warnings-errors 'silent))
To speed-up frame creation, we also put the following lines in the
early-init.el
file.
(menu-bar-mode -1) (when (fboundp 'tool-bar-mode) (tool-bar-mode -1)) (when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1)) ;; Avoid blank screen, the following match dracula theme. (when (display-graphic-p) (set-face-background 'default "#282a36" nil) (set-face-foreground 'default "#f8f8f2" nil)) ;; Ignore .Xresources (advice-add 'x-apply-session-resources :override #'ignore) ;; Make Emacs works with Ibus or other IM settings (require 'iso-transl) (setq frame-title-format "%b - Gnu/Emacs" icon-title-format "%b - Gnu/Emacs" inhibit-startup-screen t ;; Accelerate interactions by answering only y instead of yes. use-short-answers t ;; Temporary mute auto-save during init auto-save-default nil)
At various place in my init.el
file I'll use another trick to quicken
Emacs startup: I'll wrap big operation inside
(let ((gc-cons-threshold most-positive-fixnum)) …)
constructs to
temporarily desactivate the garbage collector. After the end of the
block, the default value (800ko) is reset.
Regular init
Then, we set the other options in the classical init.el
file. These
options are the bare minimal declarations to have a feel-good emacs.
(blink-cursor-mode 1) (delete-selection-mode 1) (global-font-lock-mode 1) (setq visible-bell t backup-by-copying t delete-old-versions t suggest-key-bindings 5 select-enable-clipboard t window-combination-resize t ; better balance new window major-mode 'fundamental-mode ; To avoid all text-mode related autoloads ad-redefinition-action 'accept ; Remove useless warnings user-full-name "Étienne Deparis" current-language-environment "UTF-8") (setq-default tab-width 4 fill-column 78 indent-tabs-mode nil)
Temporary directory
Emacs packages and even emacs internal are used to store a lot of information at various places. I try to keep them together in a specific directory inside the emacs user directory.
We first need to be sure that temporary directory exists
(defconst ed/temp-files-dir (expand-file-name "temp/" user-emacs-directory) "Directory where to store temporary files, often generated at runtime.") (defconst ed/backup-dir (expand-file-name "backups/" ed/temp-files-dir) "Directory where to store backup files.") (defconst ed/auto-save-dir (expand-file-name "auto-save/" ed/temp-files-dir) "Directory where to store auto-saved file references.") (defconst ed/x-win-sessions-dir (expand-file-name "x-win-sessions/" ed/temp-files-dir) "Directory where to store x-window related sessions files.") (dolist (tmpdir `(,ed/backup-dir ,ed/auto-save-dir ,ed/x-win-sessions-dir)) (unless (file-exists-p tmpdir) (make-directory tmpdir t)))
Some modes-specific cache files are declared with the rest of the mode configuration. The following variables are directly used by Emacs itself.
(setq abbrev-file-name (expand-file-name "abbrev.el" ed/temp-files-dir) auto-save-file-name-transforms `((".*" ,ed/auto-save-dir t)) auto-save-list-file-prefix ed/auto-save-dir backup-directory-alist `(("." . ,ed/backup-dir) (,tramp-file-name-regexp nil)) bookmark-default-file (expand-file-name "bookmarks.el" ed/temp-files-dir) custom-file (expand-file-name "custom.el" ed/temp-files-dir) desktop-base-file-name "desktop-session.el" desktop-dirname ed/temp-files-dir desktop-path `(,ed/temp-files-dir) eshell-directory-name (expand-file-name "eshell/" ed/temp-files-dir) nsm-settings-file (expand-file-name "network-security.el" ed/temp-files-dir) multisession-directory (expand-file-name "multisession/" ed/temp-files-dir) save-place-file (expand-file-name "places.el" ed/temp-files-dir) tramp-persistency-file-name (expand-file-name "tramp.el" ed/temp-files-dir))
The following function is an overwriting of the one defined in term/x-win.el
and related to the function emacs-session-save
. I normally don’t use it as I
very rarely load term
, however I keep it, just in case it is started, to
avoid pushing weird file anywhere. TODO: Try to understand what I can do with
those files.
(defun emacs-session-filename (session-id) (concat ed/x-win-sessions-dir session-id))
Package Management
package.el config
I only use melpa as an alternative package source to the default GNU elpa repository. However, I pin some built-in packages to gnu repo only to avoid wild upgrade from melpa.
(use-package package :init (setq package-gnupghome-dir (expand-file-name "package-gnupg/" ed/temp-files-dir) package-install-upgrade-built-in t package-pinned-packages '((bind-key . "gnu") ;; use-package (faceup . "manual") ;; built-in (git-commit . "nongnu") ;; magit (haml-mode . "nongnu") ;; sass-mode (magit-section . "nongnu") ;; magit (popup . "nongnu") ;; dumb-jump (use-package . "gnu") ;; built-in (use-package-ensure-system-package . "manual") ;; built-in (transient . "gnu") ;; magit/built-in (with-editor . "nongnu"))) ;; magit :config (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t))
I need to install these system package on archlinux to have the whole thing behaves as intended:
- grammalecte (standalone version of the grammar checker)
- hunspell-fr (spellcheck)
- mu
- w3m (to have html mail preview in mu4e)
use-package config
With Emacs 29.1, use-package
seems to become the standard way to load
packages. As it also allow us to install missing package, I switch to it for
all my dependencies.
The following lines only list packages, which do not require specific
configuration, or in the contrary for which configuration is highly specific
and is made the old way (with-eval-after-load
). When a package only require
easy configuration, the use-package
call is made directly in the related
config file (to avoid as much as possible to separate loading and
configuration).
(use-package org :ensure t :pin gnu :defer t) (use-package coffee-mode :ensure t :pin nongnu :defer t) (use-package dockerfile-mode :ensure t :pin nongnu :defer t) (use-package htmlize :ensure t :pin nongnu :defer t) (use-package lua-mode :ensure t :pin nongnu :defer t) (use-package markdown-mode :ensure t :pin nongnu :defer t) (use-package rust-mode :ensure t :pin nongnu :defer t) (use-package yaml-mode :ensure t :pin nongnu :defer t) (use-package crystal-mode :ensure t :defer t) (use-package groovy-mode :ensure t :defer t) (use-package json-navigator :ensure t :defer t) (use-package package-lint :ensure t :defer t) (use-package terraform-mode :ensure t :defer t)
Detailed Configuration
I split my detailed configuration into the following files, which contain all the required settings to customize various aspects of my emacs usage (color theme, authorship environment, integrated development environment…):
They do not contains any sensible information, in order to publish them
widely on the internet. All my private stuff are kept in the file
secrets/milouse.el
.
I also wrote my own little dashboard to greet me when I open Emacs. It
displays my 10 most recently visited files, my 10 most recent bookmarks and
the 10 most recent projects I have been involved with. It also exposes some
nerdy statistics about Emacs startup. But the most useful feature, for me, is
the listing of my clipboard content, to let me interract with it if needed
with my function ed/find-file-selected-or-killed
(defined in the utils
file).
Startup optimization
We do our best to defer the maximum things to quicken the Emacs startup, while
loading the configuration files. We use two custom hooks to avoid executing
some action while loading the settings. The first one will contain things to
be always run and the second will hold time consuming stuff only needed in a
graphical environment (i.e. not to be used by emacs -nw
).
(defvar ed/call-me-maybe-hook '()) (defvar ed/call-me-maybe-when-graphic-hook '())
To begin with, we only load the minimal necessary things we always need in an
after-init-hook
: the previously declared custom-file
, which contains all
customizations made from Emacs, the theme and mode-line customizations, and my
"utils" and "various-comp-lang", which contains settings requiring to be
loaded before the command line arguments are parsed.
(defun ed/expand-load (file &optional noerror showmessages) "Load FILE after having expanded it from `user-emacs-directory'. If optional second arg NOERROR is non-nil, report no error if FILE doesn’t exist. Do not print messages at start and end of loading unless optional third arg SHOWMESSAGES is non-nil." (load (expand-file-name file user-emacs-directory) noerror (not showmessages))) (add-hook 'after-init-hook (lambda () (let ((gc-cons-threshold most-positive-fixnum)) ;; Load emacs custom file (when (file-exists-p custom-file) (load custom-file nil t)) ;; Load theme and mode-link customizations (ed/expand-load "beauty") (ed/expand-load "utils") (ed/expand-load "various-comp-lang") ;; Only load mu4e if my secret file exists, as it is a hard ;; requirements for mu. But this secret file require utils ;; to have been loaded before. This is why I only declare ;; it here. (use-package mu4e :defer t :load-path "/usr/share/emacs/site-lisp/mu4e/" :when (ed/expand-load "secrets/milouse" t) :bind ("<f5>" . mu4e) :config ;; Only if mu4e.elc has not been already loaded (unless (featurep 'ed/mu4e) (let ((gc-cons-threshold most-positive-fixnum)) (ed/expand-load "mu4e" nil t)))))))
Finally, all other configuration files, and my previously defined custom init
hooks are executed within a lambda function called by the window-setup-hook
,
which is run at the very end of emacs startup, and never run in batch mode.
(use-package dashboard :defer t :load-path "packages/" :commands ed/dashboard) (add-hook 'window-setup-hook (lambda () (let ((gc-cons-threshold most-positive-fixnum)) (ed/expand-load "org") ;; Now unwrap deferred calls (with-temp-message "Here's my number...So call me maybe..." (run-hooks 'ed/call-me-maybe-hook) ;; Only run when a window system is present (when (display-graphic-p) (run-hooks 'ed/call-me-maybe-when-graphic-hook))) (message "Here's my number...So call me maybe...done") ;; Dashboard (ed/dashboard 'check-startup)) ;; I can now safely activate back auto-saving-mode (setq auto-save-default t)))