🏆 I provide private lessons on Emacs, Linux, and Life in general: https://protesilaos.com/coach/. Lessons continue throughout the year.

GNU Emacs configuration

This page is the successor to my old Emacs literate configuration file. I no longer use Org to maintain my Emacs setup. Instead, everything is done directly with Emacs Lisp. The purpose of this document is to describe my setup and list the contents of all the relevant files I am loading. Last revised and exported on 2023-08-05 10:26:38 +0300.

1. About this page

Herein you will find documentation about my files for Emacs. This is not a literate configuration: it is ordinary Emacs Lisp code. I am writing about it in this separate file to explain how my setup is organised.

2. Details of my Emacs build

I track the trunk of emacs.git, as I am the maintainer of several Emacs packages and a contributor to Emacs core. Users of Arch Linux can refer to this PKGBUILD I maintain for my purposes:

3. Anatomy of my Emacs configuration

3.1. Overview of my Emacs configuration

early-init.el
Optimisations for starting up Emacs and setting the basics.
init.el
Defines foundational blocks of my system and loads the individual configuration modules.
prot-emacs-modules directory
Includes all my configuration modules. These simply tweak packages and are not meant to define extra functionality.
prot-lisp directory
Custom packages

3.2. The anatomy of my Emacs in detail

I have built my setup from scratch and am observing best practices with regard to how Emacs expects things to run. I do not use the Emacs daemon, as I have encountered instabilities with it. Instead, I run a single instance of Emacs and then configure it to act as the server. This means that I can still connect to the running session via emacsclient, which is useful when I want to evaluate Elisp code from outside of Emacs (e.g. with the delight shell script that switches the entire “environment” theme of my tiling window manager—see my dotfiles for the technicalities).

With those granted, here is an overview of how files are organised and what they do:

The early-init.el file
This is the first file that Emacs reads when starting up. It should contain code that does not depend on any package or the proportions of the Emacs frame. My early-init.el is the place where I:
  • Set up the parameters of the initial and all future frames.
  • Optimise the early initialisation phase to speed up startup time.
  • Define functions that test whether my environment is using a light or dark theme. Those are used by my Emacs theme-related configurations, to load an appropriate theme.
  • Prevent the initial flash of light if my environment theme is dark when I launch Emacs.
The init.el file
This is the main configuration file that Emacs uses. Mine defines some user options that are intended for use in the prot-emacs-pre-custom.el file (more below) and then goes on to handle the substantive parts of my configuration. Concretely:
  • Make the custom-file disposable because I consider persistent configurations outside my hand-written code to be highly problematic.
  • Register my prot-lisp/ and prot-emacs-modules/ in the Emacs load-path. Read further below for what these directories contain.
  • Load all the modules of my setup in the appropriate order.
  • Arrange my package archives and ensures that the packages I develop are drawn from the elpa-devel archive. Read here for all my packages: https://protesilaos.com/emacs. I install my packages from elpa-devel just to be sure that their installation works properly.
  • Define the Lisp Macros that are used throughout my setup, such as prot-emacs-package and prot-emacs-keybind. Why not use-package, especially now that it is built into Emacs? Because (i) I do not like many aspects of its behaviour, such as key bindings “magically” delaying the load of a package, and (ii) I have no use for most of its functionality. Writing my own Lisp macros helps me practice my coding skills and get exactly what I want.
The prot-emacs-modules directory

This is where I store all the individual components of my Emacs setup. The directory is a subdirectory of ~/.emacs.d/. All files are prefixed with prot-emacs-, followed by a word that broadly describes their scope of application, such as prot-emacs-font, prot-emacs-window… Each module consists of ordinary Elisp and a final call to provide the set of configurations as a feature that can then be loaded via require from the init.el. What Emacs calls a “feature” is, in essence a variable whose value is the entirety of the file that has a provide call to it. Features are symbols that are named after the file name minus its file type extension: prot-emacs-font is the feature provided by prot-emacs-font.el.

Modules are intended only for configuration purposes. They do not define any major variables/functions.

The prot-lisp directory
As with the aforementioned modules, this directory is a subdirectory of ~/.emacs.d/. This is where I keep all my custom code that individual modules can use. The contents of this directory can be understood as “packages” and, in fact, many of my actual packages started out as prot-lisp experiments. Each file is written in accordance with the conventions on Emacs packaging, even though they are only intended for use in my setup and are not polished to the level of my actual public-facing packages (meaning the ones listed here: https://protesilaos.com/emacs).
The prot-emacs-pre-custom file
It is evaluated before the modules are loaded. It is intended for users of my configuration who want to:
  • Specify their preferred theme family by setting the user option prot-emacs-load-theme-family.
  • Choose a completion framework of their choice by configuring prot-emacs-completion-ui.
  • Opt to omit packages from being loaded by the init.el and its modules, by defining the value of prot-emacs-omit-packages.
The prot-emacs-post-custom file
Like the above, this file is meant for users of my setup. It is evaluated after the rest of my setup is loaded. Here they can include whatever code they want.
The prot-emacs.org file
The source of what you are currently reading. It is not pertinent to the configuration of Emacs. All it does is document the rest of my code.

4. Contents of all files in my setup

The following sections produce the contents of all the files that form part of my Emacs setup. ⚠️ This is a work-in-progress: not all files are included herein, though the plan is to add everything.

4.1. early-init.el

As explained earlier, this is the first file that my Emacs reads at startup (Anatomy of my Emacs configuration).

;;; early-init.el --- Early Init File -*- lexical-binding: t -*-

;; Copyright (c) 2020-2023  Protesilaos Stavrou <info@protesilaos.com>

;; Author: Protesilaos Stavrou <info@protesilaos.com>
;; URL: https://protesilaos.com/emacs/dotemacs
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This file 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 file 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 file.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; See my dotfiles: https://git.sr.ht/~protesilaos/dotfiles

;;; Code:

(defvar prot-emacs-tiling-window-manager-regexp
  "\\(?:\\(?:bsp\\|herbstluft\\)wm\\)"
  "Regular expression to match desired tiling window managers.
See definition of `prot-emacs-with-desktop-session'.")

(defmacro prot-emacs-with-desktop-session (&rest body)
  "Expand BODY if desktop session is not a tiling window manager.
See `prot-emacs-tiling-window-manager-regexp' for what
constitutes a matching tiling window manager."
  (declare (indent 0))
  `(when-let* ((session (getenv "DESKTOP_SESSION"))
               ((not (string-match-p session prot-emacs-tiling-window-manager-regexp))))
     ,@body))

(defun prot-emacs-add-to-list (list element)
  "Add to symbol of LIST the given ELEMENT.
Simplified version of `add-to-list'."
  (set list (cons element (symbol-value list))))

(prot-emacs-with-desktop-session
  (mapc
   (lambda (var)
     (prot-emacs-add-to-list var '(width . (text-pixels . 1200)))
     (prot-emacs-add-to-list var '(height . (text-pixels . 900)))
     (prot-emacs-add-to-list var '(scroll-bar-width  . 12)))
   '(default-frame-alist initial-frame-alist)))

(setq frame-resize-pixelwise t
      frame-inhibit-implied-resize t
      use-dialog-box t ; only for mouse events, which I seldom use
      use-file-dialog nil
      inhibit-splash-screen t
      inhibit-startup-screen t
      inhibit-x-resources t
      inhibit-startup-echo-area-message user-login-name ; read the docstring
      inhibit-startup-buffer-menu t)

;; I do not use those graphical elements by default, but I do enable
;; them from time-to-time for testing purposes or to demonstrate
;; something.
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)

;; Temporarily increase the garbage collection threshold.  These
;; changes help shave off about half a second of startup time.
(defvar prot-emacs--gc-cons-threshold gc-cons-threshold)

(setq gc-cons-threshold most-positive-fixnum)

;; Same idea as above for the `file-name-handler-alist'.
(defvar prot-emacs--file-name-handler-alist file-name-handler-alist)

(setq file-name-handler-alist nil)

;; Same idea as above for the `vc-handled-backends'.
(defvar prot-emacs--vc-handled-backends vc-handled-backends)

(setq vc-handled-backends nil)

(add-hook 'emacs-startup-hook
          (lambda ()
            (setq gc-cons-threshold prot-emacs--gc-cons-threshold
                  file-name-handler-alist prot-emacs--file-name-handler-alist
                  vc-handled-backends prot-emacs--vc-handled-backends)))

;; Initialise installed packages at this early stage, by using the
;; available cache.  I had tried a setup with this set to nil in the
;; early-init.el, but (i) it ended up being slower and (ii) various
;; package commands, like `describe-package', did not have an index of
;; packages to work with, requiring a `package-refresh-contents'.
(setq package-enable-at-startup t)

;;;; General theme code

(defun prot-emacs-theme-gsettings-dark-p ()
  "Return non-nil if gsettings (GNOME) has a dark theme.
Return nil if the DESKTOP_SESSION is either bspwm or
herbstluftwm, per the configuration of my dotfiles.  Also check
the `delight' shell script."
  (prot-emacs-with-desktop-session
    (string-match-p
     "dark"
     (shell-command-to-string "gsettings get org.gnome.desktop.interface color-scheme"))))

(defun prot-emacs-theme-twm-dark-p ()
  "Return non-nil if my custom setup has a dark theme.
I place a file in ~/.config/prot-xtwm-active-theme which contains
a single word describing my system-wide theme.  This is part of
my dotfiles.  Check my `delight' shell script for more."
  (when-let ((file "~/.config/prot-xtwm-active-theme")
             ((file-exists-p file)))
      (string-match-p
       "dark"
       (with-temp-buffer
         (insert-file-contents file)
         (buffer-substring-no-properties (point-min) (point-max))))))

(defun prot-emacs-theme-environment-dark-p ()
  "Return non-nil if environment theme is dark."
  (or (prot-emacs-theme-twm-dark-p)
      (prot-emacs-theme-gsettings-dark-p)))

(defun prot-emacs-re-enable-frame-theme (_frame)
  "Re-enable active theme, if any, upon FRAME creation.
Add this to `after-make-frame-functions' so that new frames do
not retain the generic background set by the function
`prot-emacs-avoid-initial-flash-of-light'."
  (when-let ((theme (car custom-enabled-themes)))
    (enable-theme theme)))

;; NOTE 2023-02-05: The reason the following works is because (i) the
;; `mode-line-format' is specified again and (ii) the
;; `prot-emacs-theme-gsettings-dark-p' will load a dark theme.
(defun prot-emacs-avoid-initial-flash-of-light ()
  "Avoid flash of light when starting Emacs, if needed.
New frames are instructed to call `prot-emacs-re-enable-frame-theme'."
  (when (prot-emacs-theme-environment-dark-p)
    (setq mode-line-format nil)
    (set-face-attribute 'default nil :background "#000000" :foreground "#ffffff")
    (set-face-attribute 'mode-line nil :background "#000000" :foreground "#ffffff" :box 'unspecified)
    (add-hook 'after-make-frame-functions #'prot-emacs-re-enable-frame-theme)))

(prot-emacs-avoid-initial-flash-of-light)

(add-hook 'after-init-hook (lambda () (set-frame-name "home")))

;;; early-init.el ends here

4.2. init.el

This is where I define the Lisp macros used in my setup and load all the invidiual modules.

;;; init.el --- Personal configuration file -*- lexical-binding: t -*-

;; Copyright (c) 2019-2023  Protesilaos Stavrou <info@protesilaos.com>

;; Author: Protesilaos Stavrou <info@protesilaos.com>
;; URL: https://protesilaos.com/emacs/dotemacs
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This file 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 file 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 file.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; See my dotfiles: <https://git.sr.ht/~protesilaos/dotfiles>

;;; Code:

(defgroup prot-emacs nil
  "User options for my dotemacs."
  :group 'file)

;; For those who use my dotfiles and need an easy way to write their
;; own extras on top of what I already load: search below for the files
;; prot-emacs-pre-custom.el and prot-emacs-post-custom.el

(defcustom prot-emacs-load-theme-family 'modus
  "Set of themes to load.
Valid values are the symbols `ef', `modus', and `standard', which
reference the `ef-themes', `modus-themes', and `standard-themes',
respectively.

A nil value does not load any of the above (use Emacs without a
theme).

This user option must be set in the `prot-emacs-pre-custom.el'
file.  If that file exists in the Emacs directory, it is loaded
before all other modules of my setup."
  :group 'prot-emacs
  :type '(choice :tag "Set of themes to load" :value modus
                 (const :tag "The `ef-themes' module" ef)
                 (const :tag "The `modus-themes' module" modus)
                 (const :tag "The `standard-themes' module" standard)
                 (const :tag "Do not load a theme module" nil)))

(defcustom prot-emacs-completion-ui 'vertico
  "Choose minibuffer completion UI between `mct' or `vertico'."
  :group 'prot-emacs
  :type '(choice :tag "Minibuffer user interface"
                 (const :tag "The `mct' module" mct)
                 (const :tag "The `vertico' module" vertico)))

(defcustom prot-emacs-omit-packages nil
  "List of package names to not load.
This instructs the relevant macros to not `require' the given
package.  In the case of `prot-emacs-elpa-package', the package
will not be installed if it is not already available on the
system.

This user option must be set in the `prot-emacs-pre-custom.el'
file.  If that file exists in the Emacs directory, it is loaded
before all other modules of my setup."
  :group 'prot-emacs
  :type '(repeat symbol))

;; Some basic settings
(setq frame-title-format '("%b"))
(setq ring-bell-function 'ignore)
(setq use-short-answers t)
(setq native-comp-async-report-warnings-errors 'silent) ; Emacs 28 with native compilation
(setq native-compile-prune-cache t) ; Emacs 29
(setq make-backup-files nil)
(setq backup-inhibited nil) ; Not sure if needed, given `make-backup-files'
(setq create-lockfiles nil)

;; Disable the damn thing by making it disposable.
(setq custom-file (make-temp-file "emacs-custom-"))

;; There is also the greek-postfix style.  This is for inserting
;; accents.  I am used to the standard use of a prefix.
(setq default-input-method "greek")

;; Enable these
(dolist (c '(narrow-to-region narrow-to-page upcase-region downcase-region))
  (put c 'disabled nil))

;; And disable these
(dolist (c '(eshell project-eshell overwrite-mode iconify-frame diary))
  (put c 'disabled t))

;; Always start with *scratch*
(setq initial-buffer-choice t)

;;;; Packages

(dolist (path '("prot-lisp" "prot-emacs-modules"))
  (add-to-list 'load-path (locate-user-emacs-file path)))

(require 'package)

(with-eval-after-load 'project-vc
  (setq package-vc-register-as-project nil)) ; Emacs 30

(add-hook 'package-menu-mode-hook #'hl-line-mode)

;; Also read: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>
(setq package-archives
      '(("gnu-elpa" . "https://elpa.gnu.org/packages/")
        ("gnu-elpa-devel" . "https://elpa.gnu.org/devel/")
        ("nongnu" . "https://elpa.nongnu.org/nongnu/")
        ("melpa" . "https://melpa.org/packages/")))

;; Highest number gets priority (what is not mentioned has priority 0)
(setq package-archive-priorities
      '(("gnu-elpa" . 3)
        ("melpa" . 2)
        ("nongnu" . 1)))

;; I want to use my own packages from specific repositories.  All
;; others will rely on `package-archive-priorities'.  I do this to
;; test that the packaged version works as intended.
(defvar prot-emacs-my-packages
  '(agitate
    altcaps
    beframe
    cursory
    denote
    dired-preview
    ef-themes
    fontaine
    lin
    logos
    mct
    modus-themes
    notmuch-indicator
    pulsar
    spacious-padding
    standard-themes
    substitute
    sxhkdrc-mode
    tmr)
  "List of symbols representing the packages I develop/maintain.")

(setq package-pinned-packages
      `(,@(mapcar
           (lambda (package)
             (cons package "gnu-elpa-devel"))
           prot-emacs-my-packages)))

(setq custom-safe-themes t)

(defun prot-emacs-package-install (package &optional method)
  "Install PACKAGE with optional METHOD.

If METHOD is nil or the `builtin' symbol, PACKAGE is not
installed as it is considered part of Emacs.

If METHOD is a string, it must be a URL pointing to the version
controlled repository of PACKAGE.  Installation is done with
`package-vc-install'.

If METHOD is a quoted list, it must have a form accepted by
`package-vc-install' such as:

\\='(denote :url \"https://git.sr.ht/~protesilaos/denote\" :branch \"main\")

If METHOD is any other non-nil value, install PACKAGE using
`package-install'."
  (unless (or (eq method 'builtin) (null method))
    (unless (package-installed-p package)
      (when (or (stringp method) (listp method))
        (package-vc-install method))
      (unless package-archive-contents
        (package-refresh-contents))
      (package-install package))))

(defvar prot-emacs-loaded-packages nil)

(defmacro prot-emacs-package (package &rest body)
  "Require PACKAGE with BODY configurations.

PACKAGE is an unquoted symbol that is passed to `require'.  It
thus conforms with `featurep'.

BODY consists of ordinary Lisp expressions.  There are,
nevertheless, two unquoted plists that are treated specially:

1. (:install METHOD)
2. (:delay NUMBER)

These plists can be anywhere in BODY and are not part of its
final expansion.

The :install property is the argument passed to
`prot-emacs-package-install' and has the meaning of METHOD
described therein.

The :delay property makes the evaluation of PACKAGE with the
expanded BODY happen with `run-with-timer'.

Also see `prot-emacs-configure'."
  (declare (indent 1))
  (unless (memq package prot-emacs-omit-packages)
    (let (install delay)
      (dolist (element body)
        (when (plistp element)
          (pcase (car element)
            (:install (setq install (cdr element)
                            body (delq element body)))
            (:delay (setq delay (cadr element)
                          body (delq element body))))))
      (let ((common `(,(when install
                         `(prot-emacs-package-install ',package ,@install))
                      (require ',package)
                      (add-to-list 'prot-emacs-loaded-packages ',package)
                      ,@body
                      ;; (message "Prot Emacs loaded package: %s" ',package)
                      )))
        (cond
         ((featurep package)
          `(progn ,@body))
         (delay
          `(run-with-timer ,delay nil (lambda () ,@(delq nil common))))
         (t
          `(progn ,@(delq nil common))))))))

;; Samples of `prot-emacs-package' (expand them with `pp-macroexpand-last-sexp').

;; (prot-emacs-package denote
;;   (setq denote-directory "path/to/dir")
;;   (define-key global-map (kbd "C-c n") #'denote)
;;   (:install '(denote . (:url "https://git.sr.ht/~protesilaos/denote" :branch "main")))
;;   (:delay 5)
;;   (setq denote-file-type nil))
;;
;; (prot-emacs-package denote
;;   (setq denote-directory "path/to/dir")
;;   (define-key global-map (kbd "C-c n") #'denote)
;;   (:install "https://git.sr.ht/~protesilaos/denote")
;;   (:delay 5)
;;   (setq denote-file-type nil))
;;
;; (prot-emacs-package denote
;;   (:delay 5)
;;   (setq denote-directory "path/to/dir")
;;   (define-key global-map (kbd "C-c n") #'denote)
;;   (:install "https://git.sr.ht/~protesilaos/denote")
;;   (setq denote-file-type nil))
;;
;; (prot-emacs-package denote
;;   (:install "https://git.sr.ht/~protesilaos/denote")
;;   (:delay 5)
;;   (setq denote-directory "path/to/dir")
;;   (define-key global-map (kbd "C-c n") #'denote)
;;   (setq denote-file-type nil))
;;
;; (prot-emacs-package denote
;;   (:delay 5)
;;   (setq denote-directory "path/to/dir")
;;   (define-key global-map (kbd "C-c n") #'denote)
;;   (setq denote-file-type nil))
;;
;; (prot-emacs-package denote
;;   (setq denote-directory "path/to/dir")
;;   (define-key global-map (kbd "C-c n") #'denote)
;;   (setq denote-file-type nil))

(defmacro prot-emacs-configure (&rest body)
  "Evaluate BODY as a `progn'.
BODY consists of ordinary Lisp expressions.  The sole exception
is an unquoted plist of the form (:delay NUMBER) which evaluates
BODY with NUMBER seconds of `run-with-timer'.

Note that `prot-emacs-configure' does not try to autoload
anything.  Use it only for forms that evaluate regardless.

Also see `prot-emacs-package'."
  (declare (indent 0))
  (let (delay)
    (dolist (element body)
      (when (plistp element)
        (pcase (car element)
          (:delay (setq delay (cadr element)
                        body (delq element body))))))
    (if delay
        `(run-with-timer ,delay nil (lambda () ,@body))
      `(progn ,@body))))


(defmacro prot-emacs-keybind (keymap &rest definitions)
  "Expand key binding DEFINITIONS for the given KEYMAP.
DEFINITIONS is a sequence of string and command pairs."
  (declare (indent 1))
  (unless (zerop (% (length definitions) 2))
    (error "Uneven number of key+command pairs"))
  (let ((keys (seq-filter #'stringp definitions))
        ;; We do accept nil as a definition: it unsets the given key.
        (commands (seq-remove #'stringp definitions)))
    `(when-let (((keymapp ,keymap))
                (map ,keymap))
       ,@(mapcar
          (lambda (pair)
            (unless (and (null (car pair))
                         (null (cdr pair)))
              `(define-key map (kbd ,(car pair)) ,(cdr pair))))
          (cl-mapcar #'cons keys commands)))))

;; Sample of `prot-emacs-keybind'

;; (prot-emacs-keybind global-map
;;   "C-z" nil
;;   "C-x b" #'switch-to-buffer
;;   "C-x C-c" nil
;;   "C-x k" #'kill-buffer)

(defmacro prot-emacs-abbrev (table &rest definitions)
  "Expand abbrev DEFINITIONS for the given TABLE.
DEFINITIONS is a sequence of string pairs mapping the
abbreviation to its expansion."
  (declare (indent 1))
  (unless (zerop (% (length definitions) 2))
    (error "Uneven number of key+command pairs"))
  `(when-let (((abbrev-table-p ,table))
              (table ,table))
     ,@(mapcar
        (lambda (pair)
          (when-let ((abbrev (car pair))
                     (expansion (cadr pair)))
            `(define-abbrev table ,abbrev ,expansion)))
        (seq-split definitions 2))))

(defun prot-emacs-return-loaded-packages ()
  "Return a list of all loaded packages.
Here packages include both `prot-emacs-loaded-packages' and
`package-activated-list'.  The latter only covers what is found
in the `package-archives', whereas the former is for anything
that is expanded with the `prot-emacs-package' macro."
  (delete-dups (append prot-emacs-loaded-packages package-activated-list)))

(defvar prot-emacs-package-form-regexp
  "^(\\(prot-emacs-package\\|prot-emacs-keybind\\|prot-emacs-abbrev\\|require\\) +'?\\([0-9a-zA-Z-]+\\)"
  "Regexp to add packages to `lisp-imenu-generic-expression'.")

(eval-after-load 'lisp-mode
  `(add-to-list 'lisp-imenu-generic-expression
                (list "Packages" ,prot-emacs-package-form-regexp 2)))

(defconst prot-emacs-font-lock-keywords
  '(("(\\(prot-emacs-package\\)\\_>[ \t']*\\(\\(?:\\sw\\|\\s_\\)+\\)?"
     (2 font-lock-constant-face nil t))
    ("(\\(prot-emacs-\\(keybind\\|abbrev\\)\\)\\_>[ \t']*\\(\\(\\sw\\|\\s_\\)+\\)?"
     (3 font-lock-variable-name-face nil t))))

(font-lock-add-keywords 'emacs-lisp-mode prot-emacs-font-lock-keywords)

;; For those who use my dotfiles and need an easy way to write their
;; own extras on top of what I already load.  The file must exist at
;; ~/.emacs.d/prot-emacs-pre-custom.el
;;
;; The purpose of this file is for the user to define their
;; preferences BEFORE loading any of the modules.  For example, the
;; user option `prot-emacs-omit-packages' lets the user specify which
;; packages not to load.  Search for all `defcustom' forms in this
;; file for other obvious customisations.
(load (locate-user-emacs-file "prot-emacs-pre-custom.el") :no-error :no-message)

(require 'prot-emacs-essentials)
(pcase prot-emacs-load-theme-family
  ('ef (require 'prot-emacs-ef-themes))
  ('modus (require 'prot-emacs-modus-themes))
  ('standard (require 'prot-emacs-standard-themes)))
(require 'prot-emacs-theme-extras)
(require 'prot-emacs-font)
(require 'prot-emacs-modeline)
(require 'prot-emacs-completion-common)
(pcase prot-emacs-completion-ui
  ('mct (require 'prot-emacs-completion-mct))
  ('vertico (require 'prot-emacs-completion-vertico)))
(require 'prot-emacs-search)
(require 'prot-emacs-dired)
(require 'prot-emacs-window)
(require 'prot-emacs-git)               ; git, diff, and related
(require 'prot-emacs-write)             ; denote, logos, etc.
(require 'prot-emacs-org)               ; org, calendar, appt
(require 'prot-emacs-langs)
(require 'prot-emacs-email)
(when (executable-find "notmuch")
  (require 'prot-emacs-email-notmuch))
(require 'prot-emacs-web)               ; eww, elfeed, rcirc

(setq safe-local-variable-values
      '((org-hide-leading-stars . t)
        (org-hide-macro-markers . t)))

;; For those who use my dotfiles and need an easy way to write their
;; own extras on top of what I already load.  The file must exist at
;; ~/.emacs.d/user-emacs.el OR ~/.emacs.d/prot-emacs-post-custom.el
;;
;; The purpose of the "post customisations" is to make tweaks to what
;; I already define, such as to change the default theme.  See above
;; for the `prot-emacs-pre-custom.el' to make changes BEFORE loading
;; any of my other configurations.
(load (locate-user-emacs-file "prot-emacs-post-custom.el") :no-error :no-message)

;;; init.el ends here