[emacs] assimilate orgalist
[~bandali/configs] / init.org
index 55e8269..4158a20 100644 (file)
--- a/init.org
+++ b/init.org
@@ -9,9 +9,21 @@
 :END:
 
 This org file is my literate configuration for GNU Emacs, and is
 :END:
 
 This org file is my literate configuration for GNU Emacs, and is
-tangled to [[./init.el][init.el]]. Packages are installed and managed using [[https://github.com/emacscollective/borg][Borg]].
-
-** Installation
+tangled to [[./init.el][init.el]]. Packages are installed and managed using
+[[https://github.com/emacscollective/borg][Borg]]. Over the years, I've taken inspiration from configurations of
+many different people. Some of the configurations that I can remember
+off the top of my head are:
+
+- [[https://github.com/dieggsy/dotfiles][dieggsy/dotfiles]]: literate Emacs and dotfiles configuration, uses
+  straight.el for managing packages
+- [[https://github.com/dakra/dmacs][dakra/dmacs]]: literate Emacs configuration, using Borg for managing
+  packages
+- [[http://pages.sachachua.com/.emacs.d/Sacha.html][Sacha Chua's literate Emacs configuration]]
+- [[https://github.com/dakrone/eos][dakrone/eos]]
+- Ryan Rix's [[http://doc.rix.si/cce/cce.html][Complete Computing Environment]] ([[http://doc.rix.si/projects/fsem.html][about cce]])
+- [[https://github.com/jwiegley/dot-emacs][jwiegley/dot-emacs]]: nix-based configuration
+- [[https://github.com/wasamasa/dotemacs][wasamasa/dotemacs]]
+- [[https://github.com/hlissner/doom-emacs][Doom Emacs]]
 
 I'd like to have a fully reproducible Emacs setup (part of the reason
 why I store my configuration in this repository) but unfortunately out
 
 I'd like to have a fully reproducible Emacs setup (part of the reason
 why I store my configuration in this repository) but unfortunately out
@@ -22,6 +34,20 @@ to my init time; which is unacceptable for me: I use Emacs as my
 window manager (via EXWM) and coming from bspwm, I'm too used to
 having fast startup times.
 
 window manager (via EXWM) and coming from bspwm, I'm too used to
 having fast startup times.
 
+** Installation
+
+To use this config for your Emacs, first you need to clone this repo,
+then bootstrap Borg, tell Borg to retrieve package submodules, and
+byte-compiled the packages. Something along these lines should work:
+
+#+begin_src sh :tangle no
+git clone https://github.com/aminb/dotfiles ~/.emacs.d
+cd ~/.emacs.d
+make bootstrap-borg
+make bootstrap
+make build
+#+end_src
+
 * Contents                                                   :toc_1:noexport:
 
 - [[#about][About]]
 * Contents                                                   :toc_1:noexport:
 
 - [[#about][About]]
@@ -79,20 +105,19 @@ file.
 
 ** Naming conventions
 
 
 ** Naming conventions
 
-The conventions below were inspired by [[https://github.com/hlissner/doom-emacs][Doom]]'s conventions, found
-[[https://github.com/hlissner/doom-emacs/blob/5dacbb7cb1c6ac246a9ccd15e6c4290def67757c/core/core.el#L3-L17][here]]. Naturally, I use my initials, =ab=, instead of =doom=.
+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:
 ;;
 
 #+begin_src emacs-lisp :comments none
 ;; Naming conventions:
 ;;
-;;   ab-...   public variables or non-interactive functions
-;;   ab--...  private anything (non-interactive), not safe for direct use
-;;   ab/...   an interactive function; safe for M-x or keybinding
-;;   ab:...   an evil operator, motion, or command
-;;   ab|...   a hook function
-;;   ab*...   an advising function
-;;   ab@...   a hydra command
-;;   ...!     a macro
+;;   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
 #+end_src
 
 * Initial setup
@@ -111,10 +136,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
 =init.el=, i.e. =user-init-file=:
 
 #+begin_src emacs-lisp
-(defvar ab--before-user-init-time (current-time)
+(defvar amin--before-user-init-time (current-time)
   "Value of `current-time' when Emacs begins loading `user-init-file'.")
 (message "Loading Emacs...done (%.3fs)"
   "Value of `current-time' when Emacs begins loading `user-init-file'.")
 (message "Loading Emacs...done (%.3fs)"
-         (float-time (time-subtract ab--before-user-init-time
+         (float-time (time-subtract amin--before-user-init-time
                                     before-init-time)))
 #+end_src
 
                                     before-init-time)))
 #+end_src
 
@@ -124,9 +149,9 @@ frequency. Clearing the ~file-name-handler-alist~ seems to help reduce
 startup time as well.
 
 #+begin_src emacs-lisp
 startup time as well.
 
 #+begin_src emacs-lisp
-(defvar ab--gc-cons-threshold gc-cons-threshold)
-(defvar ab--gc-cons-percentage gc-cons-percentage)
-(defvar ab--file-name-handler-alist file-name-handler-alist)
+(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)
 (setq gc-cons-threshold (* 400 1024 1024)  ; 400 MiB
       gc-cons-percentage 0.6
       file-name-handler-alist nil
 (setq gc-cons-threshold (* 400 1024 1024)  ; 400 MiB
       gc-cons-percentage 0.6
       file-name-handler-alist nil
@@ -141,13 +166,9 @@ done initializing.
 (add-hook
  'after-init-hook
  (lambda ()
 (add-hook
  'after-init-hook
  (lambda ()
-   (let ((elapsed (float-time (time-subtract (current-time)
-                                             ab--before-user-init-time))))
-     (message "Loading %s...done (%.3fs) [after-init]"
-              user-init-file elapsed))
-   (setq gc-cons-threshold ab--gc-cons-threshold
-         gc-cons-percentage ab--gc-cons-percentage
-         file-name-handler-alist ab--file-name-handler-alist)))
+   (setq gc-cons-threshold amin--gc-cons-threshold
+         gc-cons-percentage amin--gc-cons-percentage
+         file-name-handler-alist amin--file-name-handler-alist)))
 #+end_src
 
 Increase the number of lines kept in message logs (the =*Messages*=
 #+end_src
 
 Increase the number of lines kept in message logs (the =*Messages*=
@@ -166,6 +187,13 @@ but for now I've decided to keep them enabled. See documentation for
 ;;       '(not free-vars unresolved noruntime lexical make-local))
 #+end_src
 
 ;;       '(not free-vars unresolved noruntime lexical make-local))
 #+end_src
 
+** whoami
+
+#+begin_src emacs-lisp
+(setq user-full-name "Amin Bandali"
+      user-mail-address "amin@aminb.org")
+#+end_src
+
 ** Package management
 
 *** No =package.el=
 ** Package management
 
 *** No =package.el=
@@ -205,6 +233,17 @@ can then be managed with the help of Magit or other tools.
              (expand-file-name "lib/borg" user-emacs-directory))
 (require 'borg)
 (borg-initialize)
              (expand-file-name "lib/borg" user-emacs-directory))
 (require 'borg)
 (borg-initialize)
+
+(require 'borg-nix-shell)
+(setq borg-build-shell-command 'borg-nix-shell-build-command)
+
+(with-eval-after-load 'bind-key
+  (bind-keys
+   :package borg
+   ("C-c B A" . borg-activate)
+   ("C-c B a" . borg-assimilate)
+   ("C-c B b" . borg-build)
+   ("C-c B c" . borg-clone)))
 #+end_src
 
 *** =use-package=
 #+end_src
 
 *** =use-package=
@@ -219,7 +258,7 @@ and without compromising on performance.
 
 #+begin_src emacs-lisp
 (require 'use-package)
 
 #+begin_src emacs-lisp
 (require 'use-package)
-(if nil                                    ; set to t when need to debug init
+(if nil  ; set to t when need to debug init
     (setq use-package-verbose t
           use-package-expand-minimally nil
           use-package-compute-statistics t
     (setq use-package-verbose t
           use-package-expand-minimally nil
           use-package-compute-statistics t
@@ -240,7 +279,12 @@ database, low-level functions for querying the database, and a
 
 #+begin_src emacs-lisp
 (use-package epkg
 
 #+begin_src emacs-lisp
 (use-package epkg
-  :defer t)
+  :defer t
+  :bind
+  (("C-c B d" . epkg-describe-package)
+   ("C-c B p" . epkg-list-packages)
+   ("C-c B r" . borg-remove)
+   ("C-c B u" . epkg-update)))
 #+end_src
 
 ** No littering in =~/.emacs.d=
 #+end_src
 
 ** No littering in =~/.emacs.d=
@@ -297,6 +341,14 @@ in my shell.
   (exec-path-from-shell-copy-env "SSH_AUTH_SOCK"))
 #+end_src
 
   (exec-path-from-shell-copy-env "SSH_AUTH_SOCK"))
 #+end_src
 
+** Only one custom theme at a time
+
+#+begin_src emacs-lisp
+(defadvice load-theme (before clear-previous-themes activate)
+  "Clear existing theme settings instead of layering them"
+  (mapc #'disable-theme custom-enabled-themes))
+#+end_src
+
 ** Server
 
 Start server if not already running. Alternatively, can be done by
 ** Server
 
 Start server if not already running. Alternatively, can be done by
@@ -312,6 +364,95 @@ See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.htm
   :config (or (server-running-p) (server-mode)))
 #+end_src
 
   :config (or (server-running-p) (server-mode)))
 #+end_src
 
+** Unicode support
+
+Font stack with better unicode support, around =Ubuntu Mono= and
+=Hack=.
+
+#+begin_src emacs-lisp
+(dolist (ft (fontset-list))
+  (set-fontset-font
+   ft
+   'unicode
+   (font-spec :name "Ubuntu Mono"))
+  (set-fontset-font
+   ft
+   'unicode
+   (font-spec :name "DejaVu Sans Mono")
+   nil
+   'append)
+  ;; (set-fontset-font
+  ;;  ft
+  ;;  'unicode
+  ;;  (font-spec
+  ;;   :name "Symbola monospacified for DejaVu Sans Mono")
+  ;;  nil
+  ;;  'append)
+  ;; (set-fontset-font
+  ;;  ft
+  ;;  #x2115  ; ℕ
+  ;;  (font-spec :name "DejaVu Sans Mono")
+  ;;  nil
+  ;;  'append)
+  (set-fontset-font
+   ft
+   (cons ?Α ?ω)
+   (font-spec :name "DejaVu Sans Mono" :size 14)
+   nil
+   'prepend))
+#+end_src
+
+** Libraries
+
+#+begin_src emacs-lisp
+(require 'cl-lib)
+(require 'subr-x)
+#+end_src
+
+** Useful utilities
+
+#+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)))
+
+; 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)))))
+#+end_src
+
+Convenience macro for =setq='ing multiple variables to the same value:
+
+#+begin_src emacs-lisp
+(defmacro 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
+
 * Core
 :PROPERTIES:
 :CUSTOM_ID: core
 * Core
 :PROPERTIES:
 :CUSTOM_ID: core
@@ -319,13 +460,34 @@ See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.htm
 
 ** Defaults
 
 
 ** Defaults
 
+*** Time and battery in mode-line
+
+Enable displaying time and battery in the mode-line, since I'm not
+using the Xfce panel anymore. Also, I don't need to see the load
+average on a regular basis, so disable that.
+
+Note: using =i3status= on sway at the moment, so disabling this.
+
+#+begin_src emacs-lisp :tangle no
+(use-package time
+  :init
+  (setq display-time-default-load-average nil)
+  :config
+  (display-time-mode))
+
+(use-package battery
+  :config
+  (display-battery-mode))
+#+end_src
+
 *** Smaller fringe
 
 *** Smaller fringe
 
-Set fringe to a small value so we don't have big borders in EXWM, but
-can still see the =diff-hl= colors in the fringe.
+Might want to set the fringe to a smaller value, especially if using
+EXWM. I'm fine with the default for now.
 
 #+begin_src emacs-lisp
 
 #+begin_src emacs-lisp
-(fringe-mode '(3 . 1))
+;; (fringe-mode '(3 . 1))
+(fringe-mode nil)
 #+end_src
 
 *** Disable disabled commands
 #+end_src
 
 *** Disable disabled commands
@@ -424,7 +586,61 @@ variable.
 
 #+begin_src emacs-lisp
 (setq backup-by-copying t
 
 #+begin_src emacs-lisp
 (setq backup-by-copying t
-      version-control t)
+      version-control t
+      delete-old-versions t)
+#+end_src
+
+*** Auto revert
+
+Enable automatic reloading of changed buffers and files.
+
+#+begin_src emacs-lisp
+(global-auto-revert-mode 1)
+(setq auto-revert-verbose nil
+      global-auto-revert-non-file-buffers t)
+#+end_src
+
+*** Always use space for indentation
+
+#+begin_src emacs-lisp
+(setq-default
+ indent-tabs-mode nil
+ require-final-newline t
+ tab-width 4)
+#+end_src
+
+*** Winner mode
+
+Enable =winner-mode=.
+
+#+begin_src emacs-lisp
+(winner-mode 1)
+#+end_src
+
+*** Close =*compilation*= on success
+
+#+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)))
+#+end_src
+
+** Bindings
+
+#+begin_src emacs-lisp
+(bind-keys
+ ("C-c b k" . kill-this-buffer)
+ ("C-c s s" . save-buffer)
+ ("C-c b s" . save-buffer)
+ ("C-c S"   . save-buffer)
+ ("C-c o"   . other-window)
+ ("C-c q q" . save-buffers-kill-terminal))
 #+end_src
 
 ** Packages
 #+end_src
 
 ** Packages
@@ -443,33 +659,14 @@ customizing it.
   (auto-compile-on-load-mode)
   (auto-compile-on-save-mode)
   (setq auto-compile-display-buffer               nil
   (auto-compile-on-load-mode)
   (auto-compile-on-save-mode)
   (setq auto-compile-display-buffer               nil
-       auto-compile-mode-line-counter            t
-       auto-compile-source-recreate-deletes-dest t
-       auto-compile-toggle-deletes-nonlib-dest   t
-       auto-compile-update-autoloads             t)
+        auto-compile-mode-line-counter            t
+        auto-compile-source-recreate-deletes-dest t
+        auto-compile-toggle-deletes-nonlib-dest   t
+        auto-compile-update-autoloads             t)
   (add-hook 'auto-compile-inhibit-compile-hook
             'auto-compile-inhibit-compile-detached-git-head))
 #+end_src
 
   (add-hook 'auto-compile-inhibit-compile-hook
             'auto-compile-inhibit-compile-detached-git-head))
 #+end_src
 
-*** TODO [[https://github.com/Kungsgeten/ryo-modal][ryo-modal]]
-
-#+begin_quote
-Roll your own modal mode
-#+end_quote
-
-*** [[https://github.com/ch11ng/exwm][EXWM]] (window manager)
-
-#+begin_src emacs-lisp
-;; (use-package exwm
-;;   :config
-;;   (require 'exwm-config)
-;;   (exwm-config-default)
-;;   (require 'exwm-systemtray)
-;;   (exwm-systemtray-enable)
-;;   (require 'exwm-randr)
-;;   (exwm-randr-enable))
-#+end_src
-
 *** [[https://orgmode.org/][Org mode]]
 
 #+begin_quote
 *** [[https://orgmode.org/][Org mode]]
 
 #+begin_quote
@@ -481,9 +678,66 @@ system.
 In short, my favourite way of life.
 
 #+begin_src emacs-lisp
 In short, my favourite way of life.
 
 #+begin_src emacs-lisp
-(setq org-src-tab-acts-natively t
-      org-src-preserve-indentation nil
-      org-edit-src-content-indentation 0)
+(use-package org
+  :config
+  (setq org-src-tab-acts-natively t
+        org-src-preserve-indentation nil
+        org-edit-src-content-indentation 0
+        org-log-done 'time)
+  :hook (org-mode . org-indent-mode))
+
+(use-package org-notmuch
+  :after (:any org notmuch))
+
+(use-package orgalist)
+#+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
+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
+    "Keep *emacs* async buffers around for later inspection.")
+
+  (defvar amin-show-async-tangle-time nil
+    "Show the time spent tangling the file.")
+
+  (defvar amin-async-tangle-post-compile "make ti"
+    "If non-nil, pass to `compile' after successful tangle.")
+
+  (defun amin/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-start
+       `(lambda ()
+          (require 'org)
+          (org-babel-tangle-file ,file))
+       (unless amin-show-async-tangle-results
+         `(lambda (result)
+            (if result
+                (progn
+                  (message "Tangled %s%s"
+                           ,file-nodir
+                           (if amin-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)))
+              (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))
 #+end_src
 
 *** [[https://magit.vc/][Magit]]
 #+end_src
 
 *** [[https://magit.vc/][Magit]]
@@ -497,9 +751,24 @@ Not just how I do git, but /the/ way to do git.
 #+begin_src emacs-lisp
 (use-package magit
   :defer t
 #+begin_src emacs-lisp
 (use-package magit
   :defer t
-  :bind (("s-g"     . magit-status)
-        ("C-x g"   . magit-status)
-         ("C-x M-g" . magit-dispatch-popup))
+  :bind
+  (("s-g"   . magit-dispatch-popup)
+   ("C-x g" . magit-status)
+   :prefix-map amin--magit-prefix-map
+   :prefix "C-c g"
+   ("SPC" . magit-status)
+   ("s"   . magit-status)
+   ("S"   . magit-status-prefix)
+   ("B"   . magit-blame)
+   ("C"   . magit-clone)
+   ("f"   . magit-fetch)
+   ("F"   . magit-pull)
+   ("P"   . magit-push)
+   ("p"   . magit-dispatch-popup)
+   ("c c" . magit-commit)
+   ("c a" . magit-commit-amend)
+   ("b b" . magit-checkout)
+   ("b c" . magit-branch))
   :config
   (magit-add-section-hook 'magit-status-sections-hook
                           'magit-insert-modules
   :config
   (magit-add-section-hook 'magit-status-sections-hook
                           'magit-insert-modules
@@ -519,39 +788,131 @@ There's no way I could top that, so I won't attempt to.
 **** Ivy
 
 #+begin_src emacs-lisp
 **** Ivy
 
 #+begin_src emacs-lisp
-;; (use-package ivy
-;;   :bind
-;;   (:map ivy-minibuffer-map
-;;         ([escape] . keyboard-escape-quit)
-;;         ("C-j"    . ivy-next-line)
-;;         ("C-k"    . ivy-previous-line)
-;;         ([S-up]   . ivy-previous-history-element)
-;;         ([S-down] . ivy-next-history-element)
-;;         ("DEL"    . ivy-backward-delete-char))
-;;   :config
-;;   (ivy-mode 1))
+(use-package ivy
+  :defer 1
+  :bind
+  (("C-c b b" . ivy-switch-buffer)
+   :map ivy-minibuffer-map
+   ([escape] . keyboard-escape-quit)
+   ([S-up]   . ivy-previous-history-element)
+   ([S-down] . ivy-next-history-element)
+   ("DEL"    . ivy-backward-delete-char))
+  :config
+  (setq ivy-wrap t)
+  (ivy-mode 1))
 #+end_src
 
 **** Swiper
 
 #+begin_src emacs-lisp
 #+end_src
 
 **** Swiper
 
 #+begin_src emacs-lisp
-;; (use-package swiper
-;;   :bind (([remap isearch-forward]  . swiper)
-;;      ([remap isearch-backward] . swiper)))
+(use-package swiper
+  :bind (([remap isearch-forward]  . swiper)
+         ([remap isearch-backward] . swiper)))
 #+end_src
 
 **** Counsel
 
 #+begin_src emacs-lisp
 #+end_src
 
 **** Counsel
 
 #+begin_src emacs-lisp
-;; (use-package counsel
-;;   :bind (([remap execute-extended-command] . counsel-M-x)
-;;      ([remap find-file] . counsel-find-file)
-;;      ("s-r"     . counsel-recentf)
-;;      :map minibuffer-local-map
-;;      ("C-r" . counsel-minibuffer-history))
-;;   :config
-;;   (counsel-mode 1)
-;;   (defalias 'locate #'counsel-locate))
+(use-package counsel
+  :defer 1
+  :bind (([remap execute-extended-command] . counsel-M-x)
+         ([remap find-file]                . counsel-find-file)
+         ("s-r"                            . counsel-recentf)
+         ("C-c x"                          . counsel-M-x)
+         ("C-c f ."                        . counsel-find-file)
+         ("C-c f r"                        . counsel-recentf)
+         :map minibuffer-local-map
+         ("C-r" . counsel-minibuffer-history))
+  :config
+  (counsel-mode 1)
+  (defalias 'locate #'counsel-locate))
+#+end_src
+
+*** eshell
+
+#+begin_src emacs-lisp
+(use-package eshell
+  :commands eshell
+  :config
+  (eval-when-compile (defvar eshell-prompt-regexp))
+  (defun amin/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 ()
+    (interactive)
+    (let ((inhibit-read-only t))
+      (erase-buffer))
+    (eshell-send-input))
+
+  (defun amin|eshell-setup ()
+    (bind-keys :map eshell-mode-map
+               ("C-d" . amin/eshell-quit-or-delete-char)
+               ("C-l" . amin/eshell-clear)))
+
+  :hook (eshell-mode . amin|eshell-setup))
+#+end_src
+
+*** Ibuffer
+
+#+begin_src emacs-lisp
+(use-package ibuffer
+  :bind
+  (("C-x C-b" . ibuffer-other-window)
+   ("C-c b B" . ibuffer-other-window)
+   :map ibuffer-mode-map
+   ("P"   . ibuffer-backward-filter-group)
+   ("N"   . ibuffer-forward-filter-group)
+   ("M-p" . ibuffer-do-print)
+   ("M-n" . ibuffer-do-shell-command-pipe-replace))
+  :config
+  ;; Use human readable Size column instead of original one
+  (define-ibuffer-column size-h
+    (:name "Size" :inline t)
+    (cond
+     ((> (buffer-size) 1000000) (format "%7.1fM" (/ (buffer-size) 1000000.0)))
+     ((> (buffer-size) 100000) (format "%7.0fk" (/ (buffer-size) 1000.0)))
+     ((> (buffer-size) 1000) (format "%7.1fk" (/ (buffer-size) 1000.0)))
+     (t (format "%8d" (buffer-size)))))
+  :custom
+  (ibuffer-saved-filter-groups
+   '(("default"
+      ("dired" (mode . dired-mode))
+      ("org"   (name . "^.*org$"))
+      ("web"
+       (or
+        (mode . web-mode)
+        (mode . css-mode)
+        (mode . scss-mode)
+        (mode . js2-mode)))
+      ("shell"
+       (or
+        (mode . eshell-mode)
+        (mode . shell-mode)))
+      ("notmuch" (name . "\*notmuch\*"))
+      ("programming"
+       (or
+        (mode . python-mode)
+        (mode . c++-mode)
+        (mode . emacs-lisp-mode)))
+      ("emacs"
+       (or
+        (name . "^\\*scratch\\*$")
+        (name . "^\\*Messages\\*$"))))))
+  (ibuffer-formats
+   '((mark modified read-only locked " "
+           (name 18 18 :left :elide)
+           " "
+           (size-h 9 -1 :right)
+           " "
+           (mode 16 16 :left :elide)
+           " " filename-and-process)
+     (mark " "
+           (name 16 -1)
+           " " filename)))
+  :hook (ibuffer . (lambda () (ibuffer-switch-to-saved-filter-groups "default"))))
 #+end_src
 
 * Borg's =layer/essentials=
 #+end_src
 
 * Borg's =layer/essentials=
@@ -630,9 +991,696 @@ TODO: break this giant source block down into individual org sections.
                (list (regexp-quote (system-name)) nil nil)))
 
 (use-package undo-tree
                (list (regexp-quote (system-name)) nil nil)))
 
 (use-package undo-tree
+  :bind (("C-?" . undo-tree-undo)
+         ("M-_" . undo-tree-redo))
   :config
   (global-undo-tree-mode)
   :config
   (global-undo-tree-mode)
-  (setq undo-tree-mode-lighter ""))
+  (setq undo-tree-mode-lighter ""
+        undo-tree-auto-save-history t))
+#+end_src
+
+* Editing
+
+** Company
+
+#+begin_src emacs-lisp
+(use-package company
+  :defer 5
+  :bind
+  (:map company-active-map
+        ([tab] . company-complete-common-or-cycle))
+  :custom
+  (company-idle-delay 0.3)
+  (company-minimum-prefix-length 1)
+  (company-selection-wrap-around t)
+  (company-dabbrev-char-regexp "\\sw\\|\\s_\\|[-_]")
+  (company-dabbrev-downcase nil)
+  (company-dabbrev-ignore-case nil)
+  :config
+  (global-company-mode t))
+#+end_src
+
+* Syntax and spell checking
+#+begin_src emacs-lisp
+(use-package flycheck
+  :hook (prog-mode . flycheck-mode)
+  :config
+  ;; Use the load-path from running Emacs when checking elisp files
+  (setq flycheck-emacs-lisp-load-path 'inherit)
+
+  ;; Only flycheck when I actually save the buffer
+  (setq flycheck-check-syntax-automatically '(mode-enabled save)))
+#+end_src
+* Programming modes
+
+** [[http://alloytools.org][Alloy]] (with [[https://github.com/dwwmmn/alloy-mode][alloy-mode]])
+
+#+begin_src emacs-lisp
+(use-package alloy-mode
+  :config (setq alloy-basic-offset 2))
+#+end_src
+
+** [[https://coq.inria.fr][Coq]] (with [[https://github.com/ProofGeneral/PG][Proof General]])
+
+#+begin_src emacs-lisp
+(use-package proof-site  ; Proof General
+  :load-path "lib/proof-site/generic/")
+#+end_src
+
+** [[https://leanprover.github.io][Lean]] (with [[https://github.com/leanprover/lean-mode][lean-mode]])
+
+#+begin_src emacs-lisp
+(eval-when-compile (defvar lean-mode-map))
+(use-package lean-mode
+  :bind (:map lean-mode-map
+              ("S-SPC" . company-complete)))
+#+end_src
+
+** Haskell
+
+*** [[https://github.com/haskell/haskell-mode][haskell-mode]]
+
+#+begin_src emacs-lisp
+(use-package haskell-mode
+  :config
+  (setq haskell-indentation-layout-offset 4
+        haskell-indentation-left-offset 4
+        flycheck-checker 'haskell-hlint
+        flycheck-disabled-checkers '(haskell-stack-ghc haskell-ghc)))
+#+end_src
+
+*** [[https://github.com/jyp/dante][dante]]
+
+#+begin_src emacs-lisp
+(use-package dante
+  :after haskell-mode
+  :commands dante-mode
+  :hook (haskell-mode . dante-mode))
+#+end_src
+
+*** [[https://github.com/mpickering/hlint-refactor-mode][hlint-refactor]]
+
+Emacs bindings for [[https://github.com/ndmitchell/hlint][hlint]]'s refactor option. This requires the refact
+executable from [[https://github.com/mpickering/apply-refact][apply-refact]].
+
+#+begin_src emacs-lisp
+(use-package hlint-refactor
+  :bind (:map hlint-refactor-mode-map
+              ("C-c l b" . hlint-refactor-refactor-buffer)
+              ("C-c l r" . hlint-refactor-refactor-at-point))
+  :hook (haskell-mode . hlint-refactor-mode))
+#+end_src
+
+*** [[https://github.com/flycheck/flycheck-haskell][flycheck-haskell]]
+
+#+begin_src emacs-lisp
+(use-package flycheck-haskell)
+#+end_src
+
+*** [[https://github.com/ndmitchell/hlint/blob/20e116a043f2073c57b17b24ae6364b5e433ba7e/data/hs-lint.el][hs-lint.el]]
+:PROPERTIES:
+:header-args+: :tangle lisp/hs-lint.el :mkdirp yes
+:END:
+
+Currently using =flycheck-haskell= with the =haskell-hlint= checker
+instead.
+
+#+begin_src emacs-lisp :tangle no
+;;; hs-lint.el --- minor mode for HLint code checking
+
+;; Copyright 2009 (C) Alex Ott
+;;
+;; Author: Alex Ott <alexott@gmail.com>
+;; Keywords: haskell, lint, HLint
+;; Requirements:
+;; Status: distributed under terms of GPL2 or above
+
+;; Typical message from HLint looks like:
+;;
+;; /Users/ott/projects/lang-exp/haskell/test.hs:52:1: Eta reduce
+;; Found:
+;;   count1 p l = length (filter p l)
+;; Why not:
+;;   count1 p = length . filter p
+
+
+(require 'compile)
+
+(defgroup hs-lint nil
+  "Run HLint as inferior of Emacs, parse error messages."
+  :group 'tools
+  :group 'haskell)
+
+(defcustom hs-lint-command "hlint"
+  "The default hs-lint command for \\[hlint]."
+  :type 'string
+  :group 'hs-lint)
+
+(defcustom hs-lint-save-files t
+  "Save modified files when run HLint or no (ask user)"
+  :type 'boolean
+  :group 'hs-lint)
+
+(defcustom hs-lint-replace-with-suggestions nil
+  "Replace user's code with suggested replacements"
+  :type 'boolean
+  :group 'hs-lint)
+
+(defcustom hs-lint-replace-without-ask nil
+  "Replace user's code with suggested replacements automatically"
+  :type 'boolean
+  :group 'hs-lint)
+
+(defun hs-lint-process-setup ()
+  "Setup compilation variables and buffer for `hlint'."
+  (run-hooks 'hs-lint-setup-hook))
+
+;; regex for replace suggestions
+;;
+;; ^\(.*?\):\([0-9]+\):\([0-9]+\): .*
+;; Found:
+;; \s +\(.*\)
+;; Why not:
+;; \s +\(.*\)
+
+(defvar hs-lint-regex
+  "^\\(.*?\\):\\([0-9]+\\):\\([0-9]+\\): .*[\n\C-m]Found:[\n\C-m]\\s +\\(.*\\)[\n\C-m]Why not:[\n\C-m]\\s +\\(.*\\)[\n\C-m]"
+  "Regex for HLint messages")
+
+(defun make-short-string (str maxlen)
+  (if (< (length str) maxlen)
+      str
+    (concat (substring str 0 (- maxlen 3)) "...")))
+
+(defun hs-lint-replace-suggestions ()
+  "Perform actual replacement of suggestions"
+  (goto-char (point-min))
+  (while (re-search-forward hs-lint-regex nil t)
+    (let* ((fname (match-string 1))
+          (fline (string-to-number (match-string 2)))
+          (old-code (match-string 4))
+          (new-code (match-string 5))
+          (msg (concat "Replace '" (make-short-string old-code 30)
+                       "' with '" (make-short-string new-code 30) "'"))
+          (bline 0)
+          (eline 0)
+          (spos 0)
+          (new-old-code ""))
+      (save-excursion
+        (switch-to-buffer (get-file-buffer fname))
+        (goto-char (point-min))
+        (forward-line (1- fline))
+        (beginning-of-line)
+        (setf bline (point))
+        (when (or hs-lint-replace-without-ask
+                  (yes-or-no-p msg))
+          (end-of-line)
+          (setf eline (point))
+          (beginning-of-line)
+          (setf old-code (regexp-quote old-code))
+          (while (string-match "\\\\ " old-code spos)
+            (setf new-old-code (concat new-old-code
+                                 (substring old-code spos (match-beginning 0))
+                                 "\\ *"))
+            (setf spos (match-end 0)))
+          (setf new-old-code (concat new-old-code (substring old-code spos)))
+          (remove-text-properties bline eline '(composition nil))
+          (when (re-search-forward new-old-code eline t)
+            (replace-match new-code nil t)))))))
+
+(defun hs-lint-finish-hook (buf msg)
+  "Function, that is executed at the end of HLint execution"
+  (if hs-lint-replace-with-suggestions
+      (hs-lint-replace-suggestions)
+      (next-error 1 t)))
+
+(define-compilation-mode hs-lint-mode "HLint"
+  "Mode for check Haskell source code."
+  (set (make-local-variable 'compilation-process-setup-function)
+       'hs-lint-process-setup)
+  (set (make-local-variable 'compilation-disable-input) t)
+  (set (make-local-variable 'compilation-scroll-output) nil)
+  (set (make-local-variable 'compilation-finish-functions)
+       (list 'hs-lint-finish-hook))
+  )
+
+(defun hs-lint ()
+  "Run HLint for current buffer with haskell source"
+  (interactive)
+  (save-some-buffers hs-lint-save-files)
+  (compilation-start (concat hs-lint-command " \"" buffer-file-name "\"")
+                     'hs-lint-mode))
+
+(provide 'hs-lint)
+;;; hs-lint.el ends here
+#+end_src
+
+#+begin_src emacs-lisp :tangle no
+(use-package hs-lint
+  :load-path "lisp/"
+  :bind (:map haskell-mode-map
+              ("C-c l l" . hs-lint)))
+#+end_src
+
+** Web dev
+
+*** SGML and HTML
+
+#+begin_src emacs-lisp
+(use-package sgml-mode
+  :config
+  (setq sgml-basic-offset 2))
+#+end_src
+
+*** CSS and SCSS
+
+#+begin_src emacs-lisp
+(use-package css-mode
+  :config
+  (setq css-indent-offset 2))
+#+end_src
+
+*** Web mode
+
+#+begin_src emacs-lisp
+(use-package web-mode
+  :mode "\\.html\\'"
+  :config
+  (setq-every! 2
+    web-mode-code-indent-offset
+    web-mode-css-indent-offset
+    web-mode-markup-indent-offset))
+#+end_src
+
+*** Emmet mode
+
+#+begin_src emacs-lisp
+(use-package emmet-mode
+  :bind* (("C-)" . emmet-next-edit-point)
+          ("C-(" . emmet-prev-edit-point))
+  :config
+  (unbind-key "C-j" emmet-mode-keymap)
+  (setq emmet-move-cursor-between-quotes t)
+  :hook (web-mode css-mode html-mode sgml-mode))
+#+end_src
+
+* Emacs Enhancements
+
+** [[https://github.com/justbur/emacs-which-key][which-key]]
+
+#+begin_quote
+Emacs package that displays available keybindings in popup
+#+end_quote
+
+#+begin_src emacs-lisp
+(use-package which-key
+  :defer 1
+  :config (which-key-mode))
+#+end_src
+
+** [[https://github.com/seagle0128/doom-modeline][doom-modeline]]
+
+#+begin_src emacs-lisp
+(use-package doom-modeline
+  :demand t
+  :config (setq doom-modeline-height 32)
+  :hook (after-init . doom-modeline-init))
+#+end_src
+
+** [[https://github.com/11111000000/tao-theme-emacs][tao-theme]]
+
+#+begin_src emacs-lisp :tangle no
+(use-package tao-theme
+  :demand t
+  :config (load-theme 'tao-yang t))
+#+end_src
+
+** [[https://github.com/maio/eink-emacs][eink-theme]]
+
+#+begin_src emacs-lisp
+(load-theme 'eink t)
+#+end_src
+
+** [[https://github.com/bbatsov/crux][crux]]
+
+#+begin_src emacs-lisp
+(use-package crux
+  :bind (("C-c d"   . crux-duplicate-current-line-or-region)
+         ("C-c M-d" . crux-duplicate-and-comment-current-line-or-region)
+         ("C-c b K" . crux-kill-other-buffers)
+         ("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-S-j"   . crux-top-join-line)
+         ("C-c j"   . crux-top-join-line)))
+#+end_src
+
+** [[https://github.com/alezost/mwim.el][mwim]]
+
+#+begin_src emacs-lisp
+(use-package mwim
+  :bind (("C-a"    . mwim-beginning-of-code-or-line)
+         ("C-e"    . mwim-end-of-code-or-line)
+         ("<home>" . mwim-beginning-of-line-or-code)
+         ("<end>"  . mwim-end-of-line-or-code)))
+#+end_src
+
+** projectile
+
+#+begin_src emacs-lisp
+(use-package projectile
+  :defer 5
+  :bind-keymap ("C-c p" . projectile-command-map)
+  :config
+  (projectile-mode)
+
+  (defun my-projectile-invalidate-cache (&rest _args)
+    ;; ignore the args to `magit-checkout'
+    (projectile-invalidate-cache nil))
+
+  (eval-after-load 'magit-branch
+    '(progn
+       (advice-add 'magit-checkout
+                   :after #'my-projectile-invalidate-cache)
+       (advice-add 'magit-branch-and-checkout
+                   :after #'my-projectile-invalidate-cache))))
+#+end_src
+
+** [[https://github.com/Wilfred/helpful][helpful]]
+
+#+begin_src emacs-lisp
+(use-package helpful
+  :bind
+  (("C-h f"   . helpful-callable)
+   ("C-h v"   . helpful-variable)
+   ("C-h k"   . helpful-key)
+   ("C-c C-d" . helpful-at-point)
+   ("C-h F"   . helpful-function)
+   ("C-h C"   . helpful-command)))
+#+end_src
+
+** [[https://github.com/kyagi/shell-pop-el][shell-pop]]
+
+#+begin_src emacs-lisp
+(use-package shell-pop
+  :custom
+  (shell-pop-universal-key "C-c e")
+  (shell-pop-shell-type '("eshell" "*eshell*" (lambda nil (eshell)))))
+#+end_src
+
+** [[https://github.com/EricCrosson/unkillable-scratch][unkillable-scratch]]
+
+Make =*scratch*= and =*Messages*= unkillable.
+
+#+begin_src emacs-lisp
+(use-package unkillable-scratch
+  :config
+  (unkillable-scratch 1)
+  :custom
+  (unkillable-buffers '("^\\*scratch\\*$" "^\\*Messages\\*$")))
+#+end_src
+
+** [[https://github.com/davep/boxquote.el][boxquote.el]]
+
+#+begin_example
+,----
+| make pretty boxed quotes like this
+`----
+#+end_example
+
+#+begin_src emacs-lisp
+(use-package boxquote
+  :bind
+  (:prefix-map amin--boxquote-prefix-map
+   :prefix "C-c q"
+   ("b"   . boxquote-buffer)
+   ("B"   . boxquote-insert-buffer)
+   ("d"   . boxquote-defun)
+   ("F"   . boxquote-insert-file)
+   ("hf"  . boxquote-describe-function)
+   ("hk"  . boxquote-describe-key)
+   ("hv"  . boxquote-describe-variable)
+   ("hw"  . boxquote-where-is)
+   ("k"   . boxquote-kill)
+   ("p"   . boxquote-paragraph)
+   ("q"   . boxquote-boxquote)
+   ("r"   . boxquote-region)
+   ("s"   . boxquote-shell-command)
+   ("t"   . boxquote-text)
+   ("T"   . boxquote-title)
+   ("u"   . boxquote-unbox)
+   ("U"   . boxquote-unbox-region)
+   ("y"   . boxquote-yank)
+   ("M-q" . boxquote-fill-paragraph)
+   ("M-w" . boxquote-kill-ring-save)))
+#+end_src
+
+Also see [[https://www.emacswiki.org/emacs/rebox2][rebox2]].
+
+** [[https://github.com/DarthFennec/highlight-indent-guides][highlight-indent-guides]]
+
+#+begin_src emacs-lisp
+(use-package highlight-indent-guides
+  :demand t
+  :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
+
+* Email
+
+#+begin_src emacs-lisp
+(defvar amin-maildir (expand-file-name "~/mail/"))
+(after! recentf
+  (add-to-list 'recentf-exclude amin-maildir))
+#+end_src
+
+** sendmail
+
+#+begin_src emacs-lisp
+(use-package sendmail
+  :config
+  (setq sendmail-program "/usr/bin/msmtp"
+        ;; message-sendmail-extra-arguments '("-v" "-d")
+        mail-specify-envelope-from t
+        mail-envelope-from 'header))
+#+end_src
+
+** message
+
+#+begin_src emacs-lisp
+(use-package message
+  :config
+  (setq message-kill-buffer-on-exit t
+        message-send-mail-function 'message-send-mail-with-sendmail
+        message-sendmail-envelope-from 'header
+        ;; message-directory "drafts"
+        message-user-fqdn "aminb.org")
+  (add-hook 'message-mode-hook
+            (lambda () (setq fill-column 65
+                        message-fill-column 65)))
+  (add-hook 'message-mode-hook
+            #'flyspell-mode)
+  ;; (add-hook 'message-setup-hook
+  ;;           #'mml-secure-message-sign-pgpmime)
+  )
+
+(after! mml-sec
+  (setq mml-secure-openpgp-encrypt-to-self t
+        mml-secure-openpgp-sign-with-sender t))
+#+end_src
+
+** [[https://notmuchmail.org][notmuch]]
+
+See [[notmuch:id:87muuqsvci.fsf@fencepost.gnu.org][bug follow-up]].
+
+#+begin_src emacs-lisp
+(defun amin/notmuch ()
+  "Delete other windows, then launch `notmuch'."
+  (interactive)
+  (delete-other-windows)
+  (notmuch))
+
+(use-package notmuch
+  :commands notmuch
+  :bind ("C-c n" . amin/notmuch)
+  :custom (notmuch-always-prompt-for-sender t)
+  :config
+  (setq notmuch-hello-sections
+        '(notmuch-hello-insert-header
+          notmuch-hello-insert-saved-searches
+          ;; notmuch-hello-insert-search
+          notmuch-hello-insert-alltags)
+        notmuch-search-oldest-first nil
+        notmuch-show-all-tags-list t
+        notmuch-message-headers  ; see bug follow-up above
+        '("Subject" "To" "Cc" "Date" "List-Id" "X-RT-Originator")
+        notmuch-hello-thousands-separator ","
+        notmuch-fcc-dirs
+        '(("amin@aminb.org"        . "amin/Sent")
+          ("amin@gnu.org"          . "gnu/Sent")
+          ("abandali@uwaterloo.ca" . "\"uwaterloo/Sent Items\"")
+          ("mab@gnu.org"           . "gnu/Sent")
+          ("aminb@gnu.org"         . "gnu/Sent")
+          (".*"                    . "sent"))
+        notmuch-search-result-format
+        '(("date"    . "%12s ")
+          ("count"   . "%-7s ")
+          ("authors" . "%-40s ")
+          ("subject" . "%s ")
+          ("tags"    . "(%s)"))
+        notmuch-saved-searches
+        '((:name "inbox"     :query "tag:inbox"     :key "i")
+          (:name "unread"    :query "tag:unread"    :key "u")
+          (:name "latest"    :query "tag:latest"    :key "l")
+          (:name "encrypted" :query "tag:encrypted" :key "e")
+          (:name "flagged"   :query "tag:flagged"   :key "f")
+          (:name "sent"      :query "tag:sent"      :key "s")
+          (:name "drafts"    :query "tag:draft"     :key "d")
+          (:name "all mail"  :query "*"             :key "a")))
+  ;; (add-hook 'visual-fill-column-mode-hook
+  ;;           (lambda ()
+  ;;             (when (string= major-mode 'notmuch-message-mode)
+  ;;               (setq visual-fill-column-width 70))))
+  ;; (set! :evil-state 'notmuch-message-mode 'insert)
+  ;; (advice-add #'notmuch-bury-or-kill-this-buffer
+  ;;             :override #'kill-this-buffer)
+  :bind
+  (:map notmuch-hello-mode-map
+        ("u" . (lambda ()
+                 "Search for `unread'-tagged messages"
+                 (interactive)
+                 (notmuch-hello-search "tag:unread")))
+        ("i" . (lambda ()
+                 "Search for `inbox'-tagged messages"
+                 (interactive)
+                 (notmuch-hello-search "tag:inbox")))
+        ("l" . (lambda ()
+                 "Search for `latest'-tagged messages"
+                 (interactive)
+                 (notmuch-hello-search "tag:latest")))
+        ("e" . (lambda ()
+                 "Search for `encrypted'-tagged messages"
+                 (interactive)
+                 (notmuch-hello-search "tag:encrypted"))))
+  (:map notmuch-search-mode-map
+        ("k" . (lambda ()
+                 "Mark message read"
+                 (interactive)
+                 (notmuch-search-tag '("-unread"))
+                 ;; (notmuch-search-archive-thread)
+                 (notmuch-search-next-thread)))
+        ("u" . (lambda ()
+                 "Mark message unread"
+                 (interactive)
+                 (notmuch-search-tag '("+unread"))
+                 (notmuch-search-next-thread)))
+        ("K" . (lambda ()
+                 "Mark message deleted"
+                 (interactive)
+                 (notmuch-search-tag '("-unread" "-inbox" "+deleted"))
+                 (notmuch-search-next-thread)))
+        ("S" . (lambda ()
+                 "Mark message as spam"
+                 (interactive)
+                 (notmuch-search-tag '("-unread" "-inbox" "-webmasters" "+spam"))
+                 (notmuch-search-next-thread))))
+  (:map notmuch-tree-mode-map
+        ("k" . (lambda ()
+                 "Mark message read"
+                 (interactive)
+                 (notmuch-tree-tag '("-unread"))
+                 ;; (notmuch-tree-archive-thread)
+                 (notmuch-tree-next-message)))
+        ("u" . (lambda ()
+                 "Mark message unread"
+                 (interactive)
+                 (notmuch-tree-tag '("+unread"))
+                 (notmuch-tree-next-message)))
+        ("K" . (lambda ()
+                 "Mark message deleted"
+                 (interactive)
+                 (notmuch-tree-tag '("-unread" "-inbox" "+deleted"))
+                 (notmuch-tree-next-message)))
+        ("S" . (lambda ()
+                 "Mark message as spam"
+                 (interactive)
+                 (notmuch-tree-tag '("-unread" "-inbox" "-webmasters" "+spam"))
+                 (notmuch-tree-next-message)))))
+
+(use-package counsel-notmuch
+  :bind ("C-c s m" . counsel-notmuch))
+
+(after! notmuch-crypto
+  (setq notmuch-crypto-process-mime t))
+#+end_src
+
+** supercite
+
+#+begin_src emacs-lisp :tangle no
+(use-package supercite
+  :commands sc-cite-original
+  :init
+  (add-hook 'mail-citation-hook 'sc-cite-original)
+
+  (defun sc-remove-existing-signature ()
+    (save-excursion
+      (goto-char (region-beginning))
+      (when (re-search-forward message-signature-separator (region-end) t)
+        (delete-region (match-beginning 0) (region-end)))))
+
+  (add-hook 'mail-citation-hook 'sc-remove-existing-signature)
+
+  (defun sc-remove-if-not-mailing-list ()
+    (unless (assoc "list-id" sc-mail-info)
+      (setq attribution sc-default-attribution
+            citation (concat sc-citation-delimiter
+                             sc-citation-separator))))
+
+  (add-hook 'sc-attribs-postselect-hook 'sc-remove-if-not-mailing-list)
+
+  :config
+  (defun sc-fill-if-different (&optional prefix)
+    "Fill the region bounded by `sc-fill-begin' and point.
+Only fill if optional PREFIX is different than
+`sc-fill-line-prefix'.  If `sc-auto-fill-region-p' is nil, do not
+fill region.  If PREFIX is not supplied, initialize fill
+variables.  This is useful for a regi `begin' frame-entry."
+    (if (not prefix)
+        (setq sc-fill-line-prefix ""
+              sc-fill-begin (line-beginning-position))
+      (if (and sc-auto-fill-region-p
+               (not (string= prefix sc-fill-line-prefix)))
+          (let ((fill-prefix sc-fill-line-prefix))
+            (unless (or (string= fill-prefix "")
+                        (save-excursion
+                          (goto-char sc-fill-begin)
+                          (or (looking-at ">+  +")
+                              (< (length
+                                  (buffer-substring (point)
+                                                    (line-end-position)))
+                                 65))))
+              (fill-region sc-fill-begin (line-beginning-position)))
+            (setq sc-fill-line-prefix prefix
+                  sc-fill-begin (line-beginning-position)))))
+nil))
+#+end_src
+
+* Blogging
+** [[https://ox-hugo.scripter.co][ox-hugo]]
+
+#+begin_src emacs-lisp
+(use-package ox-hugo
+  :after ox)
+
+(use-package ox-hugo-auto-export
+  :load-path "lib/ox-hugo")
 #+end_src
 
 * Post initialization
 #+end_src
 
 * Post initialization
@@ -645,7 +1693,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)
 #+begin_src emacs-lisp
 (message "Loading %s...done (%.3fs)" user-init-file
          (float-time (time-subtract (current-time)
-                                    ab--before-user-init-time)))
+                                    amin--before-user-init-time)))
 #+end_src
 
 * Footer
 #+end_src
 
 * Footer
@@ -656,3 +1704,8 @@ Display how long it took to load the init file.
 #+begin_src emacs-lisp :comments none
 ;;; init.el ends here
 #+end_src
 #+begin_src emacs-lisp :comments none
 ;;; init.el ends here
 #+end_src
+
+* COMMENT Local Variables                                           :ARCHIVE:
+# Local Variables:
+# eval: (add-hook 'after-save-hook #'amin/async-babel-tangle 'append 'local)
+# End: