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
  (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
                                  (compat . "gnu") ;; magit mainly
                                  (dash . "gnu") ;; almost everyone
                                  (faceup . "manual") ;; built-in
                                  (git-commit . "nongnu") ;; magit
                                  (haml-mode . "nongnu") ;; sass-mode
                                  (inf-ruby . "nongnu") ;; company-inf-ruby
                                  (magit-section . "nongnu") ;; magit
                                  (package-lint . "nongnu") ;; flycheck-package
                                  (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
                                  (yasnippet . "gnu"))) ;; company-auctex
  (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 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)
                ;; 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)))