[emacs] add more ryo-modal bindings
[~bandali/configs] / init.org
index 88f5e24..526045a 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]]
@@ -141,10 +167,6 @@ 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 ab--gc-cons-threshold
          gc-cons-percentage ab--gc-cons-percentage
          file-name-handler-alist ab--file-name-handler-alist)))
@@ -166,6 +188,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=
@@ -219,7 +248,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
@@ -264,7 +293,6 @@ contain the mess.
         `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))))
 #+end_src
 
         `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))))
 #+end_src
 
-
 ** Custom file (=custom.el=)
 
 I'm not planning on using the custom file much, but even so, I
 ** Custom file (=custom.el=)
 
 I'm not planning on using the custom file much, but even so, I
@@ -287,15 +315,23 @@ Let's use [[https://github.com/purcell/exec-path-from-shell][exec-path-from-shel
 in my shell.
 
 #+begin_src emacs-lisp
 in my shell.
 
 #+begin_src emacs-lisp
-;; (use-package exec-path-from-shell
-;;   :defer 1
-;;   :init
-;;   (setq exec-path-from-shell-check-startup-files nil)
-;;   :config
-;;   (exec-path-from-shell-initialize)
-;;   ;; while we're at it, let's fix access to our running ssh-agent
-;;   (exec-path-from-shell-copy-env "SSH_AGENT_PID")
-;;   (exec-path-from-shell-copy-env "SSH_AUTH_SOCK"))
+(use-package exec-path-from-shell
+  :defer 1
+  :init
+  (setq exec-path-from-shell-check-startup-files nil)
+  :config
+  (exec-path-from-shell-initialize)
+  ;; while we're at it, let's fix access to our running ssh-agent
+  (exec-path-from-shell-copy-env "SSH_AGENT_PID")
+  (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
 #+end_src
 
 ** Server
@@ -313,6 +349,86 @@ 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 ab-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 (ab-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
+
 * Core
 :PROPERTIES:
 :CUSTOM_ID: core
 * Core
 :PROPERTIES:
 :CUSTOM_ID: core
@@ -320,13 +436,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.
+
+#+begin_src emacs-lisp
+(use-package time
+  :ensure nil
+  :init
+  (setq display-time-default-load-average nil)
+  :config
+  (display-time-mode))
+
+(use-package battery
+  :ensure nil
+  :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
@@ -444,31 +581,62 @@ 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]]
+*** [[https://github.com/Kungsgeten/ryo-modal][ryo-modal]]
 
 #+begin_quote
 Roll your own modal mode
 #+end_quote
 
 
 #+begin_quote
 Roll your own modal mode
 #+end_quote
 
-*** [[https://github.com/ch11ng/exwm][EXWM]] (window manager)
-
 #+begin_src emacs-lisp
 #+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))
+(use-package ryo-modal
+  :commands ryo-modal-mode
+  :bind ("M-m" . ryo-modal-mode)
+  :after which-key
+  :config
+  (push '((nil . "ryo:.*:") . (nil . "")) which-key-replacement-alist)
+  (ryo-modal-keys
+   ("," ryo-modal-repeat)
+   ("b" backward-char)
+   ("n" next-line)
+   ("p" previous-line)
+   ("f" forward-char)
+   ("a" move-beginning-of-line)
+   ("e" move-end-of-line)
+   ("SPC" (("b b" ibuffer-list-buffers)
+           ("b k" kill-this-buffer)
+           ("b o" other-window)
+           ("b s" save-buffer)
+           ("q q" save-buffers-kill-terminal)))
+   ("d" (("w" kill-word)
+         ("d" kill-whole-line)
+         ("b" backward-kill-word)))
+   ("c" (("w" kill-word :exit t)
+         ("c" kill-whole-line :exit t))))
+
+  (ryo-modal-keys
+   ;; First argyment to ryo-modal-keys may be a list of keywords.
+   ;; These keywords will be applied to all keybindings.
+   (:norepeat t)
+   ("0" "M-0")
+   ("1" "M-1")
+   ("2" "M-2")
+   ("3" "M-3")
+   ("4" "M-4")
+   ("5" "M-5")
+   ("6" "M-6")
+   ("7" "M-7")
+   ("8" "M-8")
+   ("9" "M-9"))
+  :hook ((text-mode . ryo-modal-mode)
+         (prog-mode . ryo-modal-mode)))
 #+end_src
 
 *** [[https://orgmode.org/][Org mode]]
 #+end_src
 
 *** [[https://orgmode.org/][Org mode]]
@@ -482,9 +650,23 @@ 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
+  :ryo ("SPC b t" org-babel-tangle)
+  :config
+  (setq org-src-tab-acts-natively t
+        org-src-preserve-indentation nil
+        org-edit-src-content-indentation 0
+        org-html-divs '((preamble  "header" "preamble")
+                        (content   "main"   "content")
+                        (postamble "footer" "postamble"))
+        org-html-doctype "html5"
+        org-html-html5-fancy t
+        org-html-postamble nil)
+  :hook (org-mode . org-indent-mode))
+(use-package htmlize
+  :after org)
+(use-package org-notmuch
+  :after (:any org notmuch))
 #+end_src
 
 *** [[https://magit.vc/][Magit]]
 #+end_src
 
 *** [[https://magit.vc/][Magit]]
@@ -497,9 +679,10 @@ Not just how I do git, but /the/ way to do git.
 
 #+begin_src emacs-lisp
 (use-package magit
 
 #+begin_src emacs-lisp
 (use-package magit
+  :ryo ("SPC" (("g s" magit-status)))
   :defer t
   :bind (("s-g"     . magit-status)
   :defer t
   :bind (("s-g"     . magit-status)
-        ("C-x g"   . magit-status)
+         ("C-x g"   . magit-status)
          ("C-x M-g" . magit-dispatch-popup))
   :config
   (magit-add-section-hook 'magit-status-sections-hook
          ("C-x M-g" . magit-dispatch-popup))
   :config
   (magit-add-section-hook 'magit-status-sections-hook
@@ -520,39 +703,50 @@ 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
+  (: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))
+  :ryo ("SPC ," ivy-switch-buffer)
+  :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
+  :ryo
+  ("SPC /" swiper)
+  ("s" 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
+  :ryo
+  ("SPC" (("f r" counsel-recentf)
+          ("SPC" counsel-M-x)
+          ("."   counsel-find-file)))
+  :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))
 #+end_src
 
 * Borg's =layer/essentials=
 #+end_src
 
 * Borg's =layer/essentials=
@@ -631,11 +825,507 @@ 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
+  :ryo
+  ("/" undo-tree-undo)
+  ("_" undo-tree-redo)
   :config
   (global-undo-tree-mode)
   (setq undo-tree-mode-lighter ""))
 #+end_src
 
   :config
   (global-undo-tree-mode)
   (setq undo-tree-mode-lighter ""))
 #+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_\\|[-_]")
+  :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
+(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
+* 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
+
+* Email
+** [[https://notmuchmail.org][notmuch]]
+
+See [[notmuch:id:87muuqsvci.fsf@fencepost.gnu.org][bug follow-up]].
+
+#+begin_src emacs-lisp
+(defun ab/notmuch ()
+  "Delete other windows, then launch `notmuch'."
+  (interactive)
+  (require 'notmuch)
+  (delete-other-windows)
+  (notmuch))
+
+;; (map!
+;;  :leader
+;;  :desc "notmuch" :n "m" #'ab/notmuch
+;;  (:desc "search" :prefix "/"
+;;    :desc "notmuch" :n "m" #'counsel-notmuch))
+#+end_src
+
+#+begin_src emacs-lisp
+(defvar ab-maildir "~/mail")
+
+(use-package sendmail
+  ;; :ensure nil
+  :config
+  (setq sendmail-program "/usr/bin/msmtp"
+        mail-specify-envelope-from t
+        mail-envelope-from 'header))
+
+(use-package message
+  ;; :ensure nil
+  :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 'notmuch-message-mode-hook #'+doom-modeline|set-special-modeline)
+  ;; TODO: is there a way to only run this when replying and not composing?
+  ;; (add-hook 'notmuch-message-mode-hook
+  ;;           (lambda () (progn
+  ;;                   (newline)
+  ;;                   (newline)
+  ;;                   (forward-line -1)
+  ;;                   (forward-line -1))))
+  ;; (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))
+
+(use-package notmuch
+  :ryo ("SPC m" ab/notmuch)
+  :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")
+          ("abandali@uwaterloo.ca"     . "\"uwaterloo/Sent Items\"")
+          ("amin.bandali@uwaterloo.ca" . "\"uwaterloo/Sent Items\"")
+          ("aminb@gnu.org"             . "gnu/Sent")
+          (".*"                        . "sent"))
+        notmuch-search-result-format
+        '(("date" . "%12s ")
+          ("count" . "%-7s ")
+          ("authors" . "%-40s ")
+          ("subject" . "%s ")
+          ("tags" . "(%s)")))
+  ;; (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
+        ("g" . notmuch-poll-and-refresh-this-buffer)
+        ("i" . (lambda ()
+                 "Search for `inbox' tagged messages"
+                 (interactive)
+                 (notmuch-hello-search "tag:inbox")))
+        ("u" . (lambda ()
+                 "Search for `unread' tagged messages"
+                 (interactive)
+                 (notmuch-hello-search "tag:unread")))
+        ("l" . (lambda ()
+                 "Search for `latest tagged messages"
+                 (interactive)
+                 (notmuch-hello-search "tag:latest")))
+        ("M" . (lambda ()
+                 "Compose new mail and prompt for sender"
+                 (interactive)
+                 (let ((current-prefix-arg t))
+                   (call-interactively #'notmuch-mua-new-mail)))))
+  (:map notmuch-search-mode-map
+        ("g" . notmuch-poll-and-refresh-this-buffer)
+        ("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-archive-thread)))
+        ("S" . (lambda ()
+                 "Mark message as spam"
+                 (interactive)
+                 (notmuch-search-tag '("-unread" "-inbox" "-webmasters" "+spam"))
+                 (notmuch-search-archive-thread))))
+  (:map notmuch-tree-mode-map  ; TODO: additional bindings
+        ("S" . (lambda ()
+                 "Mark message as spam"
+                 (interactive)
+                 (notmuch-tree-tag '("-unread" "-inbox" "-webmasters" "+spam"))
+                 (notmuch-tree-archive-thread))))
+)
+
+;; (use-package counsel-notmuch
+;;   :commands counsel-notmuch)
+
+(after! notmuch-crypto
+  (setq notmuch-crypto-process-mime t))
+
+;; (after! evil
+;;   (mapc (lambda (str) (evil-set-initial-state (car str) (cdr str)))
+;;         '((notmuch-hello-mode . emacs)
+;;           (notmuch-search-mode . emacs)
+;;           (notmuch-tree-mode . emacs))))
+
+(after! recentf
+  (add-to-list 'recentf-exclude (expand-file-name ab-maildir)))
+#+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)
+#+end_src
+
 * Post initialization
 :PROPERTIES:
 :CUSTOM_ID: post-initialization
 * Post initialization
 :PROPERTIES:
 :CUSTOM_ID: post-initialization