Emacs Config
Find the whole Emacs config in
a <details>…</details>
block at the
very end, but I’ll mostly use this space to point out parts
I think are interesting or personally-important.
I won’t explain as much as I perhaps should; if you use Emacs and want more detail, then use the help functions. Upon further confusion (or you aren’t a member of the Church of Emacs) then let me know, and I’ll update.
N.B. I use Emacs within the terminal. I know: it’s sacrilege; it’s how I like it. Some parts of my config reflect this and I will try to point them out.
Simple Bindings
Every good config starts with some rebinding!
(let ((map global-map)) (define-key map (kbd "<C-tab>") #'hs-toggle-hiding) (define-key map (kbd "<mouse-4>") #'scroll-down-line) (define-key map (kbd "<mouse-5>") #'scroll-up-line) (define-key map (kbd "<mouse-6>") #'ignore) (define-key map (kbd "<mouse-7>") #'ignore) (define-key map (kbd "C-'") #'comment-line) (define-key map (kbd "C-x C-b") #'bs-show) (define-key map (kbd "M-R") #'query-replace-regexp) (define-key map (kbd "M-n") #'forward-paragraph) (define-key map (kbd "M-o") #'other-window) (define-key map (kbd "M-p") #'backward-paragraph) (define-key map (kbd "M-r") #'query-replace)) (defvar 11fd/map (let ((map (make-sparse-keymap))) (define-key map "c" 'switch-to-completions) (define-key map "h" 'hs-hide-all) (define-key map "r" 'recompile) (define-key map "s" 'hs-show-all) (global-set-key "\e\e" map)) "My key-map.") (define-key query-replace-map "a" 'automatic) ; mnemonic: "all" (define-key query-replace-map "p" 'backup) ; mnemonic: "prev" (windmove-default-keybindings)
Using M-n for forward-paragraph
and M-p for backward-paragraph
are
great for moving around a file quickly.
M-r for query-replace
is nicer than
the awful M-%.
ESC ESC is a good location for occasionally-used bindings (from EmacsWiki: Choosing Keys To Bind). Setting up a custom keymap is an idea stolen from Lars Tveito’s config and goes well with the later modal-editing section.
a and p in query replace are easier to
remember. automatic
replaces all remaining
matches without prompt, and backup
moves point
back to previous match.
Extra Term Keys
You may find that some of the above combos, such as C-', don’t work. Emacs can interpret xterm-style keyboard escape sequences as long as the termcap fits.
key_bindings: # … # Use with xterm-256color - { key: Apostrophe, mods: Control, chars: "\e[27;5;39~" } - { key: Comma, mods: Control, chars: "\e[27;5;44~" } - { key: Minus, mods: Control, chars: "\e[27;5;45~" } - { key: Period, mods: Control, chars: "\e[27;5;46~" } - { key: Semicolon, mods: Control, chars: "\e[27;5;59~" } - { key: Tab, mods: Control, chars: "\e[27;5;9~" } - { key: Return, mods: Control, chars: "\e[27;5;13~" }
Theme
wombat
is pretty, as is tsdh-dark
.
I include the line that uses the terminal background-color,
but this should be removed for GUI editing.
(load-theme 'wombat) (set-face-background 'default "unspecified-bg") ;; Terminal only (set-face-italic 'font-lock-comment-face t)
Folding
hideshow
, often abbreviated to
just hs-
in prefixes, is Emacs’ built-in
code-folding package.
(setq-default hs-allow-nesting t hs-isearch-open t) (add-hook 'hs-minor-mode-hook #'reveal-mode) (add-hook 'prog-mode-hook #'hs-minor-mode)
Auto-unfolding
I like to have my code temporarily unfold when I pass over
it, which in Emacs is easily performed
via reveal-mode
.
Setting hs-isearch-open
means that I can still
search within folded blocks, and they are opened
automatically.
Mouse
This one isn’t all that amazing, and I don’t use it that often, but it’s something I’ve been consistently asked about.
(xterm-mouse-mode 1)
xterm-mouse-mode
requires $TERM
to be xterm-compatible. I use
Alacritty,
which can use xterm-256color
.
env: TERM: xterm-256color
Minibuffer Completion
There are various installable packages
like smex
that enhance M-x,
but icomplete
is built-in and works well enough
for me.
(icomplete-mode 1) (ido-mode 1) (require 'icomplete) (let ((map icomplete-minibuffer-map)) (define-key map [left] #'icomplete-backward-completions) (define-key map [right] #'icomplete-forward-completions) (define-key map [?\r] #'icomplete-force-complete-and-exit))
Package Config
;;; Workaround for https://debbugs.gnu.org/34341 in GNU Emacs <= 26.3. (when (and (version< emacs-version "26.3") (>= libgnutls-version 30603)) (setq-default gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")) (require 'package) (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) (setq package-selected-packages nil) (defun with-package/install-packages () "Install not-installed packages found by `with-package'." (package-refresh-contents) (package-install-selected-packages) (package-autoremove) (package-quickstart-refresh) (byte-recompile-file user-init-file 'force 'ask 'reload)) (defmacro with-package (package &rest body) "Package-specific configuration macro. Add PACKAGE to `package-selected-packages', attempt to `require' PACKAGE, evaluate BODY if successful, else add `with-package/install-packages' to `after-init-hook'." (declare (indent 1)) `(and (push ,package package-selected-packages) (if (require ,package nil 'noerror) (progn ,@body) (add-hook 'after-init-hook #'with-package/install-packages) nil)))
I’ve tried use-package
and leaf
,
both advanced, featureful, and convenient
package-configuration macros. In my opinion, they abstract
too much: what might usually be Emacs Lisp errors are
now package-config-macro
errors, which can be
more difficult to diagnose, especially for beginners.
It’s a worthy tradeoff in a language with powerful, introspective macros, but I prefer to get my hands dirty.
I 5-min’d a method that checked & installed a list of packages, which can then be configured as if built-in. It had downsides, and I kept meaning to make it into a simple macro.
Lo and behold, someone beats me to it, Declarative Package Configuration in 5 Lines of Emacs Lisp. I made some tweaks, there’s a few more lines involved, but Emacs now automatically installs new packages, and then reloads the config.
Examples
;; Just install (with-package 'fish-mode)
;; Install and run command (with-package 'flycheck (global-flycheck-mode))
Extra example…
;; Slightly-more complex example (with-package 'd-mode (add-hook 'd-mode-hook #'auto-fill-mode) (add-hook 'd-mode-hook #'display-line-numbers-mode) (add-hook 'd-mode-hook #'follow-mode) (add-hook 'd-mode-hook #'subword-mode) (add-hook 'd-mode-hook #'11fd/d-mode-setup 50) (defun 11fd/d-mode-setup () "My `d-mode' preferences." (add-hook 'before-save-hook #'whitespace-cleanup 0 'local) (c-set-style "linux") (c-set-offset 'inline-open '0) (c-toggle-auto-newline 1) (setq-local c-basic-offset 4 tab-width 4)))
Modal Editing
Attention, Vimmers!
Emacs is not anti-modal!
The way that Emacs handles ‘plugin’ keybindings is already
(sorta) modal.
Viper
is included
OOTB, there are
installable binding schemes (e.g.
Boon,
Meow,
Xah-Fly-Keys,
&c.),
plus DIY-modality packages.
Making your own modal scheme
without packages is itself cheap and easy.
PSA over :)
Most of the time, I admit to using the default Emacs bindings. I’m pretty quick with them, but — especially if I’m using my laptop’s keyboard and can’t palm-press ctrl — I sometimes like to have some basic modality.
I’ve used viper
, which is a built-in Vi-like
editing scheme, but I’m too set in my Emacs-y ways.
Modalka is a simple (< 100 loc) package which works nicely for me, but if you want to build up your own Vim-like grammars, then I hear RYO‑Modal is better.
(with-package 'modalka (setq modalka-excluded-modes '(bs-mode dired-mode package-menu-mode mu4e-headers-mode)) (modalka-remove-kbd "q") (dolist (pair '(("'" "C-'") ("," "C-,") ("." "C-.") ("/" "C-/") ("0" "C-x )") ("1" "C-x 1") ("2" "C-x 2") ("3" "C-x 3") ("4" "C-x C-k 4") ("5" "C-x C-k 5") ("6" "C-x C-k 6") ("7" "C-x C-k 7") ("8" "C-x 8 RET") ("9" "C-x (") ("SPC" "C-SPC") ("a" "C-a") ("b" "C-b") ("d" "C-d") ("e" "C-e") ("f" "C-f") ("g" "C-g") ("h" "DEL") ("i" "C-i") ("j" "M-j") ("k" "C-k") ("l" "C-l") ("m" "M-m") ("n" "C-n") ("o" "C-o") ("p" "C-p") ("r" "C-r") ("s" "C-s") ("t" "C-t") ("v" "C-v") ("w" "C-w") ("y" "C-y") ("z" "C-z"))) (modalka-define-kbd (car pair) (nth 1 pair))) (let ((map modalka-mode-map)) (define-key map "x" ctl-x-map) (define-key map "u" #'modalka-global-mode) (define-key map "c" 11fd/map) (define-key map "K" kmacro-keymap) (define-key map "?" help-map)) (global-set-key [select] #'modalka-global-mode) (global-set-key [end] #'modalka-global-mode))
Unbound Keys?
By default, unbound top-level keys still insert the character they represent, but they can be remapped.
(define-key modalka-mode-map [remap self-insert-command] #'undefined) ;; Or (define-key modalka-mode-map [remap self-insert-command] #'ignore)
Non-Emacs .el
Certain programs include their respective Emacs mode as part
of the main installation, such as Erlang and Agda. I find
an autoload
statement is the best way to manage
this.
(autoload 'agda2-mode (shell-command-to-string "agda-mode locate") "Load agda2-mode from cabal install" 'interactive) (add-to-list 'auto-mode-alist '("\\.l?agda\\'" . agda2-mode))
Entire Config
Here it is, the whole shebang. There are parts that need to be cleaned up, and some package-specific configuration has been removed for brevity.
Probably, I should link to a git forge with this in it, but I’m lazy, and this allows for quick-referencing.
~/.config/emacs/init.el:
;;; init.el --- 11fdriver's simple-ish Emacs config. ;;; Commentary: ;;; A comfortable Emacs config with help from the with-package macro ;;; (https://cosine.blue/emacs-with-package.html). ;;; Code: (defalias 'yes-or-no-p 'y-or-n-p) (load-theme 'wombat) (set-face-background 'default "unspecified-bg") (set-face-italic 'font-lock-comment-face t) (setq-default backward-delete-char-untabify-method 'hungry desktop-save t desktop-load-locked-desktop t disabled-command-function nil fill-column 68 hs-allow-nesting t hs-isearch-open t ido-enable-flex-matching t ispell-extra-args '("--sug-mode=ultra" "--lang=en_US" "--keyboard=dvorak") sentence-end-double-space nil show-paren-delay 0 whitespace-style '(face trailing empty space-after-tab space-before-tab)) (add-to-list 'desktop-locals-to-save 'buffer-undo-list) (add-to-list 'auto-mode-alist '("\\.do\\'" . sh-mode)) (require 'webjump) (add-to-list 'webjump-sites '("11fdriver" . "11fdriver.neocities.org/")) (add-to-list 'webjump-sites '("Neocities" . "neocities.org/")) (gpm-mouse-mode 0) (menu-bar-mode 0) (tool-bar-mode 0) (tooltip-mode 0) (when (fboundp 'scroll-bar-mode) (scroll-bar-mode 0)) (desktop-save-mode 1) (electric-indent-mode 1) (electric-pair-mode 1) (icomplete-mode 1) (ido-mode 1) (save-place-mode 1) (savehist-mode 1) (show-paren-mode 1) (winner-mode 1) (xterm-mouse-mode 1) (add-hook 'hs-minor-mode-hook #'reveal-mode) (add-hook 'prog-mode-hook #'flyspell-prog-mode) (add-hook 'prog-mode-hook #'hs-minor-mode) (add-hook 'prog-mode-hook #'whitespace-mode) (add-hook 'text-mode-hook #'flyspell-mode) (add-hook 'text-mode-hook #'visual-line-mode) (defconst custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file 'noerror 'nomessage 'nosuffix 'must-suffix) (setq backup-by-copying t backup-directory-alist `(("." . ,(locate-user-emacs-file "backups"))) delete-old-versions t vc-make-backup-files t version-control t) (require 'icomplete) (let ((map icomplete-minibuffer-map)) (define-key map [left] #'icomplete-backward-completions) (define-key map [right] #'icomplete-forward-completions) (define-key map [?\r] #'icomplete-force-complete-and-exit)) (let ((map global-map)) (define-key map (kbd "<C-tab>") #'hs-toggle-hiding) (define-key map (kbd "<mouse-4>") #'scroll-down-line) (define-key map (kbd "<mouse-5>") #'scroll-up-line) (define-key map (kbd "<mouse-6>") #'ignore) (define-key map (kbd "<mouse-7>") #'ignore) (define-key map (kbd "C-'") #'comment-line) (define-key map (kbd "C-x C-b") #'bs-show) (define-key map (kbd "C-x c") #'save-buffers-kill-terminal) (define-key map (kbd "C-x f") #'find-file) (define-key map (kbd "C-x w") #'write-file) (define-key map (kbd "M-/") #'hippie-expand) (define-key map (kbd "M-R") #'query-replace-regexp) (define-key map (kbd "M-n") #'forward-paragraph) (define-key map (kbd "M-o") #'other-window) (define-key map (kbd "M-p") #'backward-paragraph) (define-key map (kbd "M-r") #'query-replace)) (defvar 11fd/map (let ((map (make-sparse-keymap))) (define-key map "c" 'switch-to-completions) (define-key map "h" 'hs-hide-all) (define-key map "r" 'recompile) (define-key map "s" 'hs-show-all) (define-key map "t" 'hs-toggle-hiding) (global-set-key "\e\e" map)) "My key-map.") (define-key emacs-lisp-mode-map (kbd "C-c C-c") #'emacs-lisp-byte-compile) (define-key query-replace-map "a" 'automatic) ; mnemonic: "all" (define-key query-replace-map "p" 'backup) ; mnemonic: "prev" (windmove-default-keybindings) ;;; Workaround for https://debbugs.gnu.org/34341 in GNU Emacs <= 26.3. (when (and (version< emacs-version "26.3") (>= libgnutls-version 30603)) (setq-default gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")) (require 'package) (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) (setq package-selected-packages nil) (defun with-package/install-packages () "Install not-installed packages found by `with-package'." (package-refresh-contents) (package-install-selected-packages) (package-autoremove) (package-quickstart-refresh) (byte-recompile-file user-init-file 'force 'ask 'reload)) (defmacro with-package (package &rest body) "Package-specific configuration macro. Add PACKAGE to `package-selected-packages', attempt to `require' PACKAGE, evaluate BODY if successful, else add `with-package/install-packages' to `after-init-hook'." (declare (indent 1)) `(and (push ,package package-selected-packages) (if (require ,package nil 'noerror) (progn ,@body) (add-hook 'after-init-hook #'with-package/install-packages) nil))) (with-package 'avy (setq-default avy-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n ?s)) ; Dvorak keys (global-set-key (kbd "C-z") #'avy-goto-char-timer) (define-key isearch-mode-map (kbd "C-z") #'avy-isearch) (modify-face 'avy-lead-face "bright white" "bright red") (modify-face 'avy-lead-face-0 "bright white" "bright blue") (modify-face 'avy-lead-face-1 "bright black" "bright white") (modify-face 'avy-lead-face-2 "bright white" "bright magenta")) (with-package 'company (add-hook 'prog-mode-hook #'company-mode)) (with-package 'd-mode (add-hook 'd-mode-hook #'auto-fill-mode) (add-hook 'd-mode-hook #'display-line-numbers-mode) (add-hook 'd-mode-hook #'follow-mode) (add-hook 'd-mode-hook #'subword-mode) (add-hook 'd-mode-hook #'11fd/d-mode-setup 50) (defun 11fd/d-mode-setup () "My `d-mode' preferences." (add-hook 'before-save-hook #'whitespace-cleanup 0 'local) (c-set-style "linux") (c-set-offset 'inline-open '0) (c-toggle-auto-newline 1) (setq-local c-basic-offset 4 tab-width 4))) (with-package 'fish-mode) (with-package 'flycheck (global-flycheck-mode)) (with-package 'mhtml-mode (define-skeleton html-tag-wrap-skel "Skeleton to wrap selection with tag. Goes well with `sgml-electric-tag-pair-mode'." > "<p" - ">" \n > _ \n > "</p>" > \n ) (define-skeleton html-inline-tag-wrap-skel "Like `html-tag-wrap-skel' but on one line." > "<em" - ">" _ "</em>") (define-skeleton html-date-skel "Skeleton to insert <time datetime=…>…</time>." "Date: " > "<time datetime=\"" str | (setq date (format-time-string "%Y-%m-%d")) "\">" str | date "</time>" > \n \n) (defun refresh-firefox () "Quick 'n' dirty func to refresh current FF page." (interactive) (async-shell-command "xdotool search --name 'Mozilla Firefox' key F5" "*Refresh Firefox*") (quit-window nil (get-buffer-window "*Refresh Firefox*"))) (let ((map mhtml-mode-map)) (define-key map (kbd "C-c d") #'html-date-skel) (define-key map (kbd "C-c i") #'html-inline-tag-wrap-skel) (define-key map (kbd "<f5>") #'refresh-firefox) (define-key map (kbd "C-c t") #'html-tag-wrap-skel)) (add-hook 'mhtml-mode-hook #'sgml-electric-tag-pair-mode) (add-hook 'mhtml-mode-hook #'electric-quote-local-mode) (add-hook 'mhtml-mode-hook #'11fd/mhtml-mode-setup 50) (defun 11fd/mhtml-mode-setup () "My `mhtml-mode' preferences." (add-hook 'before-save-hook #'whitespace-cleanup 0 'local) (setq indent-tabs-mode nil))) (with-package 'modalka (setq modalka-excluded-modes '(bs-mode dired-mode package-menu-mode mu4e-headers-mode)) (modalka-remove-kbd "q") (dolist (pair '(("'" "C-'") ("," "C-,") ("." "C-.") ("/" "C-/") ("0" "C-x )") ("1" "C-x 1") ("2" "C-x 2") ("3" "C-x 3") ("4" "C-x C-k 4") ("5" "C-x C-k 5") ("6" "C-x C-k 6") ("7" "C-x C-k 7") ("8" "C-x 8 RET") ("9" "C-x (") ("SPC" "C-SPC") ("a" "C-a") ("b" "C-b") ("d" "C-d") ("e" "C-e") ("f" "C-f") ("g" "C-g") ("h" "DEL") ("i" "C-i") ("j" "M-j") ("k" "C-k") ("l" "C-l") ("m" "M-m") ("n" "C-n") ("o" "C-o") ("p" "C-p") ("r" "C-r") ("s" "C-s") ("t" "C-t") ("v" "C-v") ("w" "C-w") ("y" "C-y") ("z" "C-z"))) (modalka-define-kbd (car pair) (nth 1 pair))) (let ((map modalka-mode-map)) ;; (define-key map [remap self-insert-command] 'undefined) (define-key map "x" ctl-x-map) (define-key map "u" #'modalka-global-mode) (define-key map "c" 11fd/map) (define-key map "K" kmacro-keymap) (define-key map "?" help-map)) (global-set-key [select] #'modalka-global-mode) (global-set-key [end] #'modalka-global-mode)) (with-package 'rainbow-delimiters (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)) (with-package 'xclip (xclip-mode)) (with-package 'yaml-mode) (with-package 'zzz-to-char (global-set-key (kbd "M-z") #'zzz-up-to-char)) (autoload 'agda2-mode (shell-command-to-string "agda-mode locate") "Load agda2-mode from cabal install" 'interactive) (add-to-list 'auto-mode-alist '("\\.l?agda\\'" . agda2-mode)) ;;; init.el ends here