GNU Emacs Configuration

I’ve recently moved from a literate Emacs configuration based on Org mode to a simpler init.el file, reproduced below:

;;; init.el --- Personal Emacs configuration -*- lexical-binding: t -*-
;; Copyright (C) 2021-2023 Eshel Yaron

;; Author: Eshel Yaron <[email protected]>

;;; Commentary:

;; My personal Emacs configuration

;;; Code:

(defvar esy/init-step-times nil)

(defmacro esy/init-step (name _doc &rest body)
  (declare (indent defun)
           (doc-string 2))
  (let ((start-time-symbol (gensym)))
    `(let ((,start-time-symbol (current-time)))
       ,@body
       (push (cons ',name
                   (time-subtract (current-time) ,start-time-symbol))
             esy/init-step-times))))

(esy/init-step gc
  "Temporarily increase GC threshold to expedite Emacs startup."
  (let ((normal-gc-cons-threshold (* 20 1024 1024))
        (init-gc-cons-threshold (* 1024 1024 1024))
        (normal-mode-line-format mode-line-format))
    (setq gc-cons-threshold init-gc-cons-threshold
          mode-line-format nil)
    (add-hook 'after-init-hook
              (lambda ()
                (setq gc-cons-threshold normal-gc-cons-threshold
                      mode-line-format normal-mode-line-format)))))

(esy/init-step vars
  "Set some variables."
  (setq
   ;; my name
   user-full-name "Eshel Yaron"
   ;; my email address
   user-mail-address "[email protected]"
   ;; direct Custom definitions to some file and forget about it
   custom-file (expand-file-name "custom.el" user-emacs-directory)
   ;; silence native compilation warnings
   native-comp-async-report-warnings-errors 'silent
   ;; only display the warning buffer if something's really broken
   warning-minimum-level :error
   ;; don't use stale .elc files
   load-prefer-newer t
   ;; maximize Emacs on startup
   default-frame-alist '((fullscreen . fullboth))
   ;; disable popup dialogs
   use-dialog-box nil
   ;; disable splash screen
   inhibit-startup-screen t
   ;; make the scratch message more concise
   initial-scratch-message ";; Go.\n"
   ;; don't ring the bell
   ring-bell-function #'ignore
   ;; make C-x b obey display-buffer-alist
   switch-to-buffer-obey-display-actions t
   ;; disable new mail mode line indication
   display-time-mail-function #'ignore
   ;; enable recursive minibuffers
   enable-recursive-minibuffers t
   ;; save bookmarks immediately
   bookmark-save-flag 1
   ;; show matches count in isearch prompt
   isearch-lazy-count t
   ;; make some space in the Packages menu
   package-archive-column-width 12
   package-version-column-width 28
   ;; configure package archives
   package-archives '(("melpa"  . "http://melpa.org/packages/")
                      ("gnu"    . "https://elpa.gnu.org/devel/")
                      ("nongnu" . "https://elpa.nongnu.org/nongnu-devel/"))
   ;; select some packages to install
   package-selected-packages '(
                               all-the-icons
                               all-the-icons-completion
                               all-the-icons-dired
                               all-the-icons-gnus
                               auctex
                               avy
                               bbdb
                               corfu
                               devdocs
                               diff-hl
                               elfeed
                               embark-consult
                               emms
                               gnu-elpa-keyring-update
                               gnuplot
                               graphql-mode
                               graphviz-dot-mode
                               htmlize
                               ialign
                               keycast
                               kubernetes
                               lin
                               magit
                               marginalia
                               markdown-mode
                               mastodon
                               ob-prolog
                               orderless
                               org
                               org-transclusion
                               package-lint
                               paredit
                               pdf-tools
                               rainbow-delimiters
                               rainbow-mode
                               rg
                               smtpmail-multi
                               sqlformat
                               sweeprolog
                               terraform-mode
                               vterm
                               vundo
                               whitespace-cleanup-mode
                               )
   ;; configure Org capture templates
   org-capture-templates '(("t" "Todo [inbox]" entry
                            (file+headline "~/org/inbox.org" "Tasks")
                            "** TODO [#B] %^{Task}    %^g
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Inbox Task
:END:"
                            :prepend t
                            :empty-lines 1
                            :immediate-finish t)
                           ("w" "Work [inbox]" entry
                            (file+headline "~/org/inbox.org" "Tasks")
                            "** TODO [#B] %^{Task}    :work:
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Work Task
:END:"
                            :prepend t
                            :empty-lines 1
                            :immediate-finish t)
                           ("c" "New Calendar Event" entry
                            (file+headline "~/org/inbox.org" "Calendar")
                            "** %^{Title}    %^g
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Calendar Event
:END:
%(format-time-string \"<%Y-%m-%d %H:%M\" (org-read-date t t))-%(format-time-string \"%H:%M>\" (org-read-date t t))
%i"
                            :prepend t
                            :empty-lines 1
                            :immediate-finish t))
   ;; point Org to file agenda file
   org-agenda-files '("~/org/inbox.org")
   org-default-notes-file "~/org/inbox.org"
   ;; weeks start on Sunday
   org-agenda-start-on-weekday 0
   ;; use a nice unicode ellipsis
   org-ellipsis "…"
   ;; open everything withing Emacs
   org-file-apps '((t . emacs))
   ;; enable Org speed commands
   org-use-speed-commands t
   ;; task states
   org-todo-keywords '((sequence
                        "TODO(t)"
                        "BLOCKED(b@/!)"
                        "INPROGRESS(i!)"
                        "|"
                        "DONE(d!)"
                        "CANCELED(c@)"))
   ;; set a task to INPROGRESS when I clock into it
   org-clock-in-switch-to-state "INPROGRESS"
   ;; log the finish time of tasks in Org
   org-log-done 'time
   org-log-into-drawer t
   ;; minimal prompt for task states
   org-use-fast-todo-selection 'expert
   ;; set up a group of mutual exclusive tags
   org-tag-alist '((:startgroup)
                   ("adi"      . ?a)
                   ("holland"  . ?h)
                   ("work"     . ?w)
                   ("studies"  . ?s)
                   ("esols"    . ?e)
                   ("personal" . ?p)
                   (:endgroup))
   ;; minimal prompt for tags
   org-fast-tag-selection-single-key 'expert
   ;; allow evaluating some languages with Org Babel
   org-babel-load-languages '((emacs-lisp . t)
                              (shell      . t)
                              (sql        . t)
                              (prolog     . t))
   ;; do not ask for confirmation when evaluating Org source blocks
   org-confirm-babel-evaluate nil
   ;; extra modules to load along with Org
   org-modules '(ol-bbdb
                 ol-bibtex
                 ol-docview
                 ol-gnus
                 ol-info
                 ol-irc
                 ol-mhe
                 ol-eww
                 ob-sql
                 org-tempo)
   ;; refile Org entries to my agenda file(s)
   org-refile-targets '((org-agenda-files . (:maxlevel . 5))
                        (nil . (:maxlevel . 3)))
   ;; use full outline path when prompting for refile target
   org-refile-use-outline-path t
   ;; archive for Org entries
   org-archive-location "~/org/journal.org::datetree/* Finished Tasks   :ARCHIVE:"
   ;; associate Org Babel languages with major modes
   org-src-lang-modes '(("SWI-Prolog" . sweeprolog)
                        ("Dockerfile" . dockerfile-ts)
                        ("bash"       . sh)
                        ("shell"      . sh)
                        ("toml"       . toml-ts))
   ;; don't auto-save remote files
   remote-file-name-inhibit-auto-save t
   ;; don't ask me about it either
   tramp-allow-unsafe-temporary-files t
   ;; increase maximum number of recent files Emacs remembers
   recentf-max-saved-items 128
   ;; increase maximum kill ring size
   kill-ring-max 256
   ;; save text copied from another program to the kill ring
   save-interprogram-paste-before-kill 2048
   ;; have C-u followed by repeated C-SPC keep popping
   set-mark-command-repeat-pop t
   ;; enable italic text in code
   modus-themes-italic-constructs t
   ;; enable bold text in code
   modus-themes-bold-constructs t
   ;; use fixed-pitch text where appropriate
   modus-themes-mixed-fonts t
   ;; make prompts bold
   modus-themes-prompts '(bold)
   ;; give source blocks a distinct background shade
   modus-themes-org-blocks 'gray-background
   ;; use variable-pitch text here are there
   modus-themes-variable-pitch-ui t
   ;; fine-tune some theme settings
   modus-themes-common-palette-overrides '((border-mode-line-active unspecified)
                                           (border-mode-line-inactive unspecified)
                                           (bg-mode-line-active bg-blue-intense)
                                           (fg-mode-line-active fg-main)
                                           (fringe unspecified)
                                           (underline-link border)
                                           (underline-link-visited border)
                                           (underline-link-symbolic border)
                                           (fg-region unspecified))
   ;; follow links to version-controlled files without confirming
   vc-follow-symlinks t
   ;; jump from .c to .h files with find-sibling-file
   find-sibling-rules '(("\\([^/]+\\)\\.c\\'" "\\1.h"))
   ;; configure multiple SMTP accounts
   smtpmail-multi-accounts '((esy . ("[email protected]"
                                     "smtp.gmail.com" 587
                                     "[email protected]"
                                     starttls nil nil nil))
                             (swp . ("[email protected]"
                                     "mail.swi-prolog.com" 587
                                     "[email protected]"
                                     starttls nil nil nil))
                             (me  . ("[email protected]"
                                     "mail.eshelyaron.com"
                                     587
                                     "[email protected]"
                                     starttls nil nil nil))
                             ;; (uva  . ("[email protected]"
                             ;;         "smtp.office365.com"
                             ;;         587
                             ;;         "[email protected]"
                             ;;         starttls nil nil nil))
                             )
   ;; associate email addresses with SMTP accounts
   smtpmail-multi-associations '(("eshelshay\[email protected]\.com"   esy)
                                 ("[email protected]\.org"         swp)
                                 ("[email protected]\.com"             me)
                                 ;; ("eshel\[email protected]\.uva\.nl" uva)
                                 )
   ;; set function for sending email
   send-mail-function #'smtpmail-multi-send-it
   message-send-mail-function #'smtpmail-multi-send-it
   ;; set email user agent
   mail-user-agent 'gnus-user-agent
   read-mail-command 'gnus
   ;; make Gnus not prompt as much
   gnus-expert-user t
   ;; make Gnus read the dribble file on startup without prompting
   gnus-always-read-dribble-file t
   ;; don't break pages in incoming email
   gnus-break-pages nil
   ;; don't show the Gnus startup screen
   gnus-inhibit-startup-message t
   ;; set Gnus backends
   gnus-select-method '(nntp "news.gmane.io")
   gnus-secondary-select-methods '((nnimap "gmail"
                                           (nnimap-address "imap.gmail.com")
                                           (nnimap-server-port "imaps")
                                           (nnimap-stream ssl))
                                   (nnimap "me"
                                           (nnimap-address "mail.eshelyaron.com")
                                           (nnimap-server-port "imaps")
                                           (nnimap-stream ssl))
                                   (nnimap "swi"
                                           (nnimap-address "mail.swi-prolog.com")
                                           (nnimap-server-port "imaps")
                                           (nnimap-stream ssl))
                                   ;; (nnimap "uva"
                                   ;;         (nnimap-address "outlook.office365.com")
                                   ;;         (nnimap-server-port "imaps")
                                   ;;         (nnimap-stream ssl))
                                   )
   ;; simpler message
   gnus-no-groups-message "No new articles"
   ;; don't close other windows when opening Gnus
   gnus-use-full-window nil
   ;; make Gnus handle this content types
   gnus-article-treat-types '("text/plain"
                              "text/x-verbatim"
                              "text/x-patch"
                              "text/html"
                              "text/calendar")
   ;; capture calendar events from Gnus to my Org inbox file
   gnus-icalendar-org-capture-file "~/org/inbox.org"
   ;; put calendar events under the Calendar heading
   gnus-icalendar-org-capture-headline '("Calendar")
   ;; use ISO format for calendar dates
   calendar-date-style 'iso
   ;; maintain legibility of rendered text in HTML mails
   shr-color-visible-luminance-min 75
   ;; my Mastodon settings
   mastodon-instance-url "https://emacs.ch"
   mastodon-active-user "eshel"
   ;; direct Magit to my Git checkouts directory
   magit-repository-directories '(("~/checkouts/" . 1))
   ;; have Dired operations target another visible Dired buffer
   dired-dwim-target t
   ;; use zsh in vterm by default
   vterm-shell "/bin/zsh"
   vterm-max-scrollback 2048
   vterm-kill-buffer-on-exit nil
   vterm-use-vterm-prompt-detection-method t
   ;; use MPV with EMMS
   emms-player-list '(emms-player-mpv)
   ;; free-style numbering plan
   bbdb-phone-style nil
   ;; allow cycling through mail address completion candidate
   bbdb-complete-mail-allow-cycling t
   ;; don't pop up BBDB records after completing mail addresses
   bbdb-completion-display-record nil
   eww-auto-rename-buffer 'title
   browse-url-browser-function #'eww-browse-url
   browse-url-generic-program "open"
   global-auto-revert-non-file-buffers t
   auto-revert-verbose nil
   query-about-changed-file t
   ;; show flymake diagnostics as overlays at eol
   ;; flymake-show-diagnostics-at-end-of-line t
   kill-do-not-save-duplicates t
   show-trailing-whitespace t
   read-extended-command-predicate #'command-completion-default-include-p
   completions-format 'one-column
   completion-auto-select nil
   completion-styles '(orderless partial-completion basic)
   completion-show-help nil
   ;; completions-header-format nil
   completion-auto-help 'visual
   completions-max-height 16
   completion-auto-wrap t
   corfu-cycle t
   corfu-indexed-start     1
   shell-kill-buffer-on-exit t
   ;; allow disabling confirming before compilation via local variables
   safe-local-variable-values '((compilation-read-command . nil))
   TeX-view-program-selection '((output-pdf "PDF Tools"))
   TeX-source-correlate-start-server t
   xref-show-definitions-function #'consult-xref
   xref-show-xrefs-function       #'consult-xref
   xref-search-program             'ripgrep
   ;; include CWD in shell command prompts
   shell-command-prompt-show-cwd t
   sqlformat-command 'pgformatter
   sql-input-ring-file-name (expand-file-name ".sqli-history" user-emacs-directory)
   ;; use relative line numbers
   display-line-numbers-type 'relative
   ;; persist Git commit message history
   savehist-additional-variables '(log-edit-comment-ring)
   ;; IRC stuff
   rcirc-default-nick "esy"
   rcirc-server-alist '(("irc.libera.chat"
                         :channels ("#emacs")
                         :port 6697
                         :encryption tls))
   rcirc-log-flag t
   ;; use my custom project-prompting function
   project-prompter #'esy/read-project-by-name
   ;; have common bindings initially hidden in the output of C-h b
   describe-bindings-outline-rules `((match-regexp
                                      .
                                      ,(regexp-opt
                                        '("Key translations"
                                          "Global Bindings:"
                                          "Function key map translations"
                                          "pixel-scroll-precision-mode"
                                          "context-menu-mode")))))
  (setq-default indent-tabs-mode nil))

(esy/init-step theme
  "Load the `modus-vivendi' theme."
  (require-theme 'modus-themes)
  (load-theme 'modus-vivendi))

(esy/init-step fonts
  "Set up the Iosevka font family."
  (set-face-attribute 'default nil        :family "Iosevka" :height 130)
  (set-face-attribute 'fixed-pitch nil    :family "Iosevka")
  (set-face-attribute 'variable-pitch nil :family "Iosevka Etoile"))

(esy/init-step load-path
  "Add custom code directory to `load-path'."
  (add-to-list 'load-path (expand-file-name "lisp/" user-emacs-directory))
  (autoload 'some-button "some-button" nil t)
  (autoload 'esy-capture "esy-capture" nil t)
  (autoload 'pdf-view-mode "pdf-view"  nil t))

(esy/init-step commands
  "Define custom commands."
  (defvar-keymap transpose-lines-repeat-map
    :doc "Repeat map for \\[transpose-lines]"
    "C-t" #'transpose-lines)

  (put 'transpose-lines 'repeat-map 'transpose-lines-repeat-map)

  ;; utility commands
  (defun esy/kill-dwim ()
    "When region is active, kill region, otherwise kill last word."
    (interactive)
    (if (region-active-p)
        (let ((beg (region-beginning))
              (end (region-end)))
          (kill-region beg end))
      (let ((end (point)))
        (backward-word)
        (kill-region (point) end))))

  (defun esy/pulse-line (&optional _)
    "Pulse current line."
    (interactive)
    (pulse-momentary-highlight-one-line))

  (defun esy/seconds-to-date-string (seconds)
    "Decode and display the date corresponding to SECONDS."
    (interactive (list (if (use-region-p)
                           (string-to-number (buffer-substring (region-beginning)
                                                               (region-end)))
                         (read-number "Unix timestamp: " (round (float-time))))))
    (message (format-time-string "%FT%T%z" (seconds-to-time seconds))))

  (defun esy/find-init-file (init)
    "Find the Emacs INIT file."
    (interactive (list user-init-file))
    (find-file init))

  (defun esy/eww-target ()
    (require 'eww)
    (if (use-region-p)
        (let ((target (buffer-substring-no-properties (use-region-beginning)
                                                      (use-region-end))))
          (add-to-history 'eww-prompt-history target)
          target)
      (completing-read "Browse or search: "
                       eww-prompt-history nil nil nil
                       'eww-prompt-history
                       (eww-suggested-uris))))

  (defun esy/eww (target)
    "Browse or search for TARGET."
    (interactive (list (esy/eww-target)))
    (eww target))

  (with-eval-after-load 'shell
    (keymap-set shell-mode-map "SPC" #'comint-magic-space))

  (defvar-local esy/log-buffer-filename nil
    "File in which the current buffer is logged.")

  (defun esy/log-buffer ()
    "Save the current buffer under the ~/logs/ directory."
    (interactive)
    (let ((filename (or esy/log-buffer-filename
                        (setq esy/log-buffer-filename
                              (expand-file-name
                               (concat mode-name
                                       "_"
                                       (format-time-string
                                        "%Y%m%d%H%M%S"
                                        (current-time))
                                       ".log")
                               "~/logs")))))
      (save-restriction
        (widen)
        (write-region (point-min)
                      (point-max)
                      filename))))

  (with-eval-after-load 'comint
    (keymap-set comint-mode-map "C-c C-q" #'esy/log-buffer))

  (defun esy/recent-log-summary ()
    "Display a summary of my website's most recent access log."
    (interactive)
    (let ((default-directory "/ssh:[email protected]:/var/log/apache2/"))
      (async-shell-command (string-join  '("grep -E '\"GET.+\" 2' access.log"
                                           "tr -d '\"'"
                                           "tr -d \"'\""
                                           "cut -f 7,11,12 -d ' '"
                                           "sort"
                                           "uniq -c"
                                           "sort -n")
                                         " | ")
                           "*Recent Log Summary*")))

  (defun esy/access-log-summary ()
    "Display a summary of my website's access log."
    (interactive)
    (let ((default-directory "/ssh:[email protected]:/var/log/apache2/"))
      (async-shell-command (string-join  '("zcat access.log.*.gz"
                                           "cat - access.log access.log.1"
                                           "grep -E '\"GET.+\" 2'"
                                           "tr -d '\"'"
                                           "tr -d \"'\""
                                           "cut -f 7,11,12 -d ' '"
                                           "sort"
                                           "uniq -c"
                                           "sort -n")
                                         " | ")
                           "*Access Log Summary*")))

  (defun esy/clone (remote)
    (interactive (list (let ((default (thing-at-point 'url)))
                         (read-string (format-prompt "Clone" default)
                                      nil nil default))))
    (let ((dir (expand-file-name
                (file-name-base (car (url-path-and-query
                                      (url-generic-parse-url remote))))
                "~/checkouts")))
      (vc-clone remote 'Git dir)
      (find-file dir))))

(esy/init-step auto-exec-permissions
  "Ensure scripts ran with `executable-interpret' are executable."
  (with-eval-after-load 'executable
   (define-advice executable-interpret (:before (&rest _) ensure-executable)
     (unless (file-exists-p buffer-file-name)
       (basic-save-buffer))
     (executable-make-buffer-file-executable-if-script-p))))

(esy/init-step pulse-on-window-selection-change
  "Pulse the line around point after switching windows."

  (defun esy/pulse-on-window-selection-change (&rest _)
    (pulse-momentary-highlight-one-line))

  (add-hook 'window-selection-change-functions
            #'esy/pulse-on-window-selection-change))

(esy/init-step startup-message
  "Override the default startup message."
  (define-advice startup-echo-area-message (:override () report-init-time)
    (format "%s started in %s.  Hack away."
            (propertize "Emacs"           'face 'success)
            (propertize (emacs-init-time) 'face 'error  ))))

(esy/init-step packages
  "Ensure external packages are installed."
  (package-install-selected-packages))

(esy/init-step prog-mode-hook
  "Extend standard programming mode hooks."
  (dolist (mode '(bug-reference-prog-mode
                  display-fill-column-indicator-mode
                  display-line-numbers-mode
                  flymake-mode
                  flyspell-prog-mode
                  rainbow-delimiters-mode))
    (add-hook 'prog-mode-hook mode))
  (add-hook 'lisp-data-mode-hook #'paredit-mode))

(esy/init-step other-hooks
  "Extend other standard hooks."
  (add-hook 'text-mode-hook #'flyspell-mode))

(esy/init-step bindings
  "Bind some keys."
  (keymap-global-set "C-c w" #'esy/eww)
  (keymap-global-set "C-c c" #'org-capture)
  (keymap-global-set "C-c l" #'org-store-link)
  (keymap-global-set "C-c a" #'org-agenda)
  (keymap-global-set "C-c v" #'vterm-other-window)
  (keymap-global-set "C-c p" sweeprolog-prefix-map)
  (keymap-global-set "C-c S" #'scratch-buffer)
  (keymap-global-set "C-c m" #'esy/emms-map)
  (keymap-global-set "C-c !" #'consult-flymake)
  (keymap-global-set "C-c C" #'esy-publish-create-post)
  (keymap-global-set "C-c O" #'openai-chat)
  (keymap-global-set "C-c E" #'elfeed)
  (keymap-global-set "C-c G" #'gnus)
  (keymap-global-set "C-c M" #'mastodon)
  (keymap-global-set "C-c F" #'esy/find-init-file)
  (keymap-global-set "C-c P" #'list-packages)
  (keymap-global-set "C-c SPC" #'consult-mark)
  (keymap-global-set "C-c 1" #'delete-other-windows)
  (keymap-global-set "<remap> <kill-region>" #'esy/kill-dwim)
  (keymap-global-set "<remap> <goto-line>" #'consult-goto-line)
  (keymap-global-set "<remap> <suspend-frame>" #'zap-up-to-char)
  (keymap-global-set "<remap> <imenu>" #'consult-imenu)
  (keymap-global-set "<remap> <make-frame>" #'duplicate-line)
  (keymap-global-set "M-#" #'dictionary-search)
  (keymap-global-set "M-o" #'previous-buffer)
  (keymap-global-set "M-O" #'next-buffer)
  (keymap-global-set "C-," #'backward-delete-char)
  (keymap-global-set "C-." #'embark-act)
  (keymap-global-set "C-;" #'avy-goto-char-timer)
  (keymap-global-set "C-s-f" #'toggle-frame-fullscreen)
  (keymap-global-set "C-s-l" #'esy/pulse-line)

  (keymap-set ctl-x-map "b" #'consult-buffer)
  (keymap-set ctl-x-4-map "b" #'consult-buffer-other-window)
  (keymap-set ctl-x-5-map "b" #'consult-buffer-other-frame)
  (keymap-set ctl-x-map "r b" #'consult-bookmark)
  (keymap-set ctl-x-map "C-b" #'ibuffer)

  (keymap-set search-map "r" #'rg)
  (keymap-set search-map "l" #'rg-literal)
  (keymap-set search-map "b" #'some-button)

  (keymap-set window-prefix-map "p" #'windmove-swap-states-up)
  (keymap-set window-prefix-map "n" #'windmove-swap-states-down)
  (keymap-set window-prefix-map "a" #'windmove-swap-states-left)
  (keymap-set window-prefix-map "e" #'windmove-swap-states-right)

  (dolist (command '(windmove-swap-states-up
                     windmove-swap-states-down
                     windmove-swap-states-left
                     windmove-swap-states-right))
    (put command 'repeat-map 'window-prefix-map))

  ;; unbind some keys
  (dolist (key '("s-a" "s-d" "s-e" "s-f" "s-g" "s-h" "s-j" "s-k" "s-l"
                 "s-m" "s-o" "s-p" "s-q" "s-s" "s-t" "s-u" "s-w" "s-x"
                 "s-y" "s-z"))
    (keymap-global-unset key))

  ;; enable some commands
  (dolist (command '(set-goal-column
                     narrow-to-region
                     narrow-to-page
                     downcase-region
                     upcase-region))
    (put command 'disabled nil))

  ;; disable some commands
  (put 'suspend-frame 'disabled t))

(esy/init-step project
  "Configure project management commands."
  (with-eval-after-load 'project
    (define-advice project--find-in-directory (:override (dir) no-remote-projects)
      (unless (file-remote-p dir)
        (run-hook-with-args-until-success 'project-find-functions dir)))
    (add-to-list 'project-switch-commands '(project-compile "Compile"))
    (add-to-list 'project-switch-commands '(rg-project "rg"))
    (add-to-list 'project-switch-commands '(magit-project-status "Magit"))
    (add-to-list 'project-switch-commands '(project-shell "Shell"))
    (when (boundp 'project-prefix-map)
      (define-key project-prefix-map "R" #'rg-project)
      (define-key project-prefix-map "m" #'magit-project-status))

    (defvar esy/project-name-history nil)

    (defvar esy/projects-directory "~/checkouts/")

    (defun esy/read-project-by-name ()
      "Read a project name and return its root directory.

If no known project matches the selected name, prompt for a
sub-directory of `esy/projects-directory' using the selected name
as the initial input for completion, and return that directory."
      (let* ((name-dir-alist
              (delete
               nil
               (mapcar (lambda (dir)
                         (when-let ((proj (project-current nil dir)))
                          (cons (project-name proj) dir)))
                       (project-known-project-roots))))
             (current (project-current))
             (default (and current (project-name current)))
             (name (completing-read (format-prompt "Project" default)
                                    name-dir-alist
                                    nil nil nil
                                    'esy/project-name-history
                                    default)))
        (or (alist-get name name-dir-alist nil nil #'string=)
            (let* ((dir (read-directory-name "Project root directory: "
                                             esy/projects-directory
                                             nil t name))
                   (project (project-current nil dir)))
              (when project (project-remember-project project))
              dir))))))

(esy/init-step sql
  "Configure SQL connections."
  (with-eval-after-load 'sql
    (setq sql-connection-alist (let* ((a (auth-source-search :port 5432
                                                             :max  7
                                                             :require '(:user :port :secret :host)))
                                      (d (nth 0 a))
                                      (p (nth 1 a))
                                      (c (nth 2 a))
                                      (e (nth 3 a))
                                      (f (nth 4 a))
                                      (i (nth 5 a))
                                      (g (nth 6 a)))
                                 `((dev
                                    (sql-product 'postgres)
                                    (sql-user ,(plist-get d :user))
                                    (sql-port 5432)
                                    (sql-password ,(funcall (plist-get d :secret)))
                                    (sql-server ,(plist-get d :host))
                                    (sql-database "alerts"))
                                   (prod
                                    (sql-product 'postgres)
                                    (sql-user ,(plist-get p :user))
                                    (sql-port 5432)
                                    (sql-password ,(funcall (plist-get p :secret)))
                                    (sql-server ,(plist-get p :host))
                                    (sql-database "alerts"))
                                   (cgs
                                    (sql-product 'postgres)
                                    (sql-user ,(plist-get c :user))
                                    (sql-port 5432)
                                    (sql-password ,(funcall (plist-get c :secret)))
                                    (sql-server ,(plist-get c :host))
                                    (sql-database "container_graph"))
                                   (ten
                                    (sql-product 'postgres)
                                    (sql-user ,(plist-get e :user))
                                    (sql-port 5432)
                                    (sql-password ,(funcall (plist-get e :secret)))
                                    (sql-server ,(cadr (split-string (plist-get e :host)  (rx "^"))))
                                    (sql-database ,(car (split-string (plist-get e :host) (rx "^")))))
                                   (ac
                                    (sql-product 'postgres)
                                    (sql-user ,(plist-get f :user))
                                    (sql-port 5432)
                                    (sql-password ,(funcall (plist-get f :secret)))
                                    (sql-server ,(cadr (split-string (plist-get f :host)  (rx "^"))))
                                    (sql-database ,(car (split-string (plist-get f :host) (rx "^")))))
                                   (int
                                    (sql-product 'postgres)
                                    (sql-user ,(plist-get i :user))
                                    (sql-port 5432)
                                    (sql-password ,(funcall (plist-get i :secret)))
                                    (sql-server ,(cadr (split-string (plist-get i :host)  (rx "^"))))
                                    (sql-database ,(car (split-string (plist-get i :host) (rx "^")))))
                                   (fin
                                    (sql-product 'postgres)
                                    (sql-user ,(plist-get g :user))
                                    (sql-port 5432)
                                    (sql-password ,(funcall (plist-get g :secret)))
                                    (sql-server ,(cadr (split-string (plist-get g :host)  (rx "^"))))
                                    (sql-database ,(car (split-string (plist-get g :host) (rx "^"))))))))
    (add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
    (define-key sql-mode-map (kbd "C-c C-f") #'sqlformat)
    (define-advice sql-comint-postgres (:around (fun &rest args) use-sql-password)
      (let ((process-environment
             (nconc
              (list (format "PGPASSWORD=%s" sql-password))
              process-environment)))
        (apply fun args)))))

(esy/init-step org
  "Configure Org mode."
  (with-eval-after-load 'org
    (keymap-unset org-mode-map "C-," t)
    (esy-publish-setup))

  (with-eval-after-load 'org-agenda
    (add-to-list 'org-agenda-custom-commands
                 '("w" "Work TODOs" tags-todo "+work"))
    (add-to-list 'org-agenda-custom-commands
                 '("h" "Holland TODOs" tags-todo "+holland"))))

(esy/init-step email
  "Configure email via `gnus'."
  (with-eval-after-load 'gnus
    (require 'gnus-icalendar)
    (add-hook 'gnus-group-mode-hook #'gnus-topic-mode)
    (gnus-icalendar-setup)
    (gnus-icalendar-org-setup)
    (all-the-icons-gnus-setup)
    (bbdb-initialize 'gnus 'mail 'message)))

(esy/init-step dired
  "Configure `dired'."
  (defun esy/local-all-the-icons-dired-mode ()
    (unless (file-remote-p default-directory)
      (all-the-icons-dired-mode)))

  (with-eval-after-load 'dired
    (put 'dired-find-alternate-file 'disabled nil)
    (add-hook 'dired-mode-hook #'esy/local-all-the-icons-dired-mode)))

(esy/init-step tramp
  "Configure remote access via `tramp'."
  (with-eval-after-load 'tramp
    (tramp-set-completion-function "ssh" '((tramp-parse-netrc "~/.authinfo.gpg")))
    (add-to-list 'backup-directory-alist (cons tramp-file-name-regexp nil))
    (define-advice completion-file-name-table (:filter-args (args) no-remote-file-name-completion)
      (let ((string (car args))
            (pred (cadr args))
            (action (caddr args)))
        (if (and (file-remote-p string) (eq pred #'file-exists-p))
            (list string nil action)
          (list string pred action))))))

(esy/init-step proced
  "Configure `proced'."
  (with-eval-after-load 'proced
    (add-hook 'proced-mode-hook (lambda ()
                                  (setq proced-auto-update-flag t)))))

(esy/init-step time
  "Configure `world-clock'."
  (with-eval-after-load 'time
    (add-to-list 'zoneinfo-style-world-list '("Europe/Amsterdam" "Amsterdam"))))

(esy/init-step flyspell
  "Configure spelling errors correction via `flyspell'."
  (with-eval-after-load 'flyspell
    (keymap-unset flyspell-mode-map "C-," t)
    (keymap-unset flyspell-mode-map "C-." t)
    (keymap-unset flyspell-mode-map "C-;" t)
    (keymap-unset flyspell-mode-map "C-M-i" t)))

(esy/init-step minibuffer-completion
  "Configure minibuffer completions."

  (defvar esy/completing-read-commands nil)

  (add-to-list 'savehist-additional-variables
               'esy/completing-read-commands)

  (define-advice completing-read (:before (&rest _) record-command)
    (cl-incf (alist-get this-command esy/completing-read-commands 0)))

  (add-to-list 'display-buffer-alist
               '("\\*Completions\\*"
                 (display-buffer-reuse-window display-buffer-at-bottom)
                 (window-parameters (mode-line-format . none))))

  (add-hook 'completion-list-mode-hook
            (lambda ()
              (setq-local cursor-in-non-selected-windows nil)))

  (dolist (key-binding '(("C-p" . minibuffer-previous-completion)
                         ("C-n" . minibuffer-next-completion)
                         ("M-j" . minibuffer-force-complete-and-exit)))
    (keymap-set minibuffer-local-completion-map
                (car key-binding)
                (cdr key-binding)))

  (with-eval-after-load 'marginalia
    (add-hook 'marginalia-mode-hook
              #'all-the-icons-completion-marginalia-setup))

  (with-eval-after-load 'consult
    (with-eval-after-load 'embark
      (require 'embark-consult))))

(esy/init-step capf
  "Set up some global `completion-at-point-functions'."
  (defun esy/dabbrev-capf ()
    "Workaround for issue with `dabbrev-capf'."
    (require 'dabbrev)
    (dabbrev--reset-global-variables)
    (setq dabbrev-case-fold-search nil)
    (dabbrev-capf))

  (add-to-list 'completion-at-point-functions #'esy/dabbrev-capf)

  (defun esy/file-capf ()
    "File completion at point function."
    (let ((bs (bounds-of-thing-at-point 'filename)))
      (when bs
        (let* ((start (car bs))
               (end   (cdr bs)))
          `(,start ,end completion--file-name-table . (:exclusive no))))))

  (add-to-list 'completion-at-point-functions #'esy/file-capf))

(esy/init-step lin
  "Configure highlighting of the current line via `lin'."
  (with-eval-after-load 'lin
    (add-to-list 'lin-mode-hooks 'gnus-summary-mode-hook)
    (add-to-list 'lin-mode-hooks 'gnus-group-mode-hook)
    (add-to-list 'lin-mode-hooks 'gnus-server-mode-hook)))

(esy/init-step icons
  "Configure icons via `all-the-icons'."
  (with-eval-after-load 'all-the-icons
    (add-to-list 'all-the-icons-extension-icon-alist
                 '("pl" all-the-icons-alltheicon "prolog"
                   :height 1.1 :face all-the-icons-lmaroon))))

(esy/init-step corfu
  "Configure `completion-in-region' UI with `corfu'."
  (let ((original-completion-in-region-function completion-in-region-function))
   (defun esy/setup-corfu (&rest args)
     (setq completion-in-region-function original-completion-in-region-function)
     (global-corfu-mode)
     (apply #'completion-in-region args)))
  (setq completion-in-region-function #'esy/setup-corfu)
  (with-eval-after-load 'corfu
    (defun esy/margin-formatter (metadata)
      "Format METADATA for `corfu-margin-formatters'."
      (pcase (cdr (assoc 'category metadata))
        ('file (lambda (string)
                 (concat (if (string-suffix-p "/" string)
                             (all-the-icons-icon-for-dir string)
                           (all-the-icons-icon-for-file string))
                         " ")))
        ('dabbrev (lambda (_) "… "))))
    (add-to-list 'corfu-margin-formatters #'esy/margin-formatter)
    (corfu-indexed-mode)))

(esy/init-step minor-modes
  "Enable some global minor modes."
  (dolist (mode '(
                  column-number-mode
                  context-menu-mode
                  display-time-mode
                  display-battery-mode
                  global-auto-revert-mode
                  global-diff-hl-mode
                  global-whitespace-cleanup-mode
                  lin-global-mode
                  marginalia-mode
                  minibuffer-depth-indicate-mode
                  mouse-avoidance-mode
                  pixel-scroll-precision-mode
                  recentf-mode
                  repeat-mode
                  save-place-mode
                  savehist-mode
                  show-paren-mode
                  transient-mark-mode
                  ))
    (funcall mode)))

(esy/init-step emms
  "Set up EMMS."
  (dolist (command '(emms-start
                     emms-stop
                     emms-next
                     emms-previous
                     emms-pause
                     emms-seek
                     emms-seek-to
                     emms-seek-forward
                     emms-seek-backward
                     emms-show))
    (autoload command "emms"))

  (defvar-keymap esy/emms-map
    :doc "My keymap for EMMS commands."
    :prefix 'esy/emms-map
    "N" #'emms-next
    "P" #'emms-previous
    "S" #'emms-stop
    "b" #'emms-seek-backward
    "f" #'emms-seek-forward
    "k" #'emms-seek
    "m" #'emms-show
    "p" #'emms-pause
    "s" #'emms-start
    "t" #'emms-seek-to)

  (with-eval-after-load 'emms
    (emms-minimalistic)))

(esy/init-step pdf
  "Configure PDF handling."
  (with-eval-after-load 'pdf-view
    (require 'pdf-tools))
  (with-eval-after-load 'pdf-tools
    (pdf-tools-install :no-query)
    (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
  (add-to-list 'revert-without-query "\\.pdf\\'"))

(esy/init-step tex
  "Configure TeX via `auctex'."
  (with-eval-after-load 'tex
    (add-hook 'TeX-after-compilation-finished-functions
              #'TeX-revert-document-buffer)))

(esy/init-step vterm
  "Configure terminal emulation via `vterm'."
  (with-eval-after-load 'vterm
    (add-to-list 'vterm-tramp-shells '("kubernetes" "/bin/bash"))))

(esy/init-step paredit
  "Remove some over-intrusive keybindings in `paredit'."
  (with-eval-after-load 'paredit
    (keymap-unset paredit-mode-map "M-s" t)
    (keymap-unset paredit-mode-map "M-?" t)))

(esy/init-step sweep
  "Configure Prolog integration via `sweeprolog'."
  (add-to-list 'major-mode-remap-alist '(prolog-mode . sweeprolog-mode))
  (with-eval-after-load 'sweeprolog
    (add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
    (add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode)))

(esy/init-step rg
  "Configure recursive grepping via `rg'."
  (with-eval-after-load 'rg
    (add-to-list 'rg-custom-type-aliases '("Prolog" . "*.pl *.plt *.pro *.prolog"))))

(esy/init-step terraform
  "Configure custom faces in `terraform-mode'."
  (with-eval-after-load 'terraform-mode
    (setq terraform--resource-name-face font-lock-function-name-face
          terraform--resource-type-face font-lock-type-face)))

(esy/init-step auto-mode
  "Associate major modes with files based on their extensions."
  (dolist (cell '(("Dockerfile"  . dockerfile-ts-mode)
                  ("\\.ya?ml\\'" . yaml-ts-mode)
                  ("\\.toml\\'"  . toml-ts-mode)
                  ("\\.json\\'"  . json-ts-mode)
                  ("\\.tfstate\\'"  . json-ts-mode)
                  ("\\.ts\\'"    . typescript-ts-mode)
                  ("\\.rb\\'"    . ruby-ts-mode)
                  ("\\.rs\\'"    . rust-ts-mode)
                  ("\\.go\\'"    . go-ts-mode)
                  ("\\.pdf\\'"   . pdf-view-mode)
                  ("\\.plt?\\'"  . prolog-mode)))
    (push cell auto-mode-alist)))

(esy/init-step kill-hook
  "Compile this file (if changed) when Emacs is killed."
  (defun esy/compile-config ()
    (interactive)
    (when (file-newer-than-file-p user-init-file
                                  (byte-compile-dest-file user-init-file))
      (byte-compile-file user-init-file))
    (when (file-newer-than-file-p early-init-file
                                  (byte-compile-dest-file early-init-file))
      (byte-compile-file early-init-file)))

  (add-hook 'kill-emacs-hook #'esy/compile-config 10))

(esy/init-step help
  "Configure Help."
  (with-eval-after-load 'help-fns
    (with-eval-after-load 'shortdoc
      (add-hook 'help-fns-describe-function-functions
                #'shortdoc-help-fns-examples-function))))

(esy/init-step dict
  "Configure dictionary."
  (with-eval-after-load 'dictionary
    (setopt dictionary-search-interface   'help
            dictionary-default-dictionary "gcide"
            dictionary-default-strategy   "prefix"
            dictionary-server             "dict.org")))

(esy/init-step elfeed
  "Configure news feeds."
  (with-eval-after-load 'elfeed
    (setq
     ;; don't use bold face for unread Elfeed entries
     elfeed-search-face-alist '((unread (default)))
     ;; read feeds list from a separate file
     elfeed-feeds
     (with-temp-buffer
       (insert-file-contents-literally
        (locate-user-emacs-file "feeds.eld"))
       (goto-char (point-min))
       (read (current-buffer))))))

(dolist (step (sort esy/init-step-times (lambda (l r) (time-less-p (cdr r) (cdr l)))))
  (message "%f %s"
           (float-time (cdr step))
           (car step)))

(provide 'init)
;;; init.el ends here