+** hl-todo
+
+#+begin_src emacs-lisp
+(use-package hl-todo
+ :defer 4
+ :config
+ (global-hl-todo-mode))
+#+end_src
+
+** shrink-path
+
+#+begin_src emacs-lisp
+(use-package shrink-path
+ :after eshell
+ :config
+ (setq eshell-prompt-regexp "\\(.*\n\\)*λ "
+ eshell-prompt-function #'+eshell/prompt)
+
+ (defun +eshell/prompt ()
+ (let ((base/dir (shrink-path-prompt default-directory)))
+ (concat (propertize (car base/dir)
+ 'face 'font-lock-comment-face)
+ (propertize (cdr base/dir)
+ 'face 'font-lock-constant-face)
+ (propertize (+eshell--current-git-branch)
+ 'face 'font-lock-function-name-face)
+ "\n"
+ (propertize "λ" 'face 'eshell-prompt-face)
+ ;; needed for the input text to not have prompt face
+ (propertize " " 'face 'default))))
+
+ (defun +eshell--current-git-branch ()
+ (let ((branch (car (loop for match in (split-string (shell-command-to-string "git branch") "\n")
+ when (string-match "^\*" match)
+ collect match))))
+ (if (not (eq branch nil))
+ (concat " " (substring branch 2))
+ ""))))
+#+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
+(use-package eshell-up
+ :after eshell)
+#+end_src
+
+** multi-term
+
+#+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))
+ :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
+ (global-page-break-lines-mode))
+#+end_src
+
+** expand-region
+
+#+begin_src emacs-lisp
+(use-package expand-region
+ :bind ("C-=" . er/expand-region))
+#+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))
+#+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
+ :bind (("C-c m" . gnus)
+ ("C-c M" . gnus-unplugged)
+ ("s-m" . gnus)
+ ("s-M" . gnus-unplugged))
+ :init
+ (setq
+ gnus-select-method '(nnnil "")
+ gnus-secondary-select-methods
+ '((nnimap "amin"
+ (nnimap-stream plain)
+ (nnimap-address "127.0.0.1")
+ (nnimap-server-port 143)
+ (nnimap-authenticator plain)
+ (nnimap-user "amin@aminb.org"))
+ (nnimap "uwaterloo"
+ (nnimap-stream plain)
+ (nnimap-address "127.0.0.1")
+ (nnimap-server-port 143)
+ (nnimap-authenticator plain)
+ (nnimap-user "abandali@uwaterloo.ca")))
+ gnus-message-archive-group "nnimap+amin:Sent"
+ gnus-parameters
+ '(("gnu.*"
+ (gcc-self . t)))
+ gnus-large-newsgroup 50
+ gnus-home-directory (no-littering-expand-var-file-name "gnus/")
+ gnus-directory (concat gnus-home-directory "news/")
+ message-directory (concat gnus-home-directory "mail/")
+ nndraft-directory (concat gnus-home-directory "drafts/")
+ gnus-save-newsrc-file nil
+ gnus-read-newsrc-file nil
+ gnus-interactive-exit nil
+ gnus-gcc-mark-as-read t))
+
+(use-package gnus-art
+ :config
+ (setq
+ gnus-visible-headers
+ (concat gnus-visible-headers "\\|^List-Id:\\|^X-RT-Originator:\\|^User-Agent:")
+ gnus-sorted-header-list
+ '("^From:" "^Subject:" "^Summary:" "^Keywords:"
+ "^Followup-To:" "^To:" "^Cc:" "X-RT-Originator"
+ "^Newsgroups:" "List-Id:" "^Organization:"
+ "^User-Agent:" "^Date:")
+ ;; local-lapsed article dates
+ ;; from https://www.emacswiki.org/emacs/GnusFormatting#toc11
+ gnus-article-date-headers '(user-defined)
+ gnus-article-time-format
+ (lambda (time)
+ (let* ((date (format-time-string "%a, %d %b %Y %T %z" time))
+ (local (article-make-date-line date 'local))
+ (combined-lapsed (article-make-date-line date
+ 'combined-lapsed))
+ (lapsed (progn
+ (string-match " (.+" combined-lapsed)
+ (match-string 0 combined-lapsed))))
+ (concat local lapsed))))
+ (bind-keys
+ :map gnus-article-mode-map
+ ("r" . gnus-article-reply-with-original)
+ ("R" . gnus-article-wide-reply-with-original)
+ ("M-L" . org-store-link)))
+
+(use-package gnus-sum
+ :bind (:map gnus-summary-mode-map
+ :prefix-map amin--gnus-summary-prefix-map
+ :prefix "v"
+ ("r" . gnus-summary-reply)
+ ("w" . gnus-summary-wide-reply)
+ ("v" . gnus-summary-show-raw-article))
+ :config
+ (bind-keys
+ :map gnus-summary-mode-map
+ ("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))
+
+(use-package gnus-msg
+ :config
+ (setq gnus-posting-styles
+ '((".*"
+ (address "amin@aminb.org")
+ (body "\nBest,\namin\n")
+ (eval (setq amin--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)))
+ ("nnimap\\+uwaterloo:.*"
+ (address "abandali@uwaterloo.ca")
+ (gcc "\"nnimap+uwaterloo:Sent Items\"")))))
+
+(use-package gnus-topic
+ :hook (gnus-group-mode . gnus-topic-mode))
+
+(use-package gnus-agent
+ :config
+ (setq gnus-agent-synchronize-flags 'ask)
+ :hook (gnus-group-mode . gnus-agent-mode))
+
+(use-package gnus-group
+ :config
+ (setq gnus-permanently-visible-groups "\\((INBOX\\|gnu$\\)"))
+
+(use-package mm-decode
+ :config
+ (setq mm-discouraged-alternatives '("text/html" "text/richtext")))
+#+end_src
+
+** sendmail
+
+#+begin_src emacs-lisp