[emacs][wip] enable Email
[~bandali/configs] / init.org
index f58e843..16351aa 100644 (file)
--- a/init.org
+++ b/init.org
@@ -54,6 +54,13 @@ make build
 - [[#header][Header]]
 - [[#initial-setup][Initial setup]]
 - [[#core][Core]]
+- [[#borg-essentials][Borg's =layer/essentials=]]
+- [[#editing][Editing]]
+- [[#syntax-spell-checking][Syntax and spell checking]]
+- [[#programming-modes][Programming modes]]
+- [[#emacs-enhancements][Emacs enhancements]]
+- [[#email][Email]]
+- [[#blogging][Blogging]]
 - [[#post-initialization][Post initialization]]
 - [[#footer][Footer]]
 
@@ -65,7 +72,7 @@ make build
 ** First line
 
 #+begin_src emacs-lisp :comments none
-;;; init.el --- Amin Bandali's Emacs config -*- lexical-binding: t ; eval: (view-mode 1)-*-
+;;; init.el --- Amin Bandali's Emacs config -*- lexical-binding: t; eval: (view-mode 1) -*-
 #+end_src
 
 Enable =view-mode=, which both makes the file read-only (as a reminder
@@ -97,38 +104,17 @@ file.
 #+begin_src emacs-lisp :comments none
 ;;; Commentary:
 
-;; Emacs configuration of Amin Bandali, computer scientist and functional
-;; programmer.
+;; Emacs configuration of Amin Bandali, computer scientist, functional
+;; programmer, and free software advocate.
 
 ;; THIS FILE IS AUTO-GENERATED FROM `init.org'.
 #+end_src
 
-** Naming conventions
-
-The conventions below were inspired by [[https://github.com/hlissner/doom-emacs][Doom]]'s, found [[https://github.com/hlissner/doom-emacs/blob/5dacbb7cb1c6ac246a9ccd15e6c4290def67757c/core/core.el#L3-L17][here]].
-
-#+begin_src emacs-lisp :comments none
-;; Naming conventions:
-;;
-;;   amin-...   public variables or non-interactive functions
-;;   amin--...  private anything (non-interactive), not safe for direct use
-;;   amin/...   an interactive function; safe for M-x or keybinding
-;;   amin:...   an evil operator, motion, or command
-;;   amin|...   a hook function
-;;   amin*...   an advising function
-;;   amin@...   a hydra command
-;;   ...!       a macro
-#+end_src
-
 * Initial setup
 :PROPERTIES:
 :CUSTOM_ID: initial-setup
 :END:
 
-#+begin_src emacs-lisp :comments none
-;;; Code:
-#+end_src
-
 ** Emacs initialization
 
 I'd like to do a couple of measurements of Emacs' startup time. First,
@@ -136,10 +122,10 @@ let's see how long Emacs takes to start up, before even loading
 =init.el=, i.e. =user-init-file=:
 
 #+begin_src emacs-lisp
-(defvar amin--before-user-init-time (current-time)
+(defvar a/before-user-init-time (current-time)
   "Value of `current-time' when Emacs begins loading `user-init-file'.")
 (message "Loading Emacs...done (%.3fs)"
-         (float-time (time-subtract amin--before-user-init-time
+         (float-time (time-subtract a/before-user-init-time
                                     before-init-time)))
 #+end_src
 
@@ -149,9 +135,9 @@ frequency. Clearing the ~file-name-handler-alist~ seems to help reduce
 startup time as well.
 
 #+begin_src emacs-lisp
-(defvar amin--gc-cons-threshold gc-cons-threshold)
-(defvar amin--gc-cons-percentage gc-cons-percentage)
-(defvar amin--file-name-handler-alist file-name-handler-alist)
+(defvar a/gc-cons-threshold gc-cons-threshold)
+(defvar a/gc-cons-percentage gc-cons-percentage)
+(defvar a/file-name-handler-alist file-name-handler-alist)
 (setq gc-cons-threshold (* 400 1024 1024)  ; 400 MiB
       gc-cons-percentage 0.6
       file-name-handler-alist nil
@@ -166,9 +152,9 @@ done initializing.
 (add-hook
  'after-init-hook
  (lambda ()
-   (setq gc-cons-threshold amin--gc-cons-threshold
-         gc-cons-percentage amin--gc-cons-percentage
-         file-name-handler-alist amin--file-name-handler-alist)))
+   (setq gc-cons-threshold a/gc-cons-threshold
+         gc-cons-percentage a/gc-cons-percentage
+         file-name-handler-alist a/file-name-handler-alist)))
 #+end_src
 
 Increase the number of lines kept in message logs (the =*Messages*=
@@ -191,16 +177,16 @@ but for now I've decided to keep them enabled. See documentation for
 
 #+begin_src emacs-lisp
 (setq user-full-name "Amin Bandali"
-      user-mail-address "amin@bandali.me")
+      user-mail-address "amin@aminb.org")
 #+end_src
 
 ** Package management
 
 *** No =package.el=
 
-I can do all my package management things with Borg, and don't need
-Emacs' built-in =package.el=. Emacs 27 lets us disable =package.el= in
-the =early-init-file= (see [[https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=24acb31c04b4048b85311d794e600ecd7ce60d3b][here]]).
+I can do all my package management things with =straight.el=, and
+don't need Emacs' built-in =package.el=.  Emacs 27 lets us disable
+=package.el= in the =early-init-file= (see [[https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=24acb31c04b4048b85311d794e600ecd7ce60d3b][here]]).
 
 #+begin_src emacs-lisp :tangle early-init.el
 (setq package-enable-at-startup nil)
@@ -211,12 +197,56 @@ right now), and even when released it'll be long before most distros
 ship in their repos, I'll still put the old workaround with the
 commented call to ~package-initialize~ here anyway.
 
-#+begin_src emacs-lisp
+#+begin_src emacs-lisp :tangle no
 (setq package-enable-at-startup nil)
 ;; (package-initialize)
 #+end_src
 
-*** Borg
+Update: the above is not necessary, since =straight.el= automatically
+does that (and more).  See =straight-package-neutering-mode=.
+
+*** =straight.el=
+
+#+begin_quote
+Next-generation, purely functional package manager for the Emacs
+hacker.
+#+end_quote
+
+=straight.el= allows me to have a fully reproducible Emacs setup.
+
+#+begin_src emacs-lisp
+(setq straight-repository-branch "develop")
+
+(defvar bootstrap-version)
+(let ((bootstrap-file
+       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
+      (bootstrap-version 5))
+  (unless (file-exists-p bootstrap-file)
+    (with-current-buffer
+        (url-retrieve-synchronously
+         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
+         'silent 'inhibit-cookies)
+      (goto-char (point-max))
+      (eval-print-last-sexp)))
+  (load bootstrap-file nil 'nomessage))
+
+(setq straight-use-package-by-default t)
+#+end_src
+
+Since we enable =straight.el='s =straight-use-package-by-default=
+integration, we will define a =use-feature= for plain ole
+=use-package= without any of the =straight.el= stuff.
+
+#+begin_src emacs-lisp
+(defmacro use-feature (name &rest args)
+  "Like `use-package', but with `straight-use-package-by-default' disabled."
+  (declare (indent defun))
+  `(use-package ,name
+     :straight nil
+,@args))
+#+end_src
+
+*** COMMENT Borg
 
 #+begin_quote
 Assimilate Emacs packages as Git submodules
@@ -244,7 +274,6 @@ can then be managed with the help of Magit or other tools.
    ("C-c b a" . borg-assimilate)
    ("C-c b b" . borg-build)
    ("C-c b c" . borg-clone)
-   ("C-c b m" . borg-insert-update-message)
    ("C-c b r" . borg-remove)))
 #+end_src
 
@@ -259,7 +288,7 @@ packages (in our case especially the latter) in a neatly organized way
 and without compromising on performance.
 
 #+begin_src emacs-lisp
-(require 'use-package)
+(straight-use-package 'use-package)
 (if nil  ; set to t when need to debug init
     (setq use-package-verbose t
           use-package-expand-minimally nil
@@ -269,7 +298,7 @@ and without compromising on performance.
         use-package-expand-minimally t))
 #+end_src
 
-*** Epkg
+*** COMMENT Epkg
 
 #+begin_quote
 Browse the Emacsmirror package database
@@ -285,7 +314,12 @@ database, low-level functions for querying the database, and a
   :bind
   (("C-c b d" . epkg-describe-package)
    ("C-c b p" . epkg-list-packages)
-   ("C-c b u" . epkg-update)))
+   ("C-c b u" . epkg-update))
+  :config
+  (eval-when-compile (defvar ivy-initial-inputs-alist))
+  (with-eval-after-load 'ivy
+    (add-to-list
+     'ivy-initial-inputs-alist '(epkg-describe-package . "^") t)))
 #+end_src
 
 ** No littering in =~/.emacs.d=
@@ -316,7 +350,7 @@ definitely don't want it mixing with =init.el=. So, here; let's give
 it it's own file. While at it, treat themes as safe.
 
 #+begin_src emacs-lisp
-(use-package custom
+(use-feature custom
   :no-require t
   :config
   (setq custom-file (no-littering-expand-etc-file-name "custom.el"))
@@ -327,8 +361,11 @@ it it's own file. While at it, treat themes as safe.
 
 ** Secrets file
 
+Load the secrets file if it exists, otherwise show a warning.
+
 #+begin_src emacs-lisp
-(load (no-littering-expand-etc-file-name "secrets"))
+(with-demoted-errors
+    (load (no-littering-expand-etc-file-name "secrets")))
 #+end_src
 
 ** Better =$PATH= handling
@@ -367,7 +404,7 @@ login; so starting the server from inside Emacs is good enough for me.
 See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html#Emacs-Server][Using Emacs as a Server]].
 
 #+begin_src emacs-lisp
-(use-package server
+(use-feature server
   :defer 1
   :config (or (server-running-p) (server-mode)))
 #+end_src
@@ -377,7 +414,7 @@ See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.htm
 Font stack with better unicode support, around =Ubuntu Mono= and
 =Hack=.
 
-#+begin_src emacs-lisp :tangle no
+#+begin_src emacs-lisp
 (dolist (ft (fontset-list))
   (set-fontset-font
    ft
@@ -416,57 +453,62 @@ Font stack with better unicode support, around =Ubuntu Mono= and
 (setq text-scale-mode-step 1.05)
 #+end_src
 
-** Libraries
+** Focus follows mouse
+
+I’d like focus to follow the mouse when I move the cursor from one
+window to the next.
 
 #+begin_src emacs-lisp
-(require 'cl-lib)
-(require 'subr-x)
+(setq mouse-autoselect-window t)
 #+end_src
 
-** Useful utilities
+Let’s define a function to conveniently disable this for certain
+buffers and/or modes.
 
 #+begin_src emacs-lisp
-(defun amin-enlist (exp)
-  "Return EXP wrapped in a list, or as-is if already a list."
-(if (listp exp) exp (list exp)))
+(defun a/no-mouse-autoselect-window ()
+  (make-local-variable 'mouse-autoselect-window)
+  (setq mouse-autoselect-window nil))
+#+end_src
 
-; from https://github.com/hlissner/doom-emacs/commit/589108fdb270f24a98ba6209f6955fe41530b3ef
-(defmacro after! (features &rest body)
-  "A smart wrapper around `with-eval-after-load'. Supresses warnings during
-compilation."
-  (declare (indent defun) (debug t))
-  (list (if (or (not (bound-and-true-p byte-compile-current-file))
-                (dolist (next (amin-enlist features))
-                  (if (symbolp next)
-                      (require next nil :no-error)
-                    (load next :no-message :no-error))))
-            #'progn
-          #'with-no-warnings)
-        (cond ((symbolp features)
-               `(eval-after-load ',features '(progn ,@body)))
-              ((and (consp features)
-                    (memq (car features) '(:or :any)))
-               `(progn
-                  ,@(cl-loop for next in (cdr features)
-                             collect `(after! ,next ,@body))))
-              ((and (consp features)
-                    (memq (car features) '(:and :all)))
-               (dolist (next (cdr features))
-                 (setq body `(after! ,next ,@body)))
-               body)
-              ((listp features)
-               `(after! (:all ,@features) ,@body)))))
+** Libraries
+
+#+begin_src emacs-lisp
+(require 'cl-lib)
+(require 'subr-x)
 #+end_src
 
+** Useful utilities
+
 Convenience macro for =setq='ing multiple variables to the same value:
 
 #+begin_src emacs-lisp
-(defmacro setq-every! (value &rest vars)
+(defmacro a/setq-every (value &rest vars)
   "Set all the variables from VARS to value VALUE."
   (declare (indent defun) (debug t))
   `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars)))
 #+end_src
 
+The following process-related stuff from [[https://github.com/alezost/emacs-config][alezost's emacs-config]].
+
+#+begin_src emacs-lisp
+(defun a/start-process (program &rest args)
+  "Same as `start-process', but doesn't bother about name and buffer."
+  (let ((process-name (concat program "_process"))
+        (buffer-name  (generate-new-buffer-name
+                       (concat program "_output"))))
+    (apply #'start-process
+           process-name buffer-name program args)))
+
+(defun a/dired-start-process (program &optional args)
+  "Open current file with a PROGRAM."
+  ;; Shell command looks like this: "program [ARGS]... FILE" (ARGS can
+  ;; be nil, so remove it).
+  (apply #'a/start-process
+         program
+         (remove nil (list args (dired-get-file-for-visit)))))
+#+end_src
+
 * Core
 :PROPERTIES:
 :CUSTOM_ID: core
@@ -631,18 +673,30 @@ Enable =winner-mode=.
 (winner-mode 1)
 #+end_src
 
-*** Close =*compilation*= on success
+*** Don’t display =*compilation*= on success
+
+Based on https://stackoverflow.com/a/17788551, with changes to use
+=cl-letf= instead of the now obsolete =flet=.
 
 #+begin_src emacs-lisp
-(setq compilation-exit-message-function
-      (lambda (status code msg)
-        "Close the compilation window if successful."
-        ;; if M-x compile exits with 0
-         (when (and (eq status 'exit) (zerop code))
-           (bury-buffer)
-           (delete-window (get-buffer-window (get-buffer "*compilation*"))))
-         ;; return the result of compilation-exit-message-function
-         (cons msg code)))
+(with-eval-after-load 'compile
+  (defun a/compilation-finish-function (buffer outstr)
+    (unless (string-match "finished" outstr)
+      (switch-to-buffer-other-window buffer))
+    t)
+
+  (setq compilation-finish-functions #'a/compilation-finish-function)
+
+  (require 'cl-macs)
+
+  (defadvice compilation-start
+      (around inhibit-display
+              (command &optional mode name-function highlight-regexp))
+    (if (not (string-match "^\\(find\\|grep\\)" command))
+        (cl-letf (((symbol-function 'display-buffer) #'ignore))
+          (save-window-excursion ad-do-it))
+      ad-do-it))
+  (ad-activate 'compilation-start))
 #+end_src
 
 *** Search for non-ASCII characters
@@ -658,12 +712,39 @@ for this.
 ;; (setq replace-char-fold t)
 #+end_src
 
+*** Cursor shape
+
+#+begin_src emacs-lisp
+(setq-default cursor-type 'bar)
+#+end_src
+
+*** Allow scrolling in Isearch
+
+#+begin_src emacs-lisp
+(setq isearch-allow-scroll t)
+#+end_src
+
 ** Bindings
 
 #+begin_src emacs-lisp
 (bind-keys
- ("s-c e b" . eval-buffer)
- ("s-c e r" . eval-region)
+ ("C-c a i" . ielm)
+
+ ("C-c e b" . eval-buffer)
+ ("C-c e r" . eval-region)
+
+ ("C-c F m" . make-frame-command)
+ ("C-c F d" . delete-frame)
+ ("C-c F D" . delete-other-frames)
+
+ ("C-c o"   . other-window)
+
+ ("C-c Q"   . save-buffers-kill-terminal)
+
+ ("C-S-h C" . describe-char)
+ ("C-S-h F" . describe-face)
+
+ ("C-x K"   . kill-this-buffer)
 
  ("s-p"     . beginning-of-buffer)
  ("s-n"     . end-of-buffer))
@@ -693,195 +774,73 @@ customizing it.
             'auto-compile-inhibit-compile-detached-git-head))
 #+end_src
 
-*** [[https://github.com/noctuid/general.el][general]]
+*** [[https://orgmode.org/][Org]]
 
-#+begin_src emacs-lisp
-(use-package general
-  :demand t
-  :config
-  (general-evil-setup t)
-  (general-override-mode)
+#+begin_quote
+Org mode is for keeping notes, maintaining TODO lists, planning
+projects, and authoring documents with a fast and effective plain-text
+system.
+#+end_quote
 
-  (general-create-definer
-   amin--leader-keys
-   :keymaps 'override
-   :states '(emacs normal visual motion insert)
-   :non-normal-prefix "M-m"
-   :prefix "SPC"))
-#+end_src
+In short, my favourite way of life.
 
-*** [[https://github.com/emacs-evil/evil][evil]]
+First, we have to resort to a [[https://github.com/raxod502/straight.el#installing-org-with-straightel][hack]] to be able to use the correct
+latest version of Org from upstream.
 
 #+begin_src emacs-lisp
-(use-package evil
-  :demand t
-  ;; :hook (org-src-mode . evil-motion-state)
-  :config
-  (evil-mode 1)
-  (general-swap-key nil '(normal motion) ";" ":")
-
-  (setq evil-want-visual-char-semi-exclusive t
-        evil-cross-lines t)
-
-  ;; custom mode state mappings
-  (dolist (mspair '((ebdb-mode    . emacs)
-                    (helpful-mode . motion)
-                    (view-mode    . motion)))
-    (evil-set-initial-state (car mspair) (cdr mspair)))
-
-  ;; fix tab and indentation in src blocks inside org-mode buffer
-  ;; also see https://git.sr.ht/~bandali/dotfiles/commit/0e2ffd584aafdd4cf256bcdf2473f01c3aaaed55
-  (unbind-key "TAB" evil-motion-state-map)
-
-  (unbind-key "C-d" evil-insert-state-map)
-  (unbind-key "C-v" evil-insert-state-map)
-  (unbind-key "C-y" evil-insert-state-map)
-  (unbind-key "C-a" evil-insert-state-map)
-  (unbind-key "C-e" evil-insert-state-map)
-  (unbind-key "C-p" evil-insert-state-map)
-  (unbind-key "C-n" evil-insert-state-map)
-  (unbind-key "C-k" evil-insert-state-map)
-  (bind-keys
-    :map evil-insert-state-map
-    ("C-k"   . kill-line)
-    ("C-S-k" . evil-insert-digraph)
-    :map evil-motion-state-map
-    ([down-mouse-1] . nil)))
-#+end_src
+  (use-package git)
 
-#+begin_src emacs-lisp
-(use-package evil-escape
-  :after evil
-  :init
-  (setq evil-escape-excluded-states '(normal visual multiedit emacs motion)
-        evil-escape-excluded-major-modes '(neotree-mode)
-        evil-escape-key-sequence "jk"
-        evil-escape-delay 0.25)
-  ;; :general
-  ;; (:states '(insert replace visual operator)
-  ;;          "C-g" #'evil-escape)
-  :config
-  (evil-escape-mode 1)
-  ;; no `evil-escape' in minibuffer
-  (push #'minibufferp evil-escape-inhibit-functions))
-#+end_src
+  (defun org-git-version ()
+    "The Git version of org-mode.
+  Inserted by installing org-mode or when a release is made."
+    (require 'git)
+    (let ((git-repo (expand-file-name
+                     "straight/repos/org/" user-emacs-directory)))
+      (string-trim
+       (git-run "describe"
+                "--match=release\*"
+                "--abbrev=6"
+                "HEAD"))))
 
-#+begin_src emacs-lisp
-(use-package evil-nerd-commenter
-  :after evil
-  :general
-  (nmap
-    "gc" 'evilnc-comment-operator
-    "gy" 'evilnc-copy-and-comment-lines))
-#+end_src
+  (defun org-release ()
+    "The release version of org-mode.
+  Inserted by installing org-mode or when a release is made."
+    (require 'git)
+    (let ((git-repo (expand-file-name
+                     "straight/repos/org/" user-emacs-directory)))
+      (string-trim
+       (string-remove-prefix
+        "release_"
+        (git-run "describe"
+                 "--match=release\*"
+                 "--abbrev=0"
+                 "HEAD")))))
 
-#+begin_src emacs-lisp
-(use-package evil-surround
-  :after evil
-  :general
-  (omap
-    "s" 'evil-surround-edit
-    "S" 'evil-Surround-edit)
-  (vmap
-    "S" 'evil-surround-region
-    "gS" 'evil-Surround-region))
+(provide 'org-version)
 #+end_src
 
-#+begin_src emacs-lisp
-(amin--leader-keys
-  "/"   '(:ignore t :wk "search")
-
-  "a"   '(:ignore t :wk "apps")
-  "a i" 'ielm
-
-  "b"   '(:ignore t :wk "buffers")
-  "b k" 'kill-this-buffer
-  "b s" 'save-buffer
-
-  "e"   '(:ignore t :wk "eval")
-  "e b" 'eval-buffer
-  "e r" 'eval-region
-
-  "f"   '(:ignore t :wk "files")
-
-  "F"   '(:ignore t :wk "frames")
-  "F m" 'make-frame-command
-  "F d" 'delete-frame
-  "F D" 'delete-other-frames
+We will use the =org-plus-contrib= package to get the whole deal:
 
-  "h"   '(:ignore t :wk "help(ful)")
-  "h c" 'describe-char
-  "h f" 'describe-function
-  "h F" 'describe-face
-  "h H" 'view-hello-file
-  "h i" 'info
-  "h k" 'describe-key
-  "h l" 'view-lossage
-  "h v" 'describe-variable
-
-  "o"   'other-window
-
-  "w"   '(:ignore t :wk "window")
-  "w o" 'other-window
-  "w 0" 'delete-window
-  "w 1" 'delete-other-windows
-  "w 2" 'split-window-below
-  "w 3" 'split-window-right
-  "w u" 'winner-undo
-  "w r" 'winner-redo
-
-  "q"   '(:ignore t :wk "quit")
-  "q q" 'save-buffers-kill-terminal)
+#+begin_src emacs-lisp
+(straight-use-package 'org-plus-contrib)
 #+end_src
 
-*** [[https://orgmode.org/][Org mode]]
-
-#+begin_quote
-Org mode is for keeping notes, maintaining TODO lists, planning
-projects, and authoring documents with a fast and effective plain-text
-system.
-#+end_quote
-
-In short, my favourite way of life.
+And here's where my actual Org configurations begin:
 
 #+begin_src emacs-lisp
-(use-package org
-  :defer 3
-  :general
-  (amin--leader-keys
-    :states 'normal
-    :keymaps 'org-mode-map
-    "'" 'org-edit-special)
-  (amin--leader-keys
-    :definer 'minor-mode
-    :states 'normal
-    :keymaps 'org-src-mode
-    "'" 'org-edit-src-exit
-    "k" 'org-edit-src-abort)
-  (general-define-key
-   :definer 'minor-mode
-   :states 'normal
-   :keymaps 'org-src-mode
-   "q" 'org-edit-src-exit)
+(use-feature org
+  :defer 0.5
   :config
   (setq org-src-tab-acts-natively t
         org-src-preserve-indentation nil
         org-edit-src-content-indentation 0
         org-email-link-description-format "Email %c: %s" ; %.30s
         org-highlight-latex-and-related '(entities)
+        org-use-speed-commands t
+        org-startup-folded 'content
+        org-catch-invisible-edits 'show-and-error
         org-log-done 'time)
   (add-to-list 'org-structure-template-alist '("L" . "src emacs-lisp") t)
-  (after! org-src
-    (define-key org-src-mode-map [remap evil-write] 'org-edit-src-save)
-    (define-key org-src-mode-map [remap evil-save-and-close]
-      (lambda () (interactive)
-        (org-edit-src-save)
-        (org-edit-src-exit)))
-    (define-key org-src-mode-map [remap evil-save-modified-and-close]
-      (lambda () (interactive)
-        (org-edit-src-save)
-        (org-edit-src-exit)))
-    (define-key org-src-mode-map [remap evil-quit] 'org-edit-src-abort))
   (font-lock-add-keywords
    'org-mode
    '(("[ \t]*\\(#\\+\\(BEGIN\\|END\\|begin\\|end\\)_\\(\\S-+\\)\\)[ \t]*\\([^\n:]*\\)"
@@ -900,7 +859,7 @@ In short, my favourite way of life.
   '(org-block ((t (:background "#1d1f21"))))
   '(org-latex-and-related ((t (:foreground "#b294bb")))))
 
-(use-package ox-latex
+(use-feature ox-latex
   :after ox
   :config
   (setq org-latex-listings 'listings
@@ -917,67 +876,57 @@ In short, my favourite way of life.
                  ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
                t))
 
-(use-package ox-beamer
+(use-feature ox-beamer
   :after ox)
-
-(use-package ob-tangle
-  :general
-  (amin--leader-keys
-    :states 'normal
-    :keymaps 'org-mode-map
-    "b t" 'org-babel-tangle))
-
-(use-package orgalist
-  :after message
-  :hook (message-mode . orgalist-mode))
 #+end_src
 
 **** asynchronous tangle
 
-=amin/async-babel-tangle= is a function closely inspired by [[https://github.com/dieggsy/dotfiles/tree/cc10edf7701958eff1cd94d4081da544d882a28c/emacs.d#dotfiles][dieggsy's
+=a/async-babel-tangle= is a function closely inspired by [[https://github.com/dieggsy/dotfiles/tree/cc10edf7701958eff1cd94d4081da544d882a28c/emacs.d#dotfiles][dieggsy's
 d/async-babel-tangle]] which uses [[https://github.com/jwiegley/emacs-async][async]] to asynchronously tangle an org
 file.
 
 #+begin_src emacs-lisp
-(after! org
-  (defvar amin-show-async-tangle-results nil
+(with-eval-after-load 'org
+  (defvar a/show-async-tangle-results nil
     "Keep *emacs* async buffers around for later inspection.")
 
-  (defvar amin-show-async-tangle-time nil
+  (defvar a/show-async-tangle-time nil
     "Show the time spent tangling the file.")
 
-  (defvar amin-async-tangle-post-compile "make ti"
+  (defvar a/async-tangle-post-compile "make ti"
     "If non-nil, pass to `compile' after successful tangle.")
 
-  (defun amin/async-babel-tangle ()
+  (defun a/async-babel-tangle ()
     "Tangle org file asynchronously."
     (interactive)
     (let* ((file-tangle-start-time (current-time))
            (file (buffer-file-name))
            (file-nodir (file-name-nondirectory file))
-           (async-quiet-switch "-q"))
+           ;; (async-quiet-switch "-q")
+           )
       (async-start
        `(lambda ()
           (require 'org)
           (org-babel-tangle-file ,file))
-       (unless amin-show-async-tangle-results
+       (unless a/show-async-tangle-results
          `(lambda (result)
             (if result
                 (progn
                   (message "Tangled %s%s"
                            ,file-nodir
-                           (if amin-show-async-tangle-time
+                           (if a/show-async-tangle-time
                                (format " (%.3fs)"
                                        (float-time (time-subtract (current-time)
                                                                   ',file-tangle-start-time)))
                              ""))
-                  (when amin-async-tangle-post-compile
-                    (compile amin-async-tangle-post-compile)))
+                  (when a/async-tangle-post-compile
+                    (compile a/async-tangle-post-compile)))
               (message "Tangling %s failed" ,file-nodir))))))))
 
 (add-to-list
  'safe-local-variable-values
- '(eval add-hook 'after-save-hook #'amin/async-babel-tangle 'append 'local))
+ '(eval add-hook 'after-save-hook #'a/async-babel-tangle 'append 'local))
 #+end_src
 
 *** [[https://magit.vc/][Magit]]
@@ -990,9 +939,10 @@ Not just how I do git, but /the/ way to do git.
 
 #+begin_src emacs-lisp
 (use-package magit
-  :defer 2
-  :general (amin--leader-keys "g s" 'magit-status)
-  :bind ("s-g" . magit-status)
+  :defer 1
+  :bind (("C-x g" . magit-status)
+         ("s-g s" . magit-status)
+         ("s-g l" . magit-log-buffer-file))
   :config
   (magit-add-section-hook 'magit-status-sections-hook
                           'magit-insert-modules
@@ -1007,6 +957,18 @@ Not just how I do git, but /the/ way to do git.
   :custom-face (magit-diff-file-heading ((t (:weight normal)))))
 #+end_src
 
+*** recentf
+
+Recently opened files.
+
+#+begin_src emacs-lisp
+(use-feature recentf
+  :defer 0.5
+  :config
+  (add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:")
+  (setq recentf-max-saved-items 40))
+#+end_src
+
 *** [[https://github.com/abo-abo/swiper][Ivy]] (and friends)
 
 #+begin_quote
@@ -1020,8 +982,7 @@ There's no way I could top that, so I won't attempt to.
 
 #+begin_src emacs-lisp
 (use-package ivy
-  :defer 1
-  :general (amin--leader-keys "," 'ivy-switch-buffer)
+  :defer 0.6
   :bind
   (:map ivy-minibuffer-map
    ([escape] . keyboard-escape-quit)
@@ -1042,21 +1003,17 @@ There's no way I could top that, so I won't attempt to.
 
 #+begin_src emacs-lisp
 (use-package swiper
-  :general (:states '(normal motion) "/" 'swiper)
-  :bind (("C-s" . swiper)
-         ("C-r" . swiper)))
+  :after ivy
+  :bind (("C-s"   . swiper)
+         ("C-r"   . swiper)
+         ("C-S-s" . isearch-forward)))
 #+end_src
 
 **** Counsel
 
 #+begin_src emacs-lisp
 (use-package counsel
-  :defer 1
-  :general
-  (amin--leader-keys
-    "r" 'counsel-recentf
-    "SPC" 'counsel-M-x
-    "."   'counsel-find-file)
+  :after ivy
   :bind (([remap execute-extended-command] . counsel-M-x)
          ([remap find-file]                . counsel-find-file)
          ("s-r"                            . counsel-recentf)
@@ -1072,33 +1029,35 @@ There's no way I could top that, so I won't attempt to.
 *** eshell
 
 #+begin_src emacs-lisp
-(use-package eshell
+(use-feature eshell
   :defer 1
   :commands eshell
+  :bind ("C-c a s e" . eshell)
   :config
   (eval-when-compile (defvar eshell-prompt-regexp))
-  (defun amin/eshell-quit-or-delete-char (arg)
+  (defun a/eshell-quit-or-delete-char (arg)
     (interactive "p")
     (if (and (eolp) (looking-back eshell-prompt-regexp nil))
         (eshell-life-is-too-much)
       (delete-char arg)))
 
-  (defun amin/eshell-clear ()
+  (defun a/eshell-clear ()
     (interactive)
     (let ((inhibit-read-only t))
       (erase-buffer))
     (eshell-send-input))
 
-  (defun amin|eshell-setup ()
+  (defun a/eshell-setup ()
     (make-local-variable 'company-idle-delay)
+    (defvar company-idle-delay)
     (setq company-idle-delay nil)
     (bind-keys :map eshell-mode-map
-               ("C-d"   . amin/eshell-quit-or-delete-char)
-               ("C-S-l" . amin/eshell-clear)
+               ("C-d"   . a/eshell-quit-or-delete-char)
+               ("C-S-l" . a/eshell-clear)
                ("M-r"   . counsel-esh-history)
                ([tab]   . company-complete)))
 
-  :hook (eshell-mode . amin|eshell-setup)
+  :hook (eshell-mode . a/eshell-setup)
   :custom
   (eshell-hist-ignoredups t)
   (eshell-input-filter 'eshell-input-filter-initial-space))
@@ -1107,9 +1066,8 @@ There's no way I could top that, so I won't attempt to.
 *** Ibuffer
 
 #+begin_src emacs-lisp
-(use-package ibuffer
+(use-feature ibuffer
   :defer t
-  :general (amin--leader-keys "b b" 'ibuffer-other-window)
   :bind
   (("C-x C-b" . ibuffer-other-window)
    :map ibuffer-mode-map
@@ -1131,6 +1089,13 @@ There's no way I could top that, so I won't attempt to.
    '(("default"
       ("dired" (mode . dired-mode))
       ("org"   (mode . org-mode))
+      ("gnus"
+       (or
+        (mode . gnus-group-mode)
+        (mode . gnus-summary-mode)
+        (mode . gnus-article-mode)
+        ;; not really, but...
+        (mode . message-mode)))
       ("web"
        (or
         (mode . web-mode)
@@ -1140,20 +1105,21 @@ There's no way I could top that, so I won't attempt to.
       ("shell"
        (or
         (mode . eshell-mode)
-        (mode . shell-mode)))
-      ("notmuch" (name . "\*notmuch\*"))
+        (mode . shell-mode)
+        (mode . term-mode)))
       ("programming"
        (or
         (mode . python-mode)
+        (mode . c-mode)
         (mode . c++-mode)
-        (mode . emacs-lisp-mode)))
+        (mode . emacs-lisp-mode)
+        (mode . scheme-mode)
+        (mode . haskell-mode)
+        (mode . lean-mode)))
       ("emacs"
        (or
         (name . "^\\*scratch\\*$")
-        (name . "^\\*Messages\\*$")))
-      ("slack"
-       (or
-        (name . "^\\*Slack*"))))))
+        (name . "^\\*Messages\\*$"))))))
   (ibuffer-formats
    '((mark modified read-only locked " "
            (name 18 18 :left :elide)
@@ -1171,7 +1137,7 @@ There's no way I could top that, so I won't attempt to.
 *** Outline
 
 #+begin_src emacs-lisp
-(use-package outline
+(use-feature outline
   :defer t
   :hook (prog-mode . outline-minor-mode)
   :bind
@@ -1180,7 +1146,7 @@ There's no way I could top that, so I won't attempt to.
    ("<s-tab>"  . outline-toggle-children)
    ("M-p"      . outline-previous-visible-heading)
    ("M-n"      . outline-next-visible-heading)
-   :prefix-map amin--outline-prefix-map
+   :prefix-map a/outline-prefix-map
    :prefix "s-o"
    ("TAB" . outline-toggle-children)
    ("a"   . outline-hide-body)
@@ -1190,103 +1156,158 @@ There's no way I could top that, so I won't attempt to.
    ("s"   . outline-show-subtree)))
 #+end_src
 
-* Borg's =layer/essentials=
+*** Dired
 
-TODO: break this giant source block down into individual org sections.
+#+begin_src emacs-lisp
+(use-feature ls-lisp
+  :custom (ls-lisp-dirs-first t))
+
+(use-feature dired
+  :defer t
+  :config
+  (setq dired-listing-switches "-alh"
+        ls-lisp-use-insert-directory-program nil)
+
+  ;; easily diff 2 marked files
+  ;; https://oremacs.com/2017/03/18/dired-ediff/
+  (defun dired-ediff-files ()
+    (interactive)
+    (defvar ediff-after-quit-hook-internal)
+    (let ((files (dired-get-marked-files))
+          (wnd (current-window-configuration)))
+      (if (<= (length files) 2)
+          (let ((file1 (car files))
+                (file2 (if (cdr files)
+                           (cadr files)
+                         (read-file-name
+                          "file: "
+                          (dired-dwim-target-directory)))))
+            (if (file-newer-than-file-p file1 file2)
+                (ediff-files file2 file1)
+              (ediff-files file1 file2))
+            (add-hook 'ediff-after-quit-hook-internal
+                      (lambda ()
+                        (setq ediff-after-quit-hook-internal nil)
+                        (set-window-configuration wnd))))
+        (error "no more than 2 files should be marked"))))
+  :bind (:map dired-mode-map
+              ("e"  . dired-ediff-files)
+              ("E"  . dired-toggle-read-only)
+              ("\\" . dired-hide-details-mode)
+              ("z"  . (lambda ()
+                        (interactive)
+                        (a/dired-start-process "zathura"))))
+  :hook (dired-mode . dired-hide-details-mode))
+#+end_src
+
+*** Help
+
+#+begin_src emacs-lisp
+(use-feature help
+  :defer t
+  :config
+  (temp-buffer-resize-mode)
+  (setq help-window-select t))
+#+end_src
+
+*** Tramp
+
+#+begin_src emacs-lisp
+(use-feature tramp
+  :defer t
+  :config
+  (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
+  (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
+  (add-to-list 'tramp-default-proxies-alist
+               (list (regexp-quote (system-name)) nil nil)))
+#+end_src
+
+*** Dash
 
 #+begin_src emacs-lisp
 (use-package dash
+  :defer t
   :config (dash-enable-font-lock))
+#+end_src
+
+* Editing
+:PROPERTIES:
+:CUSTOM_ID: editing
+:END:
+
+** =diff-hl=
+
+Highlight uncommitted changes in the left fringe.
 
+#+begin_src emacs-lisp
 (use-package diff-hl
   :config
   (setq diff-hl-draw-borders nil)
   (global-diff-hl-mode)
-  (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh t))
+  :hook (magit-post-refresh . diff-hl-magit-post-refresh))
+#+end_src
 
-(use-package dired
-  :defer t
-  :config (setq dired-listing-switches "-alh"))
+** ElDoc
+
+Display Lisp objects at point in the echo area.
 
-(use-package eldoc
+#+begin_src emacs-lisp
+(use-feature eldoc
   :when (version< "25" emacs-version)
   :config (global-eldoc-mode))
+#+end_src
 
-(use-package help
-  :defer t
-  :config
-  (temp-buffer-resize-mode)
-  (setq help-window-select t))
-
-(progn ;    `isearch'
-  (setq isearch-allow-scroll t))
+** paren
 
-(use-package lisp-mode
-  :config
-  (add-hook 'emacs-lisp-mode-hook 'outline-minor-mode)
-  (add-hook 'emacs-lisp-mode-hook 'reveal-mode)
-  (defun indent-spaces-mode ()
-    (setq indent-tabs-mode nil))
-  (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
+Highlight matching parens.
 
-(use-package man
-  :defer t
-  :config (setq Man-width 80))
-
-(use-package paren
+#+begin_src emacs-lisp
+(use-feature paren
   :config (show-paren-mode))
+#+end_src
 
-(use-package prog-mode
-  :config (global-prettify-symbols-mode)
-  (defun indicate-buffer-boundaries-left ()
-    (setq indicate-buffer-boundaries 'left))
-  (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left))
+** =savehist=
 
-(use-package recentf
-  :defer 0.5
-  :config
-  (add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:")
-  (setq recentf-max-saved-items 40))
+Save minibuffer history.
 
-(use-package savehist
+#+begin_src emacs-lisp
+(use-feature savehist
   :config (savehist-mode))
+#+end_src
+
+** =saveplace=
 
-(use-package saveplace
+Automatically save place in each file.
+
+#+begin_src emacs-lisp
+(use-feature saveplace
   :when (version< "25" emacs-version)
   :config (save-place-mode))
+#+end_src
 
-(use-package simple
-  :config (column-number-mode))
+** =prog-mode=
 
-(progn ;    `text-mode'
-  (add-hook 'text-mode-hook #'indicate-buffer-boundaries-left))
+#+begin_src emacs-lisp
+(use-feature prog-mode
+  :config (global-prettify-symbols-mode)
+  (defun indicate-buffer-boundaries-left ()
+    (setq indicate-buffer-boundaries 'left))
+  (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left))
+#+end_src
 
-(use-package tramp
-  :defer t
-  :config
-  (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
-  (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
-  (add-to-list 'tramp-default-proxies-alist
-               (list (regexp-quote (system-name)) nil nil)))
+** =text-mode=
 
-(use-package undo-tree
-  :config
-  (global-undo-tree-mode -1))
-  ;; :bind (("C-?" . undo-tree-undo)
-  ;;        ("M-_" . undo-tree-redo))
-  ;; :config
-  ;; (global-undo-tree-mode)
-  ;; (setq undo-tree-mode-lighter ""
-  ;;       undo-tree-auto-save-history t))
+#+begin_src emacs-lisp
+(use-feature text-mode
+  :hook ((text-mode . indicate-buffer-boundaries-left)
+         (text-mode . abbrev-mode)))
 #+end_src
 
-* Editing
-
 ** Company
 
 #+begin_src emacs-lisp
 (use-package company
-  :defer 2
+  :defer 1
   :bind
   (:map company-active-map
         ([tab]    . company-complete-common-or-cycle)
@@ -1301,7 +1322,8 @@ TODO: break this giant source block down into individual org sections.
   (global-company-mode t))
 #+end_src
 
-* Syntax and spell checking
+** Flycheck
+
 #+begin_src emacs-lisp
 (use-package flycheck
   :defer 3
@@ -1343,12 +1365,29 @@ TODO: break this giant source block down into individual org sections.
   (advice-add #'ispell-parse-output :filter-args
               #'endless/replace-quote))
 #+end_src
+
 * Programming modes
+:PROPERTIES:
+:CUSTOM_ID: programming-modes
+:END:
+
+** Lisp
+
+#+begin_src emacs-lisp
+(use-feature lisp-mode
+  :config
+  (add-hook 'emacs-lisp-mode-hook 'outline-minor-mode)
+  (add-hook 'emacs-lisp-mode-hook 'reveal-mode)
+  (defun indent-spaces-mode ()
+    (setq indent-tabs-mode nil))
+  (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
+#+end_src
 
 ** [[http://alloytools.org][Alloy]] (with [[https://github.com/dwwmmn/alloy-mode][alloy-mode]])
 
 #+begin_src emacs-lisp
 (use-package alloy-mode
+  :straight (:host github :repo "dwwmmn/alloy-mode")
   :defer t
   :config (setq alloy-basic-offset 2))
 #+end_src
@@ -1356,9 +1395,8 @@ TODO: break this giant source block down into individual org sections.
 ** [[https://coq.inria.fr][Coq]] (with [[https://github.com/ProofGeneral/PG][Proof General]])
 
 #+begin_src emacs-lisp
-(use-package proof-site  ; Proof General
-  :defer t
-  :load-path "lib/proof-site/generic/")
+(use-package proof-general
+  :defer t)
 #+end_src
 
 ** [[https://leanprover.github.io][Lean]] (with [[https://github.com/leanprover/lean-mode][lean-mode]])
@@ -1568,7 +1606,7 @@ instead.
               ("C-c l l" . hs-lint)))
 #+end_src
 
-** Web dev
+** Web
 
 *** SGML and HTML
 
@@ -1595,7 +1633,7 @@ instead.
   :defer t
   :mode "\\.html\\'"
   :config
-  (setq-every! 2
+  (a/setq-every 2
     web-mode-code-indent-offset
     web-mode-css-indent-offset
     web-mode-markup-indent-offset))
@@ -1614,19 +1652,11 @@ instead.
   :hook (web-mode css-mode html-mode sgml-mode))
 #+end_src
 
-** Nix
-
-#+begin_src emacs-lisp
-(use-package nix-mode
-  :defer t
-  :mode "\\.nix\\'")
-#+end_src
-
-** Java
+** COMMENT Java
 
 *** meghanada
 
-#+begin_src emacs-lisp :tangle no
+#+begin_src emacs-lisp
 (use-package meghanada
   :bind
   (:map meghanada-mode-map
@@ -1653,7 +1683,7 @@ tree-mode
 treemacs
 #+end_comment
 
-#+begin_src emacs-lisp :tangle no
+#+begin_src emacs-lisp
 (use-package treemacs
   :config (setq treemacs-never-persist t))
 
@@ -1703,7 +1733,34 @@ treemacs
   :after (treemacs))
 #+end_src
 
-* Emacs Enhancements
+** COMMENT geiser
+
+#+begin_src emacs-lisp
+(use-package geiser)
+
+(use-feature geiser-guile
+  :config
+  (setq geiser-guile-load-path "~/src/git/guix"))
+#+end_src
+
+** COMMENT guix
+
+#+begin_src emacs-lisp
+(use-package guix)
+#+end_src
+
+* Emacs enhancements
+:PROPERTIES:
+:CUSTOM_ID: emacs-enhancements
+:END:
+
+** man
+
+#+begin_src emacs-lisp
+(use-feature man
+  :defer t
+  :config (setq Man-width 80))
+#+end_src
 
 ** [[https://github.com/justbur/emacs-which-key][which-key]]
 
@@ -1714,7 +1771,51 @@ Emacs package that displays available keybindings in popup
 #+begin_src emacs-lisp
 (use-package which-key
   :defer 1
-  :config (which-key-mode))
+  :config
+  (which-key-add-key-based-replacements
+    ;; prefixes for global prefixes and minor modes
+    "C-c @"   "outline"
+    "C-c !"   "flycheck"
+    "C-c 8"   "typo"
+    "C-c 8 -" "typo/dashes"
+    "C-c 8 <" "typo/left-brackets"
+    "C-c 8 >" "typo/right-brackets"
+    "C-x 8"   "unicode"
+    "C-x a"   "abbrev/expand"
+    "C-x r"   "rectangle/register/bookmark"
+    "C-x v"   "version control"
+    ;; prefixes for my personal bindings
+    "C-c a"   "applications"
+    "C-c a s" "shells"
+    "C-c b"   "borg"
+    "C-c c"   "compile-and-comments"
+    "C-c e"   "eval"
+    "C-c f"   "files"
+    "C-c F"   "frames"
+    "C-S-h"   "help(ful)"
+    "C-c m"   "multiple-cursors"
+    "C-c p"   "projectile"
+    "C-c p s" "projectile/search"
+    "C-c p x" "projectile/execute"
+    "C-c p 4" "projectile/other-window"
+    "C-c q"   "boxquote"
+    "s-g"     "magit"
+    "s-o"     "outline"
+    "s-t"     "themes")
+
+  ;; prefixes for major modes
+  (which-key-add-major-mode-key-based-replacements 'message-mode
+    "C-c f"   "footnote")
+  (which-key-add-major-mode-key-based-replacements 'org-mode
+    "C-c C-v" "org-babel")
+  (which-key-add-major-mode-key-based-replacements 'web-mode
+    "C-c C-a" "web/attributes"
+    "C-c C-b" "web/blocks"
+    "C-c C-d" "web/dom"
+    "C-c C-e" "web/element"
+    "C-c C-t" "web/tags")
+
+  (which-key-mode))
 #+end_src
 
 ** theme
@@ -1724,13 +1825,12 @@ Emacs package that displays available keybindings in popup
 (load-theme 'tangomod t)
 #+end_src
 
-** doom-modeline
+** smart-mode-line
 
 #+begin_src emacs-lisp
-(use-package doom-modeline
-  :demand t
-  :config (setq doom-modeline-height 32)
-  :hook (after-init . doom-modeline-init))
+(use-package smart-mode-line
+  :config
+  (sml/setup))
 #+end_src
 
 ** doom-themes
@@ -1742,53 +1842,44 @@ Emacs package that displays available keybindings in popup
 ** theme helper functions
 
 #+begin_src emacs-lisp
-(defun amin/lights-on ()
+(defun a/lights-on ()
   "Enable my favourite light theme."
   (interactive)
-  (progn
-    (mapc #'disable-theme custom-enabled-themes)
-    (load-theme 'tangomod t)))
+  (mapc #'disable-theme custom-enabled-themes)
+  (load-theme 'tangomod t)
+  (sml/apply-theme 'automatic))
 
-(defun amin/lights-off ()
+(defun a/lights-off ()
   "Go dark."
   (interactive)
-  (progn
-    (mapc #'disable-theme custom-enabled-themes)
-    (load-theme 'doom-tomorrow-night t)))
+  (mapc #'disable-theme custom-enabled-themes)
+  (load-theme 'doom-tomorrow-night t)
+  (sml/apply-theme 'automatic))
 
-(amin--leader-keys
-  "t"   '(:ignore t :wk "theme")
-  "t d" 'amin/lights-off
-  "t l" 'amin/lights-on)
+(bind-keys
+ ("s-t d" . a/lights-off)
+ ("s-t l" . a/lights-on))
 #+end_src
 
 ** [[https://github.com/bbatsov/crux][crux]]
 
 #+begin_src emacs-lisp
-(use-package crux
+(use-package crux                       ; results in Waiting for git... [2 times]
   :defer 1
-  :general
-  (amin--leader-keys
-    "b K" 'crux-kill-other-buffers
-    "c d" 'crux-duplicate-current-line-or-region
-    "c D" 'crux-duplicate-and-comment-current-line-or-region
-    "f c" 'crux-copy-file-preserve-attributes
-    "f d" 'crux-delete-file-and-buffer
-    "f r" 'crux-rename-file-and-buffer)
-  :bind (("C-c d" . crux-duplicate-current-line-or-region)
-         ("C-c D" . crux-duplicate-and-comment-current-line-or-region)
-         ("C-S-j" . crux-top-join-line)
-         ("C-c j" . crux-top-join-line)))
+  :bind (("C-c b k" . crux-kill-other-buffers)
+         ("C-c d"   . crux-duplicate-current-line-or-region)
+         ("C-c D"   . crux-duplicate-and-comment-current-line-or-region)
+         ("C-c f c" . crux-copy-file-preserve-attributes)
+         ("C-c f d" . crux-delete-file-and-buffer)
+         ("C-c f r" . crux-rename-file-and-buffer)
+         ("C-c j"   . crux-top-join-line)
+         ("C-S-j"   . crux-top-join-line)))
 #+end_src
 
 ** [[https://github.com/alezost/mwim.el][mwim]]
 
 #+begin_src emacs-lisp
 (use-package mwim
-  :general
-  (:states '(normal visual)
-           "0" 'mwim-beginning-of-code-or-line
-           "$" 'mwim-end-of-code-or-line)
   :bind (("C-a"    . mwim-beginning-of-code-or-line)
          ("C-e"    . mwim-end-of-code-or-line)
          ("<home>" . mwim-beginning-of-line-or-code)
@@ -1799,7 +1890,7 @@ Emacs package that displays available keybindings in popup
 
 #+begin_src emacs-lisp
 (use-package projectile
-  :defer 2
+  :defer t
   :bind-keymap ("C-c p" . projectile-command-map)
   :config
   (projectile-mode)
@@ -1813,7 +1904,8 @@ Emacs package that displays available keybindings in popup
        (advice-add 'magit-checkout
                    :after #'my-projectile-invalidate-cache)
        (advice-add 'magit-branch-and-checkout
-                   :after #'my-projectile-invalidate-cache))))
+                   :after #'my-projectile-invalidate-cache)))
+  :custom (projectile-completion-system 'ivy))
 #+end_src
 
 ** [[https://github.com/Wilfred/helpful][helpful]]
@@ -1821,62 +1913,12 @@ Emacs package that displays available keybindings in popup
 #+begin_src emacs-lisp
 (use-package helpful
   :defer 1
-  :general
-  (amin--leader-keys
-    "h h"   '(:ignore t :wk "helpful")
-    "h h c" 'helpful-command
-    "h h f" 'helpful-callable           ; helpful-function
-    "h h v" 'helpful-variable
-    "h h k" 'helpful-key
-    "h h p" 'helpful-at-point))
-#+end_src
-
-** [[https://github.com/knu/shell-toggle.el][shell-toggle]]
-
-#+begin_src emacs-lisp
-(use-package shell-toggle
-  :after eshell
-  :general (amin--leader-keys "a s" 'amin/shell-toggle)
-  :bind ("C-c e" . amin/shell-toggle)
-  :config
-  (defun amin/shell-toggle (make-cd)
-    "Toggle between the shell buffer and whatever buffer you are editing.
-With a prefix argument MAKE-CD also insert a \"cd DIR\" command
-into the shell, where DIR is the directory of the current buffer.
-
-When called in the shell buffer returns you to the buffer you were editing
-before calling this the first time.
-
-Options: `shell-toggle-goto-eob'"
-    (interactive "P")
-    ;; Try to decide on one of three possibilities:
-    ;; If not in shell-buffer, switch to it.
-    ;; If in shell-buffer, return to state before going to the shell-buffer
-    (if (eq (current-buffer) shell-toggle-shell-buffer)
-        (shell-toggle-buffer-return-from-shell)
-      (progn
-        (shell-toggle-buffer-goto-shell make-cd)
-        (if shell-toggle-full-screen-window-only (delete-other-windows)))))
-
-  ;; override to split horizontally instead
-  (defun shell-toggle-buffer-switch-to-other-window ()
-    "Switch to other window.
-If the current window is the only window in the current frame,
-create a new window and switch to it.
-
-\(This is less intrusive to the current window configuration than
-`switch-buffer-other-window')"
-    (let ((this-window (selected-window)))
-      (other-window 1)
-      ;; If we did not switch window then we only have one window and need to
-      ;; create a new one.
-      (if (eq this-window (selected-window))
-             (progn
-               (split-window-horizontally)
-            (other-window 1)))))
-
-  :custom
-  (shell-toggle-launch-shell 'shell-toggle-eshell))
+  :bind
+  (("C-S-h c" . helpful-command)
+   ("C-S-h f" . helpful-callable)        ; helpful-function
+   ("C-S-h v" . helpful-variable)
+   ("C-S-h k" . helpful-key)
+   ("C-S-h p" . helpful-at-point)))
 #+end_src
 
 ** [[https://github.com/EricCrosson/unkillable-scratch][unkillable-scratch]]
@@ -1905,7 +1947,7 @@ Make =*scratch*= and =*Messages*= unkillable.
 (use-package boxquote
   :defer 3
   :bind
-  (:prefix-map amin--boxquote-prefix-map
+  (:prefix-map a/boxquote-prefix-map
    :prefix "C-c q"
    ("b"   . boxquote-buffer)
    ("B"   . boxquote-insert-buffer)
@@ -1931,46 +1973,12 @@ Make =*scratch*= and =*Messages*= unkillable.
 
 Also see [[https://www.emacswiki.org/emacs/rebox2][rebox2]].
 
-** COMMENT [[https://github.com/DarthFennec/highlight-indent-guides][highlight-indent-guides]]                                 :ARCHIVE:
-
-#+begin_src emacs-lisp
-(use-package highlight-indent-guides
-  :defer 3
-  :hook ((prog-mode . highlight-indent-guides-mode)
-         ;; (org-mode  . highlight-indent-guides-mode)
-         )
-  :config
-  (setq highlight-indent-guides-character ?\|)
-  (setq highlight-indent-guides-auto-enabled nil)
-  (setq highlight-indent-guides-method 'character)
-  (setq highlight-indent-guides-responsive 'top)
-  (set-face-foreground 'highlight-indent-guides-character-face "gainsboro")
-  (set-face-foreground 'highlight-indent-guides-top-character-face "grey40")) ; grey13 is nice too
-#+end_src
-
-** pdf-tools
-
-#+begin_src emacs-lisp
-(use-package pdf-tools
-  :defer t
-  :magic ("%PDF" . pdf-view-mode)
-  :config
-  (setq pdf-view-resize-factor 1.05)
-  (pdf-tools-install)
-  :bind
-  (:map pdf-view-mode-map
-        ("C-s" . isearch-forward)
-        ("C-r" . isearch-backward)
-        ("j"   . pdf-view-next-line-or-next-page)
-        ("k"   . pdf-view-previous-line-or-previous-page)
-        ("h"   . image-backward-hscroll)
-        ("l"   . image-forward-hscroll)))
-#+end_src
-
-** anzu
+** orgalist
 
 #+begin_src emacs-lisp
-(use-package anzu)
+(use-package orgalist
+  :after message
+  :hook (message-mode . orgalist-mode))
 #+end_src
 
 ** typo.el
@@ -1983,71 +1991,6 @@ Also see [[https://www.emacswiki.org/emacs/rebox2][rebox2]].
   :hook (text-mode . typo-mode))
 #+end_src
 
-** slack
-
-Hopefully temporary.
-
-#+begin_src emacs-lisp
-(use-package slack
-  :commands (slack-start)
-  :init
-  (eval-when-compile                    ; silence the byte-compiler
-    (defvar url-http-data nil)
-    (defvar url-http-extra-headers nil)
-    (defvar url-http-method nil)
-    (defvar url-callback-function nil)
-    (defvar url-callback-arguments nil)
-    (defvar oauth--token-data nil))
-  (setq slack-buffer-emojify t
-        slack-prefer-current-team t)
-  :config
-  (slack-register-team
-   :name "uw-apv"
-   :default t
-   :client-id uw-apv-client-id
-   :client-secret uw-apv-client-secret
-   :token uw-apv-token
-   :subscribed-channels '(general)
-   :full-and-display-names t)
-  (slack-register-team
-   :name "watform"
-   :default nil
-   :client-id watform-client-id
-   :client-secret watform-client-secret
-   :token watform-token
-   :subscribed-channels '(general)
-   :full-and-display-names t)
-  (add-to-list 'swiper-font-lock-exclude 'slack-message-buffer-mode t)
-  (setq lui-time-stamp-format "[%Y-%m-%d %H:%M:%S]"
-        lui-time-stamp-only-when-changed-p t
-        lui-time-stamp-position 'right)
-  :bind
-  (("C-c s s" . slack-start)
-   ("C-c s u" . slack-select-unread-rooms)
-   ("C-c s b" . slack-select-rooms)
-   ("C-c s t" . slack-change-current-team)
-   ("C-c s c" . slack-ws-close)
-   :map slack-mode-map
-   ("M-p"     . slack-buffer-goto-prev-message)
-   ("M-n"     . slack-buffer-goto-next-message)
-   ("C-c e"   . slack-message-edit)
-   ("C-c k"   . slack-message-delete)
-   ("C-c C-k" . slack-channel-leave)
-   ("C-c r a" . slack-message-add-reaction)
-   ("C-c r r" . slack-message-remove-reaction)
-   ("C-c r s" . slack-message-show-reaction-users)
-   ("C-c p l" . slack-room-pins-list)
-   ("C-c p a" . slack-message-pins-add)
-   ("C-c p r" . slack-message-pins-remove)
-   ("@"       . slack-message-embed-mention)
-   ("#"       . slack-message-embed-channel)))
-
-(use-package alert
-  :commands (alert)
-  :init
-  (setq alert-default-style 'notifier))
-#+end_src
-
 ** hl-todo
 
 #+begin_src emacs-lisp
@@ -2088,48 +2031,99 @@ Hopefully temporary.
         ""))))
 #+end_src
 
-** magithub
+** [[https://github.com/peterwvj/eshell-up][eshell-up]]
 
-For when I /have to/ use GH.
+#+begin_src emacs-lisp
+(use-package eshell-up
+  :after eshell)
+#+end_src
+
+** multi-term
 
 #+begin_src emacs-lisp
-(use-package magithub
-  :after magit
+(use-package multi-term
+  :defer 1
+  :bind (("C-c a s m" . multi-term-dedicated-toggle)
+         :map term-mode-map
+         ("C-c C-j" . term-char-mode)
+         :map term-raw-map
+         ("C-c C-j" . term-line-mode))
+  :config
+  (setq multi-term-program "/bin/screen"
+        ;; TODO: add separate bindings for connecting to existing
+        ;; session vs. always creating a new one
+        multi-term-dedicated-select-after-open-p t
+        multi-term-dedicated-window-height 20
+        multi-term-dedicated-max-window-height 30
+        term-bind-key-alist
+        '(("C-c C-c" . term-interrupt-subjob)
+          ("C-c C-e" . term-send-esc)
+          ("C-k" . kill-line)
+          ("C-y" . term-paste)
+          ("M-f" . term-send-forward-word)
+          ("M-b" . term-send-backward-word)
+          ("M-p" . term-send-up)
+          ("M-n" . term-send-down)
+          ("<C-backspace>" . term-send-backward-kill-word)
+          ("<M-DEL>" . term-send-backward-kill-word)
+          ("M-d" . term-send-delete-word)
+          ("M-," . term-send-raw)
+          ("M-." . comint-dynamic-complete))
+        term-unbind-key-alist
+        '("C-z" "C-x" "C-c" "C-h" "C-y" "<ESC>")))
+#+end_src
+
+** page-break-lines
+
+#+begin_src emacs-lisp
+(use-package page-break-lines
   :config
-  (magithub-feature-autoinject t)
-  (setq magithub-clone-default-directory "~/src/git"))
+  (global-page-break-lines-mode))
 #+end_src
 
-** [[https://github.com/peterwvj/eshell-up][eshell-up]]
+** expand-region
 
 #+begin_src emacs-lisp
-(use-package eshell-up
-  :after eshell)
+(use-package expand-region
+  :bind ("C-=" . er/expand-region))
+#+end_src
+
+** multiple-cursors
+
+#+begin_src emacs-lisp
+(use-package multiple-cursors
+  :bind
+  (("C-S-<mouse-1>" . mc/add-cursor-on-click)
+   (:prefix-map a/mc-prefix-map
+               :prefix "C-c m"
+               ("c" . mc/edit-lines)
+               ("n" . mc/mark-next-like-this)
+               ("p" . mc/mark-previous-like-this)
+               ("a" . mc/mark-all-like-this))))
 #+end_src
 
 * Email
+:PROPERTIES:
+:CUSTOM_ID: email
+:END:
 
 #+begin_src emacs-lisp
-(defvar amin-maildir (expand-file-name "~/mail/"))
-(after! recentf
-  (add-to-list 'recentf-exclude amin-maildir))
+(defvar a/maildir (expand-file-name "~/mail/"))
+(with-eval-after-load 'recentf
+  (add-to-list 'recentf-exclude a/maildir))
 #+end_src
 
 ** Gnus
 
 #+begin_src emacs-lisp
 (setq
- amin-gnus-init-file (no-littering-expand-etc-file-name "gnus")
- mail-user-agent     'gnus-user-agent
- read-mail-command   'gnus)
-
-(use-package gnus
-  :general
-  (amin--leader-keys
-    "m" 'gnus
-    "M" 'gnus-unplugged)
-  :bind (("s-m" . gnus)
-         ("s-M" . gnus-unplugged))
+ a/gnus-init-file  (no-littering-expand-etc-file-name "gnus")
+ mail-user-agent   'gnus-user-agent
+ read-mail-command 'gnus)
+
+(use-feature gnus
+  :bind (("s-m"   . gnus)
+         ("s-M"   . gnus-unplugged))
   :init
   (setq
    gnus-select-method '(nnnil "")
@@ -2160,7 +2154,7 @@ For when I /have to/ use GH.
    gnus-interactive-exit nil
    gnus-gcc-mark-as-read t))
 
-(use-package gnus-art
+(use-feature gnus-art
   :config
   (setq
    gnus-visible-headers
@@ -2189,9 +2183,9 @@ For when I /have to/ use GH.
    ("R"   . gnus-article-wide-reply-with-original)
    ("M-L" . org-store-link)))
 
-(use-package gnus-sum
+(use-feature gnus-sum
   :bind (:map gnus-summary-mode-map
-              :prefix-map amin--gnus-summary-prefix-map
+              :prefix-map a/gnus-summary-prefix-map
               :prefix "v"
               ("r" . gnus-summary-reply)
               ("w" . gnus-summary-wide-reply)
@@ -2201,41 +2195,39 @@ For when I /have to/ use GH.
    :map gnus-summary-mode-map
    ("r"   . gnus-summary-reply-with-original)
    ("R"   . gnus-summary-wide-reply-with-original)
-   ("M-L" . org-store-link)))
+   ("M-L" . org-store-link))
+  :hook (gnus-summary-mode . a/no-mouse-autoselect-window))
 
-(use-package gnus-msg
+(use-feature gnus-msg
   :config
   (setq gnus-posting-styles
         '((".*"
-           (address "amin@bandali.me")
+           (address "amin@aminb.org")
            (body "\nBest,\namin\n")
-           (signature-file "~/.signature-amin")
-           (eval (setq amin--message-cite-say-hi t)))
+           (eval (setq a/message-cite-say-hi t)))
           ("gnu.*"
-           (address "bandali@gnu.org")
-           (signature-file nil))
+           (address "bandali@gnu.org"))
           ((header "subject" "ThankCRM")
            (to "webmasters-comment@gnu.org")
            (body "\nAdded  to 2018supporters.html.\n\nMoving to campaigns.\n\n-amin\n")
-           (eval (setq amin--message-cite-say-hi nil)))
+           (eval (setq a/message-cite-say-hi nil)))
           ("nnimap\\+uwaterloo:.*"
            (address "abandali@uwaterloo.ca")
-           (gcc "\"nnimap+uwaterloo:Sent Items\"")
-           (signature-file nil)))))
+           (gcc "\"nnimap+uwaterloo:Sent Items\"")))))
 
-(use-package gnus-topic
+(use-feature gnus-topic
   :hook (gnus-group-mode . gnus-topic-mode))
 
-(use-package gnus-agent
+(use-feature gnus-agent
   :config
   (setq gnus-agent-synchronize-flags 'ask)
   :hook (gnus-group-mode . gnus-agent-mode))
 
-(use-package gnus-group
+(use-feature gnus-group
   :config
   (setq gnus-permanently-visible-groups "\\((INBOX\\|gnu$\\)"))
 
-(use-package mm-decode
+(use-feature mm-decode
   :config
   (setq mm-discouraged-alternatives '("text/html" "text/richtext")))
 #+end_src
@@ -2243,7 +2235,7 @@ For when I /have to/ use GH.
 ** sendmail
 
 #+begin_src emacs-lisp
-(use-package sendmail
+(use-feature sendmail
   :config
   (setq sendmail-program "/usr/bin/msmtp"
         ;; message-sendmail-extra-arguments '("-v" "-d")
@@ -2254,9 +2246,9 @@ For when I /have to/ use GH.
 ** message
 
 #+begin_src emacs-lisp
-(use-package message
+(use-feature message
   :config
-  (defconst amin--message-cite-style-format "On %Y-%m-%d %l:%M %p, %N wrote:")
+  (defconst a/message-cite-style-format "On %Y-%m-%d %l:%M %p, %N wrote:")
   (defconst message-cite-style-bandali
     '((message-cite-function  'message-cite-original)
       (message-citation-line-function  'message-insert-formatted-citation-line)
@@ -2265,28 +2257,31 @@ For when I /have to/ use GH.
       (message-yank-cited-prefix  ">")
       (message-yank-empty-prefix  ">")
       (message-citation-line-format
-       (if amin--message-cite-say-hi
-           (concat "Hi %F,\n\n" amin--message-cite-style-format)
-         amin--message-cite-style-format)))
+       (if a/message-cite-say-hi
+           (concat "Hi %F,\n\n" a/message-cite-style-format)
+         a/message-cite-style-format)))
     "Citation style based on Mozilla Thunderbird's. Use with message-cite-style.")
   (setq message-cite-style 'message-cite-style-bandali
         message-kill-buffer-on-exit t
         message-send-mail-function 'message-send-mail-with-sendmail
         message-sendmail-envelope-from 'header
         message-dont-reply-to-names
-        "\\(\\(amin@bandali\\.me\\)\\|\\(.*@aminb\\.org\\)\\|\\(\\(aminb?\\|mab\\|bandali\\)@gnu\\.org\\)\\|\\(\\(m\\|a\\(min\\.\\)?\\)bandali@uwaterloo\\.ca\\)\\)"
+        "\\(\\(.*@aminb\\.org\\)\\|\\(amin@bandali\\.me\\)\\|\\(\\(aminb?\\|mab\\|bandali\\)@gnu\\.org\\)\\|\\(\\(m\\|a\\(min\\.\\)?\\)bandali@uwaterloo\\.ca\\)\\)"
         message-user-fqdn "aminb.org")
   :hook (;; (message-setup . mml-secure-message-sign-pgpmime)
          (message-mode . flyspell-mode)
-         (message-mode . (lambda () (setq fill-column 65
-                                     message-fill-column 65))))
+         (message-mode . (lambda ()
+                           ;; (setq fill-column 65
+                           ;;       message-fill-column 65)
+                           (make-local-variable 'company-idle-delay)
+                           (setq company-idle-delay 0.2))))
   ;; :custom-face
   ;; (message-header-subject ((t (:foreground "#111" :weight semi-bold))))
   ;; (message-header-to      ((t (:foreground "#111" :weight normal))))
   ;; (message-header-cc      ((t (:foreground "#333" :weight normal))))
   )
 
-(after! mml-sec
+(with-eval-after-load 'mml-sec
   (setq mml-secure-openpgp-encrypt-to-self t
         mml-secure-openpgp-sign-with-sender t))
 #+end_src
@@ -2296,11 +2291,11 @@ For when I /have to/ use GH.
 Convenient footnotes in =message-mode=.
 
 #+begin_src emacs-lisp
-(use-package footnote
+(use-feature footnote
   :after message
   :bind
   (:map message-mode-map
-        :prefix-map amin--footnote-prefix-map
+        :prefix-map a/footnote-prefix-map
         :prefix "C-c f"
         ("a" . footnote-add-footnote)
         ("b" . footnote-back-to-message)
@@ -2315,36 +2310,56 @@ Convenient footnotes in =message-mode=.
         footnote-style     'unicode))
 #+end_src
 
-** bbdb
+** ebdb
 
-Manually install bbdb (=lisp/bbdb= copied from an ELPA-based setup),
-because installing it from source on Emacs 27 using the following
-submodule configuration for some reason doesn’t work and results in
-very strange errors when using any of the functions.
+#+begin_src emacs-lisp
+(use-package ebdb
+  :defer 1
+  :bind (:map gnus-group-mode-map ("e" . ebdb))
+  :config
+  (setq ebdb-sources (no-littering-expand-var-file-name "ebdb"))
+  (with-eval-after-load 'swiper
+    (add-to-list 'swiper-font-lock-exclude 'ebdb-mode t)))
 
-#+begin_src conf :tangle no
-[submodule "bbdb"]
-       path = lib/bbdb
-       url = https://git.savannah.nongnu.org/git/bbdb.git
-       load-path = lisp
-       info-path = doc
-       build-step = ./autogen.sh
-       build-step = ./configure
-       build-step = make
-       build-step = make install
-#+end_src
+(use-feature ebdb-com
+  :after ebdb)
 
-I tried using =borg-elpa= instead of doing it like this, but it added
-2 seconds to my startup time, which is unacceptable to me.
+;; (use-package ebdb-complete
+;;   :after ebdb
+;;   :config
+;;   (ebdb-complete-enable))
 
-#+begin_src emacs-lisp
-(use-package bbdb
-  :load-path "lisp/bbdb"
-  :init
-  (load (expand-file-name "lisp/bbdb/bbdb-autoloads.el" user-emacs-directory))
-  (bbdb-mua-auto-update-init 'message)
-  (setq bbdb-mua-auto-update-p 'query)
-  (add-hook 'gnus-startup-hook 'bbdb-insinuate-gnus))
+(use-package company-ebdb
+  :after (:all company message)
+  :config
+  (defun company-ebdb--post-complete (_) nil)
+  :hook
+  (message-mode . (lambda ()
+                    (add-to-list (make-local-variable 'company-backends)
+                                 'company-ebdb))))
+
+(use-feature ebdb-gnus
+  :after ebdb
+  :custom
+  (ebdb-gnus-window-configuration
+   '(article
+     (vertical 1.0
+               (summary 0.25 point)
+               (horizontal 1.0
+                           (article 1.0)
+                           (ebdb-gnus 0.3))))))
+
+(use-feature ebdb-mua
+  :after ebdb
+  ;; :custom (ebdb-mua-pop-up nil)
+  )
+
+;; (use-package ebdb-message
+;;   :after ebdb)
+
+
+;; (use-package ebdb-vcard
+;;   :after ebdb)
 #+end_src
 
 ** COMMENT message-x
@@ -2374,7 +2389,11 @@ I tried using =borg-elpa= instead of doing it like this, but it added
     (gnus-harvest-install)))
 #+end_src
 
-* Blogging
+* COMMENT Blogging
+:PROPERTIES:
+:CUSTOM_ID: blogging
+:END:
+
 ** [[https://ox-hugo.scripter.co][ox-hugo]]
 
 #+begin_src emacs-lisp
@@ -2395,7 +2414,7 @@ Display how long it took to load the init file.
 #+begin_src emacs-lisp
 (message "Loading %s...done (%.3fs)" user-init-file
          (float-time (time-subtract (current-time)
-                                    amin--before-user-init-time)))
+                                    a/before-user-init-time)))
 #+end_src
 
 * Footer
@@ -2409,5 +2428,6 @@ Display how long it took to load the init file.
 
 * COMMENT Local Variables                                           :ARCHIVE:
 # Local Variables:
-# eval: (add-hook 'after-save-hook #'amin/async-babel-tangle 'append 'local)
+# eval: ;; (add-hook 'after-save-hook #'a/async-babel-tangle 'append 'local)
+# eval: (typo-mode -1)
 # End: