This org file is my literate configuration for GNU Emacs, and is tangled to init.el. Packages are installed and managed using 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:
- dieggsy/dotfiles: literate Emacs and dotfiles configuration, uses straight.el for managing packages
- dakra/dmacs: literate Emacs configuration, using Borg for managing packages
- Sacha Chua’s literate Emacs configuration
- dakrone/eos
- Ryan Rix’s Complete Computing Environment (about cce)
- jwiegley/dot-emacs: nix-based configuration
- wasamasa/dotemacs
- 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
of the box, that’s not achievable with package.el
, not currently
anyway. So, I’ve opted to use Borg. For what it’s worth, I briefly
experimented with straight.el, but found that it added about 2 seconds
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.
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:
git clone https://github.com/aminb/dotfiles ~/.emacs.d
cd ~/.emacs.d
make bootstrap-borg
make bootstrap
make build
;;; init.el --- Amin Bandali's Emacs config -*- lexical-binding: t ; eval: (view-mode 1)-*-
Enable view-mode
, which both makes the file read-only (as a reminder
that init.el
is an auto-generated file, not supposed to be edited),
and provides some convenient key bindings for browsing through the
file.
;; Copyright (C) 2018 Amin Bandali <amin@aminb.org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Emacs configuration of Amin Bandali, computer scientist and functional
;; programmer.
;; THIS FILE IS AUTO-GENERATED FROM `init.org'.
The conventions below were inspired by Doom’s conventions, found
here. Naturally, I use my initials, ab
, instead of doom
.
;; Naming conventions:
;;
;; ab-... public variables or non-interactive functions
;; ab--... private anything (non-interactive), not safe for direct use
;; ab/... an interactive function; safe for M-x or keybinding
;; ab:... an evil operator, motion, or command
;; ab|... a hook function
;; ab*... an advising function
;; ab@... a hydra command
;; ...! a macro
;;; Code:
I’d like to do a couple of measurements of Emacs’ startup time. First,
let’s see how long Emacs takes to start up, before even loading
init.el
, i.e. user-init-file
:
(defvar ab--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 ab--before-user-init-time
before-init-time)))
Also, temporarily increase gc-cons-threshhold
and
gc-cons-percentage
during startup to reduce garbage collection
frequency. Clearing the file-name-handler-alist
seems to help reduce
startup time as well.
(defvar ab--gc-cons-threshold gc-cons-threshold)
(defvar ab--gc-cons-percentage gc-cons-percentage)
(defvar ab--file-name-handler-alist file-name-handler-alist)
(setq gc-cons-threshold (* 400 1024 1024) ; 400 MiB
gc-cons-percentage 0.6
file-name-handler-alist nil
;; sidesteps a bug when profiling with esup
esup-child-profile-require-level 0)
Of course, we’d like to set them back to their defaults once we’re done initializing.
(add-hook
'after-init-hook
(lambda ()
(setq gc-cons-threshold ab--gc-cons-threshold
gc-cons-percentage ab--gc-cons-percentage
file-name-handler-alist ab--file-name-handler-alist)))
Increase the number of lines kept in message logs (the *Messages*
buffer).
(setq message-log-max 20000)
Optionally, we could suppress some byte compiler warnings like below,
but for now I’ve decided to keep them enabled. See documentation for
byte-compile-warnings
for more details.
;; (setq byte-compile-warnings
;; '(not free-vars unresolved noruntime lexical make-local))
(setq user-full-name "Amin Bandali"
user-mail-address "amin@aminb.org")
I can do all my package management things with Borg, and don’t need
Emacs’ built-in package.el
. Emacs 27 lets us disable package.el
in
the early-init-file
(see here).
(setq package-enable-at-startup nil)
But since Emacs 27 isn’t out yet (Emacs 26 is just around the corner
right now), and even when released it’ll be long before most distros
ship in their repos, I’ll still put the old workaround with the
commented call to package-initialize
here anyway.
(setq package-enable-at-startup nil)
;; (package-initialize)
Assimilate Emacs packages as Git submodules
Borg is at the heart of package management of my Emacs setup. In
short, it creates a git submodule in lib/
for each package, which
can then be managed with the help of Magit or other tools.
(setq user-init-file (or load-file-name buffer-file-name)
user-emacs-directory (file-name-directory user-init-file))
(add-to-list 'load-path
(expand-file-name "lib/borg" user-emacs-directory))
(require 'borg)
(borg-initialize)
A use-package declaration for simplifying your .emacs
use-package is an awesome utility for managing and configuring packages (in our case especially the latter) in a neatly organized way and without compromising on performance.
(require 'use-package)
(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
debug-on-error t)
(setq use-package-verbose nil
use-package-expand-minimally t))
Browse the Emacsmirror package database
Epkg provides access to a local copy of the Emacsmirror package
database, low-level functions for querying the database, and a
package.el
-like user interface for browsing the available packages.
(use-package epkg
:defer t)
Help keeping ~/.emacs.d clean
By default, even for Emacs’ built-in packages, the configuration files
and persistent data are all over the place. Use no-littering
to help
contain the mess.
(use-package no-littering
:demand t
:config
(savehist-mode 1)
(add-to-list 'savehist-additional-variables 'kill-ring)
(save-place-mode 1)
(setq auto-save-file-name-transforms
`((".*" ,(no-littering-expand-var-file-name "auto-save/") t))))
I’m not planning on using the custom file much, but even so, I
definitely don’t want it mixing with init.el
. So, here; let’s give
it it’s own file. While at it, treat themes as safe.
(use-package custom
:no-require t
:config
(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
(when (file-exists-p custom-file)
(load custom-file))
(setf custom-safe-themes t))
Let’s use exec-path-from-shell to make Emacs use the $PATH
as set up
in my shell.
(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"))
(defadvice load-theme (before clear-previous-themes activate)
"Clear existing theme settings instead of layering them"
(mapc #'disable-theme custom-enabled-themes))
Start server if not already running. Alternatively, can be done by
issuing emacs --daemon
in the terminal, which can be automated with
a systemd service or using brew services start emacs
on macOS. I use
Emacs as my window manager (via EXWM), so I always start Emacs on
login; so starting the server from inside Emacs is good enough for me.
(use-package server
:config (or (server-running-p) (server-mode)))
Font stack with better unicode support, around Ubuntu Mono
and
Hack
.
(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))
(require 'cl-lib)
(require 'subr-x)
(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)))))
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.
(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))
Might want to set the fringe to a smaller value, especially if using EXWM. I’m fine with the default for now.
;; (fringe-mode '(3 . 1))
(fringe-mode nil)
Emacs disables some commands by default that could persumably be confusing for novice users. Let’s disable that.
(setq disabled-command-function nil)
Save what I copy into clipboard from other applications into Emacs’ kill-ring, which would allow me to still be able to easily access it in case I kill (cut or copy) something else inside Emacs before yanking (pasting) what I’d originally intended to.
(setq save-interprogram-paste-before-kill t)
(setq enable-recursive-minibuffers t
resize-mini-windows t)
Lazy people would prefer to type fewer keystrokes, especially for yes or no questions. I’m lazy.
(defalias 'yes-or-no-p #'y-or-n-p)
Firstly, let Emacs know that I’d like to have *scratch*
as my
startup buffer.
(setq initial-buffer-choice t)
Now let’s customize the *scratch*
buffer a bit. First off, I don’t
need the default hint.
(setq initial-scratch-message nil)
Also, let’s use Text mode as the major mode, in case I want to
customize it (*scratch*
’s default major mode, Fundamental mode,
can’t really be customized).
(setq initial-major-mode 'text-mode)
Inhibit the buffer list when more than 2 files are loaded.
(setq inhibit-startup-buffer-menu t)
I don’t really need to see the startup screen or echo area message either.
(advice-add #'display-startup-echo-area-message :override #'ignore)
(setq inhibit-startup-screen t
inhibit-startup-echo-area-message user-login-name)
Show either the file name or the buffer name (in case the buffer isn’t visiting a file). Borrowed from Emacs Prelude.
(setq frame-title-format
'("" invocation-name " - "
(:eval (if (buffer-file-name)
(abbreviate-file-name (buffer-file-name))
"%b"))))
Emacs’ default backup settings aren’t that great. Let’s use more
sensible options. See documentation for the make-backup-file
variable.
(setq backup-by-copying t
version-control t)
Enable automatic reloading of changed buffers and files.
(global-auto-revert-mode 1)
(setq auto-revert-verbose nil
global-auto-revert-non-file-buffers t)
(setq-default
indent-tabs-mode nil
require-final-newline t
tab-width 4)
The packages in this section are absolutely essential to my everyday workflow, and they play key roles in how I do my computing. They immensely enhance the Emacs experience for me; both using Emacs, and customizing it.
(use-package auto-compile
:demand t
:config
(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)
(add-hook 'auto-compile-inhibit-compile-hook
'auto-compile-inhibit-compile-detached-git-head))
Roll your own modal mode
(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)
("/" undo)
("i" ryo-modal-mode)
("l" recenter-top-bottom)
("v" scroll-up-command)
("V" scroll-down-command)
("x" delete-forward-char)
("SPC" (("b" (("b" ibuffer-list-buffers)
("k" kill-this-buffer)
("o" other-window)
("s" save-buffer)))
("B" (("A" borg-activate)
("a" borg-assimilate)
("b" borg-build)
("c" borg-clone)
("r" borg-remove)))
("h" (("c" describe-char)
("f" describe-function)
("F" describe-face)
("i" info)
("k" describe-key)
("l" view-lossage)
("v" describe-variable)))
("q" (("q" save-buffers-kill-terminal)))))
("d" (("w" kill-word)
("b" backward-kill-word)))
("c w" kill-word :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)))
Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system.
In short, my favourite way of life.
(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))
It’s Magit! A Git porcelain inside Emacs.
Not just how I do git, but the way to do git.
(use-package magit
:ryo ("SPC" (("g s" magit-status)))
:defer t
:bind (("s-g" . magit-status)
("C-x g" . magit-status)
("C-x M-g" . magit-dispatch-popup))
:config
(magit-add-section-hook 'magit-status-sections-hook
'magit-insert-modules
'magit-insert-stashes
'append))
Ivy (and friends)
Ivy - a generic completion frontend for Emacs, Swiper - isearch with an overview, and more. Oh, man!
There’s no way I could top that, so I won’t attempt to.
(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))
(use-package swiper
:ryo
("SPC /" swiper)
("s" swiper)
:bind (([remap isearch-forward] . swiper)
([remap isearch-backward] . swiper)))
(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))
TODO: break this giant source block down into individual org sections.
(use-package dash
:config (dash-enable-font-lock))
(use-package diff-hl
:config
(setq diff-hl-draw-borders nil)
(global-diff-hl-mode)
(add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh t))
(use-package dired
:defer t
:config (setq dired-listing-switches "-alh"))
(use-package eldoc
:when (version< "25" emacs-version)
:config (global-eldoc-mode))
(use-package help
:defer t
:config (temp-buffer-resize-mode))
(progn ; `isearch'
(setq isearch-allow-scroll t))
(use-package lisp-mode
:config
(add-hook 'emacs-lisp-mode-hook 'outline-minor-mode)
(add-hook 'emacs-lisp-mode-hook 'reveal-mode)
(defun indent-spaces-mode ()
(setq indent-tabs-mode nil))
(add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
(use-package man
:defer t
:config (setq Man-width 80))
(use-package paren
:config (show-paren-mode))
(use-package prog-mode
:config (global-prettify-symbols-mode)
(defun indicate-buffer-boundaries-left ()
(setq indicate-buffer-boundaries 'left))
(add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left))
(use-package recentf
:demand t
:config (add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:"))
(use-package savehist
:config (savehist-mode))
(use-package saveplace
:when (version< "25" emacs-version)
:config (save-place-mode))
(use-package simple
:config (column-number-mode))
(progn ; `text-mode'
(add-hook 'text-mode-hook #'indicate-buffer-boundaries-left))
(use-package tramp
:defer t
:config
(add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
(add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
(add-to-list 'tramp-default-proxies-alist
(list (regexp-quote (system-name)) nil nil)))
(use-package undo-tree
:ryo
("?" undo-tree-undo)
("_" undo-tree-redo)
:bind (("C-?" . undo-tree-undo)
("M-_" . undo-tree-redo))
:config
(global-undo-tree-mode)
(setq undo-tree-mode-lighter ""
undo-tree-auto-save-history t))
(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))
(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)))
Alloy (with alloy-mode)
(use-package alloy-mode
:config (setq alloy-basic-offset 2))
Coq (with Proof General)
(use-package proof-site ; Proof General
:load-path "lib/proof-site/generic/")
(use-package lean-mode
:bind (:map lean-mode-map
("S-SPC" . company-complete)))
(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)))
(use-package dante
:after haskell-mode
:commands dante-mode
:hook (haskell-mode . dante-mode))
Emacs bindings for hlint’s refactor option. This requires the refact executable from apply-refact.
(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))
(use-package flycheck-haskell)
Currently using flycheck-haskell
with the haskell-hlint
checker
instead.
;;; 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
(use-package hs-lint
:load-path "lisp/"
:bind (:map haskell-mode-map
("C-c l l" . hs-lint)))
Emacs package that displays available keybindings in popup
(use-package which-key
:defer 1
:config (which-key-mode))
(use-package doom-modeline
:demand t
:config (setq doom-modeline-height 32)
:hook (after-init . doom-modeline-init))
(use-package tao-theme
:demand t
:config (load-theme 'tao-yang t))
(load-theme 'eink t)
(use-package crux
:bind (("C-c d" . crux-duplicate-current-line-or-region)
("C-c M-d" . crux-duplicate-and-comment-current-line-or-region))
:ryo
("o" crux-smart-open-line :exit t)
("O" crux-smart-open-line-above :exit t)
("SPC b K" crux-kill-other-buffers)
("d d" crux-kill-whole-line)
("c c" crux-kill-whole-line :then '(crux-smart-open-line-above) :exit t)
("SPC f" (("c" crux-copy-file-preserve-attributes)
("D" crux-delete-file-and-buffer)
("R" crux-rename-file-and-buffer))))
(use-package mwim
:bind (("C-a" . mwim-beginning-of-code-or-line)
("C-e" . mwim-end-of-code-or-line)
("<home>" . mwim-beginning-of-line-or-code)
("<end>" . mwim-end-of-line-or-code))
:ryo
("a" mwim-beginning-of-code-or-line)
("e" mwim-end-of-code-or-line))
(use-package key-chord
:demand t
:config
(key-chord-mode 1)
(key-chord-define-global "jk" 'ryo-modal-mode)
(setq key-chord-one-key-delay 0 ; i don't need one-key chords for now
key-chord-two-keys-delay 0.005))
See bug follow-up.
(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))
(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)))
(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))
(use-package ox-hugo
:after ox)
Display how long it took to load the init file.
(message "Loading %s...done (%.3fs)" user-init-file
(float-time (time-subtract (current-time)
ab--before-user-init-time)))
;;; init.el ends here