[emacs] update 7 drones
[~bandali/configs] / init.org
index 371ec1e..51bf6ed 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
-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
@@ -22,13 +34,27 @@ 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.
 
+** 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]]
 - [[#header][Header]]
 - [[#initial-setup][Initial setup]]
 - [[#core][Core]]
-- [[#post-init][Post initialization]]
+- [[#post-initialization][Post initialization]]
 - [[#footer][Footer]]
 
 * Header
@@ -141,10 +167,6 @@ done initializing.
 (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)))
@@ -264,7 +286,6 @@ contain the mess.
         `((".*" ,(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
@@ -287,15 +308,23 @@ Let's use [[https://github.com/purcell/exec-path-from-shell][exec-path-from-shel
 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
@@ -313,6 +342,86 @@ See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.htm
   :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
@@ -320,13 +429,34 @@ See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.htm
 
 ** 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
 
-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
-(fringe-mode '(3 . 1))
+;; (fringe-mode '(3 . 1))
+(fringe-mode nil)
 #+end_src
 
 *** Disable disabled commands
@@ -460,15 +590,228 @@ Roll your own modal mode
 
 *** [[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))
+#+begin_src emacs-lisp :tangle no
+(use-package exwm
+  :demand t
+  :config
+  (require 'exwm-config)
+
+  ;; Set the initial workspace number.
+  (setq exwm-workspace-number 4)
+
+  ;; Make class name the buffer name, truncating beyond 50 characters
+  (defun exwm-rename-buffer ()
+    (interactive)
+    (exwm-workspace-rename-buffer
+     (concat exwm-class-name ":"
+             (if (<= (length exwm-title) 50) exwm-title
+               (concat (substring exwm-title 0 49) "...")))))
+  (add-hook 'exwm-update-class-hook 'exwm-rename-buffer)
+  (add-hook 'exwm-update-title-hook 'exwm-rename-buffer)
+
+  ;; 's-R': Reset
+  (exwm-input-set-key (kbd "s-R") #'exwm-reset)
+  ;; 's-\': Switch workspace
+  (exwm-input-set-key (kbd "s-\\") #'exwm-workspace-switch)
+  ;; 's-N': Switch to certain workspace
+  (dotimes (i 10)
+    (exwm-input-set-key (kbd (format "s-%d" i))
+                        (lambda ()
+                          (interactive)
+                          (exwm-workspace-switch-create i))))
+  ;; 's-SPC': Launch application
+  ;; (exwm-input-set-key
+  ;;  (kbd "s-SPC")
+  ;;  (lambda (command)
+  ;;    (interactive (list (read-shell-command "➜ ")))
+  ;;    (start-process-shell-command command nil command)))
+
+  (exwm-input-set-key (kbd "M-s-SPC") #'counsel-linux-app)
+
+  ;; Shorten 'C-c C-q' to 'C-q'
+  (define-key exwm-mode-map [?\C-q] #'exwm-input-send-next-key)
+
+  ;; Line-editing shortcuts
+  (setq exwm-input-simulation-keys
+       '(;; movement
+          ([?\C-b] . [left])
+          ([?\M-b] . [C-left])
+          ([?\C-f] . [right])
+          ([?\M-f] . [C-right])
+          ([?\C-p] . [up])
+          ([?\C-n] . [down])
+          ([?\C-a] . [home])
+          ([?\C-e] . [end])
+          ([?\M-v] . [prior])
+          ([?\C-v] . [next])
+          ([?\C-d] . [delete])
+          ([?\C-k] . [S-end delete])
+          ;; cut/copy/paste
+          ;; ([?\C-w] . [?\C-x])
+          ([?\M-w] . [?\C-c])
+          ([?\C-y] . [?\C-v])
+          ;; search
+          ([?\C-s] . [?\C-f])))
+
+  ;; Enable EXWM
+  (exwm-enable)
+
+  (add-hook 'exwm-init-hook #'exwm-config--fix/ido-buffer-window-other-frame)
+
+  (require 'exwm-systemtray)
+  (exwm-systemtray-enable)
+
+  (require 'exwm-randr)
+  (exwm-randr-enable)
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "s-<return>")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process "urxvt" nil "urxvt")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "s-SPC")  ;; rofi doesn't properly launch programs when started from emacs
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "rofi-run" nil "rofi -show run -display-run '> ' -display-window ' 🗔 '")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "s-/")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "rofi-win" nil "rofi -show window  -display-run '> ' -display-window ' 🗔 '")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "M-SPC")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process "rofi-pass" nil "rofi-pass")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "<XF86AudioMute>")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "pamixer" nil "pamixer --toggle-mute")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "<XF86AudioLowerVolume>")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "pamixer" nil "pamixer --allow-boost --decrease 5")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "<XF86AudioRaiseVolume>")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "pamixer" nil "pamixer --allow-boost --increase 5")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "<XF86AudioPlay>")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "mpc" nil "mpc toggle")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "<XF86AudioPrev>")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "mpc" nil "mpc prev")))
+
+  ;; (exwm-input-set-key
+  ;;  (kbd "<XF86AudioNext>")
+  ;;  (lambda ()
+  ;;    (interactive)
+  ;;    (start-process-shell-command "mpc" nil "mpv next")))
+
+  (defun ab--exwm-pasystray ()
+    "A command used to start pasystray."
+    (interactive)
+    (if (executable-find "pasystray")
+       (progn
+          (message "EXWM: starting pasystray ...")
+          (start-process-shell-command "pasystray" nil "pasystray --notify=all"))
+      (message "EXWM: pasystray is not installed, abort!")))
+
+  (add-hook 'exwm-init-hook #'ab--exwm-pasystray)
+
+  (exwm-input-set-key
+   (kbd "s-t")
+   (lambda ()
+     (interactive)
+     (exwm-floating-toggle-floating)))
+
+  (exwm-input-set-key
+   (kbd "s-f")
+   (lambda ()
+     (interactive)
+     (exwm-layout-toggle-fullscreen)))
+
+  (exwm-input-set-key
+   (kbd "s-w")
+   (lambda ()
+     (interactive)
+     (kill-buffer (current-buffer))))
+
+  (exwm-input-set-key
+   (kbd "s-q")
+   (lambda ()
+     (interactive)
+     (exwm-manage--kill-client))))
+#+end_src
+
+**** sxhkdrc
+:PROPERTIES:
+:header-args+: :tangle ~/.config/sxhkd/sxhkdrc :mkdirp yes
+:END:
+
+#+begin_src conf :tangle no
+# terminal emulator
+super + Return
+       urxvt
+
+# program launcher
+super + space
+       rofi -show run -display-run '> ' -display-window ' 🗔 '
+
+# window finder
+super + slash
+       rofi -show window  -display-run '> ' -display-window ' 🗔 '
+
+# password manager
+alt + space
+       rofi-pass
+
+# make sxhkd reload its configuration files:
+super + Escape
+       pkill -USR1 -x sxhkd
+
+# volume {up,down}
+XF86Audio{Raise,Lower}Volume
+       pamixer --allow-boost --{in,de}crease 5
+
+# mute
+XF86AudioMute
+       pamixer --toggle-mute
+
+# playback control
+XF86Audio{Play,Prev,Next}
+       mpc {toggle,prev,next}
+
+# Toggle keyboard layout
+# super + F7
+#      toggle-layout
+
+# Toggle Xfce presentation mode
+# XF86LaunchB
+#      toggle-presentation-mode
+
+# monitor brightness
+XF86MonBrightness{Up,Down}
+       light -{A,U} 5
+
+super + apostrophe
+       rofi-light
 #+end_src
 
 *** [[https://orgmode.org/][Org mode]]
@@ -520,39 +863,41 @@ There's no way I could top that, so I won't attempt to.
 **** 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
+  :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
+  (setq ivy-wrap t)
+  (ivy-mode 1))
 #+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
-;; (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)
+         :map minibuffer-local-map
+        ("C-r" . counsel-minibuffer-history))
+  :config
+  (counsel-mode 1)
+  (defalias 'locate #'counsel-locate))
 #+end_src
 
 * Borg's =layer/essentials=
@@ -636,9 +981,390 @@ TODO: break this giant source block down into individual org sections.
   (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
+
+** [[https://github.com/leanprover/lean-mode][Lean]]
+
+#+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
+* Email
+** notmuch
+
+#+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
+  :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-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")))
+  ;; (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")))
+       ("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" "+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" "+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
+
 * Post initialization
 :PROPERTIES:
-:CUSTOM_ID: post-init
+:CUSTOM_ID: post-initialization
 :END:
 
 Display how long it took to load the init file.