[emacs] Update 2 drones
[~bandali/configs] / init.org
index ae458c7..d8563fd 100644 (file)
--- a/init.org
+++ b/init.org
@@ -104,28 +104,12 @@ 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|...   a hook function
-;;   amin*...   an advising function
-;;   amin@...   a hydra command
-;;   ...!       a macro
-#+end_src
-
 * Initial setup
 :PROPERTIES:
 :CUSTOM_ID: initial-setup
@@ -138,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
 
@@ -151,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
@@ -168,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*=
@@ -286,7 +270,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=
@@ -433,7 +422,7 @@ Let’s define a function to conveniently disable this for certain
 buffers and/or modes.
 
 #+begin_src emacs-lisp
-(defun amin--no-mouse-autoselect-window ()
+(defun a/no-mouse-autoselect-window ()
   (make-local-variable 'mouse-autoselect-window)
   (setq mouse-autoselect-window nil))
 #+end_src
@@ -447,48 +436,35 @@ buffers and/or modes.
 
 ** 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)
+(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
@@ -659,14 +635,13 @@ Based on https://stackoverflow.com/a/17788551, with changes to use
 =cl-letf= instead of the now obsolete =flet=.
 
 #+begin_src emacs-lisp
-(use-package compile
-  :config
-  (defun amin--compilation-finish-function (buffer outstr)
+(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 #'amin--compilation-finish-function)
+  (setq compilation-finish-functions #'a/compilation-finish-function)
 
   (require 'cl-macs)
 
@@ -693,6 +668,12 @@ for this.
 ;; (setq replace-char-fold t)
 #+end_src
 
+*** Cursor shape
+
+#+begin_src emacs-lisp
+(setq-default cursor-type 'bar)
+#+end_src
+
 ** Bindings
 
 #+begin_src emacs-lisp
@@ -762,6 +743,9 @@ In short, my favourite way of life.
         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)
   (font-lock-add-keywords
@@ -809,22 +793,22 @@ In short, my favourite way of life.
 
 **** 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))
@@ -836,24 +820,24 @@ file.
        `(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]]
@@ -946,30 +930,32 @@ There's no way I could top that, so I won't attempt to.
 (use-package 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 nil)
+    (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))
@@ -1058,7 +1044,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)
@@ -1085,9 +1071,45 @@ TODO: break this giant source block down into individual org sections.
   (global-diff-hl-mode)
   (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh t))
 
+(use-package ls-lisp
+  :custom (ls-lisp-dirs-first t))
+
 (use-package dired
   :defer t
-  :config (setq dired-listing-switches "-alh"))
+  :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))
 
 (use-package eldoc
   :when (version< "25" emacs-version)
@@ -1223,6 +1245,7 @@ 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
@@ -1478,7 +1501,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))
@@ -1609,7 +1632,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
@@ -1636,14 +1703,14 @@ 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)
   (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)
   (mapc #'disable-theme custom-enabled-themes)
@@ -1651,8 +1718,8 @@ Emacs package that displays available keybindings in popup
   (sml/apply-theme 'automatic))
 
 (bind-keys
- ("s-t d" . amin/lights-off)
- ("s-t l" . amin/lights-on))
+ ("s-t d" . a/lights-off)
+ ("s-t l" . a/lights-on))
 #+end_src
 
 ** [[https://github.com/bbatsov/crux][crux]]
@@ -1698,7 +1765,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]]
@@ -1714,53 +1782,6 @@ Emacs package that displays available keybindings in popup
    ("C-S-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
-  :bind ("C-c a s 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))
-#+end_src
-
 ** [[https://github.com/EricCrosson/unkillable-scratch][unkillable-scratch]]
 
 Make =*scratch*= and =*Messages*= unkillable.
@@ -1787,7 +1808,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)
@@ -1813,42 +1834,6 @@ 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]]
-
-#+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
-
 ** typo.el
 
 #+begin_src emacs-lisp
@@ -1899,71 +1884,6 @@ Also see [[https://www.emacswiki.org/emacs/rebox2][rebox2]].
         ""))))
 #+end_src
 
-** COMMENT 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
-
 ** [[https://github.com/peterwvj/eshell-up][eshell-up]]
 
 #+begin_src emacs-lisp
@@ -1976,9 +1896,11 @@ Hopefully temporary.
 #+begin_src emacs-lisp
 (use-package multi-term
   :defer 1
-  :bind (("C-c C-j"     . term-line-mode)
-         ("C-c a s m m" . multi-term)
-         ("C-c a s m p" . multi-term-dedicated-toggle))
+  :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
@@ -2019,29 +1941,41 @@ Hopefully temporary.
   :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)
+ a/gnus-init-file  (no-littering-expand-etc-file-name "gnus")
+ mail-user-agent   'gnus-user-agent
+ read-mail-command 'gnus)
 
 (use-package gnus
-  :bind (("C-c m" . gnus)
-         ("C-c M" . gnus-unplugged)
-         ("s-m"   . gnus)
+  :bind (("s-m"   . gnus)
          ("s-M"   . gnus-unplugged))
   :init
   (setq
@@ -2104,7 +2038,7 @@ Hopefully temporary.
 
 (use-package 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)
@@ -2115,7 +2049,7 @@ Hopefully temporary.
    ("r"   . gnus-summary-reply-with-original)
    ("R"   . gnus-summary-wide-reply-with-original)
    ("M-L" . org-store-link))
-  :hook (gnus-summary-mode . amin--no-mouse-autoselect-window))
+  :hook (gnus-summary-mode . a/no-mouse-autoselect-window))
 
 (use-package gnus-msg
   :config
@@ -2123,13 +2057,13 @@ Hopefully temporary.
         '((".*"
            (address "amin@aminb.org")
            (body "\nBest,\namin\n")
-           (eval (setq amin--message-cite-say-hi t)))
+           (eval (setq a/message-cite-say-hi t)))
           ("gnu.*"
            (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\"")))))
@@ -2167,7 +2101,7 @@ Hopefully temporary.
 #+begin_src emacs-lisp
 (use-package 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)
@@ -2176,9 +2110,9 @@ Hopefully temporary.
       (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
@@ -2200,7 +2134,7 @@ Hopefully temporary.
   ;; (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
@@ -2214,7 +2148,7 @@ Convenient footnotes in =message-mode=.
   :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)
@@ -2229,37 +2163,56 @@ Convenient footnotes in =message-mode=.
         footnote-style     'unicode))
 #+end_src
 
-** bbdb
+** ebdb
+
+#+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)))
+
+(use-package ebdb-com
+  :after 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.
+;; (use-package ebdb-complete
+;;   :after ebdb
+;;   :config
+;;   (ebdb-complete-enable))
 
-#+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-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-package 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-package ebdb-mua
+  :after ebdb
+  ;; :custom (ebdb-mua-pop-up nil)
+  )
 
-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-message
+;;   :after ebdb)
 
-#+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
-        bbdb-complete-mail nil)
-  (bbdb-initialize 'gnus 'message))
+
+;; (use-package ebdb-vcard
+;;   :after ebdb)
 #+end_src
 
 ** COMMENT message-x
@@ -2314,7 +2267,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
@@ -2328,6 +2281,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: