aminb’s Literate Emacs Configuration
About
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.
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:
git clone https://github.com/aminb/dotfiles ~/.emacs.d
cd ~/.emacs.d
make bootstrap-borg
make bootstrap
make buildContents
Header
First line
;;; 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.
License
;; 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
;;; Commentary:
;; Emacs configuration of Amin Bandali, computer scientist and functional
;; programmer.
;; THIS FILE IS AUTO-GENERATED FROM `init.org'.Naming conventions
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 macroInitial setup
;;; Code:Emacs initialization
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))Package management
No package.el
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)Borg
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)use-package
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))Epkg
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)No littering in ~/.emacs.d
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))))Custom file (custom.el)
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))Better $PATH handling
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"))Only one custom theme at a time
(defadvice load-theme (before clear-previous-themes activate)
"Clear existing theme settings instead of layering them"
(mapc #'disable-theme custom-enabled-themes))Server
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)))Unicode support
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))Libraries
(require 'cl-lib)
(require 'subr-x)Useful utilities
(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)))))Core
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.
(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))Smaller fringe
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)Disable disabled commands
Emacs disables some commands by default that could persumably be confusing for novice users. Let’s disable that.
(setq disabled-command-function nil)Kill-ring
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)Minibuffer
(setq enable-recursive-minibuffers t
resize-mini-windows t)Lazy-person-friendly yes/no prompts
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)Startup screen and *scratch*
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)More useful frame titles
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"))))Backups
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)Packages
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.
auto-compile
(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))general
Roll your own modal mode
(use-package general
:demand t
:config
(general-evil-setup t)
(general-override-mode)
(general-create-definer
ab--mode-leader-keys
:keymaps 'override
:states '(emacs normal visual motion insert)
:non-normal-prefix "C-,"
:prefix ",")
(general-create-definer
ab--leader-keys
:keymaps 'override
:states '(emacs normal visual motion insert)
:non-normal-prefix "M-m"
:prefix "SPC"))evil
(use-package evil
:demand t
:hook (view-mode . evil-motion-state)
:config (evil-mode 1))(use-package evil-escape
:demand t
:init
(setq evil-escape-excluded-states '(normal visual multiedit emacs motion)
evil-escape-excluded-major-modes '(neotree-mode)
evil-escape-key-sequence "jk"
evil-escape-delay 0.25)
:general
(:states '(insert replace visual operator)
"C-g" #'evil-escape)
:config
(evil-escape-mode 1)
;; no `evil-escape' in minibuffer
(push #'minibufferp evil-escape-inhibit-functions))EXWM (window manager)
(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))))sxhkdrc
:header-args+: :tangle ~/.config/sxhkd/sxhkdrc :mkdirp yes# 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
Org 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.
(setq org-src-tab-acts-natively t
org-src-preserve-indentation nil
org-edit-src-content-indentation 0
org-html-doctype "html5"
org-html-html5-fancy t)
(add-hook 'org-mode-hook 'org-indent-mode)
(use-package htmlize)Magit
It’s Magit! A Git porcelain inside Emacs.
Not just how I do git, but the way to do git.
(use-package magit
:general (ab--leader-keys "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.
Ivy
(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))
:config
(setq ivy-wrap t)
(ivy-mode 1))Swiper
(use-package swiper
:general (:states 'normal "/" 'swiper)
:bind (([remap isearch-forward] . swiper)
([remap isearch-backward] . swiper)))Counsel
(use-package counsel
:defer 1
:general (ab--leader-keys
"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))Borg’s layer/essentials
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
:config
(global-undo-tree-mode)
(setq undo-tree-mode-lighter ""))Editing
Company
(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))Customizations
(ab--leader-keys
"b s" 'save-buffer
"b b" 'ivy-switch-buffer
"," 'ivy-switch-buffer
"b k" 'kill-this-buffer
"q q" 'evil-save-and-quit)Syntax and spell checking
(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)))Programming modes
Lean
(use-package lean-mode
:bind (:map lean-mode-map
("S-SPC" . company-complete)))Haskell
haskell-mode
(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)))dante
(use-package dante
:after haskell-mode
:commands dante-mode
:hook (haskell-mode . dante-mode))hlint-refactor
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))flycheck-haskell
(use-package flycheck-haskell)hs-lint.el
:header-args+: :tangle lisp/hs-lint.el :mkdirp yesCurrently 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 Enhancements
which-key
Emacs package that displays available keybindings in popup
(use-package which-key
:defer 1
:config (which-key-mode))notmuch
(defun ab/notmuch ()
"Delete other windows, then launch `notmuch'."
(interactive)
(require 'notmuch)
(delete-other-windows)
(notmuch))
;; (ab--leader-keys
;; "m" 'ab/notmuch
;; "s" 'save-buffer
;; "SPC" 'counsel-M-x)
;; (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 "fencepost.gnu.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
:general (ab--leader-keys "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-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" "-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)))Post initialization
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)))Footer
;;; init.el ends here