This is my emacs configuration. After many years with vim then a year with spacemacs & prelude, I came to the realisation that I needed to construct my own to really get it[fn:1].
After a few aborted attempts to split the config into separate files, I settled on the single file literate approach via Sacha Chua.
Documenting this is mostly for my benefit, but I hope others might find it useful constucting their own. The live version is on GitHub, with this version updated periodically.
Not that we needed all that for the trip, but once you get locked into a serious drug collection, the tendency is to push it as far as you can. Hunter S. Thompson, Fear and Loathing in Las Vegas
Add repositories from which we’ll load packages. I prefer to live on the bleeding edge so have only enabled melpa. Setting package-enable-at-startup
to nil
prevents a second package load and slightly improves startup time.
(setq package-enable-at-startup nil)
(setq package-archives '(("gnu" . "http://mirrors.163.com/elpa/gnu/")
("melpa" . "https://melpa.org/packages/")
("org" . "http://orgmode.org/elpa/")))
If use-package
is not installed, install it.
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package)
(eval-when-compile (require 'use-package)))
By default all packages should be installed from package manager as that’s the usual path. This is equivalent to setting :ensure t
on each call to use-package
. To disable set :ensure nil
(this is done automatically for any packages using :load-path
so shouldn’t generally be needed).
(setq use-package-always-ensure t)
benchmark-init
records startup time by package so we can debug. It only records things after it’s initialised, so put as early in config as possible.
(use-package benchmark-init
:config
;; To disable collection of benchmark data after init is done.
(add-hook 'after-init-hook 'benchmark-init/deactivate))
(add-hook 'after-init-hook
(lambda () (message "loaded in %s" (emacs-init-time))))
The default garbage collection threshold is 800kB, increasing this to 10MB for startup increases speed (from 11.0s -> 9.7s when I tested).
(setq gc-cons-threshold 10000000)
;; Restore after startup
(add-hook 'after-init-hook
(lambda ()
(setq gc-cons-threshold 1000000)
(message "gc-cons-threshold restored to %S"
gc-cons-threshold)))
(defun find-config ()
"Edit config.org"
(interactive)
(find-file "~/dotfiles/config.org"))
(global-set-key (kbd "C-c I") 'find-config)
This stops emacs adding customised settings to init.el
. I try to avoid using customize anyway, preferring programmatic control of variables. Creating it as a temporary file effectively disables it (i.e. any changes are session local).
(setq custom-file (make-temp-file "emacs-custom"))
By default Emacs only includes files directly under user-emacs-directory
(usually ~/.emacs.d/
), so we need to add any folders containing custom packages.
I put my scripts under ~/dotfiles/lisp/
and symlink it with ln -s ~/dotfiles/lisp ~/.emacs.d/lisp
.
(add-to-list 'load-path "~/.emacs.d/lisp/")
This is useful to find out what I use a lot. I plan to then change bindings to improve ergonomics on commonly used functions.
(use-package keyfreq
:config
(keyfreq-mode 1)
(keyfreq-autosave-mode 1))
Don’t display the help screen on startup.
(setq inhibit-startup-screen t)
On I use ⌘ as meta
and prefer ⌥ to do nothing so I can still insert special characters easily.
(setq mac-command-modifier 'meta
mac-option-modifier 'none)
I prefer lines to wrap.
(global-visual-line-mode 1)
Let’s turn off unwanted window decoration.
(tool-bar-mode -1)
(scroll-bar-mode -1)
I don’t want the error bell.
(setq ring-bell-function 'ignore)
Make the yes or no
prompts shorter.
(defalias 'yes-or-no-p 'y-or-n-p)
A common frustration with new Emacs users is the filename#
files created. This centralises the backup files created as you edit.
(setq backup-directory-alist '(("." . "~/.emacs.d/backup"))
backup-by-copying t ; Don't delink hardlinks
version-control t ; Use version numbers on backups
delete-old-versions t ; Automatically delete excess backups
kept-new-versions 20 ; how many of the newest versions to keep
kept-old-versions 5 ; and how many of the old
)
I usually don’t want tabs, if I do I can set this buffer-local to t
. If I just want one tab then use C-q
(quoted-insert
) to insert as a literal.
(setq-default indent-tabs-mode nil)
crux
has useful functions extracted from Emacs Prelude. Set C-a
to move to the first non-whitespace character on a line, and then to toggle between that and the beginning of the line.
(use-package crux
:bind (("C-a" . crux-move-beginning-of-line)))
I never want whitespace at the end of lines. Remove it on save.
(add-hook 'before-save-hook 'delete-trailing-whitespace)
Evil-mode emulates Vim in Emacs.
(use-package evil
:config
;; (evil-mode 1)
(evil-set-initial-state 'NeoTree 'emacs))
Todo: evil leader etc?
God-mode is a sort-of alternative to Vim - it’s a modal interface to emacs existing commands, so in essence when enabled you don’t need to chord Ctrl / Meta. As an example C-x C-s
(save) becomes xs
.
(use-package god-mode
:disabled
:bind (("<escape>" . god-local-mode)
("C-x C-1" . delete-other-windows)
("C-x C-2" . split-window-below)
("C-x C-3" . split-window-right)
("C-x C-0" . delete-window)))
(defun my-update-cursor ()
(setq cursor-type (if (or god-local-mode buffer-read-only)
'box
'bar)))
(add-hook 'god-mode-enabled-hook 'my-update-cursor)
(add-hook 'god-mode-disabled-hook 'my-update-cursor)
Todo: update window-divider
on god-mode status?
Sometimes it’s useful to step to the last changes in a buffer.
(use-package goto-last-change
:bind (("C-;" . goto-last-change)))
ivy
is a generic completion framework which uses the minibuffer. Turning on ivy-mode
enables replacement of lots of built in ido
functionality.
(use-package ivy
:config
(ivy-mode t))
By default ivy
starts filters with ^
. I don’t normally want that and can easily type it manually when I do.
(setq ivy-initial-inputs-alist nil)
counsel
is a collection of ivy
enhanced versions of common Emacs commands. I haven’t bound much as ivy-mode
takes care of most things.
(use-package counsel
:bind (("M-x" . counsel-M-x)))
prescient
sorts and filters candidate lists for avy/counsel.
(use-package prescient)
(use-package ivy-prescient
:config
(ivy-prescient-mode t))
swiper
is an ivy
enhanced version of isearch.
(use-package swiper
:bind (("M-s" . counsel-grep-or-swiper)))
hydra
presents menus for ivy
commands.
(use-package ivy-hydra)
major-mode-hydra
binds a single key to open a context sensitive hydra based on current major mode. Hydras can be defined in use-package
definitions via the :mode-hydra
integration.
(use-package major-mode-hydra
:bind
("C-M-SPC" . major-mode-hydra)
:config
(major-mode-hydra-define org-mode
()
("Tools"
(("l" org-lint "lint")))))
Suggest next keys to me based on currently entered key combination.
(use-package which-key
:config
(add-hook 'after-init-hook 'which-key-mode))
undo-tree
visualises undo history as a tree for easy navigation.
(use-package undo-tree
:defer 5
:config
(global-undo-tree-mode 1))
One of the most important features of an advanced editor is quick text navigation. avy
lets us jump to any character or line quickly.
(use-package avy)
ace-window
lets us navigate between windows in the same way as avy
. Once activated it has useful sub-modes like x
to switch into window deletion mode.
(use-package ace-window
:config
(setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)))
expand-region
expands the region around the cursor semantically depending on mode. Hard to describe but a killer feature.
(use-package expand-region
:bind ("C-=" . er/expand-region))
This function toggles the frame-parameter
fullscreen
so that I can maximise Emacs from within rather than relying on the external MacOS controls.
(defun mac-toggle-max-window ()
(interactive)
(set-frame-parameter
nil
'fullscreen
(if (frame-parameter nil 'fullscreen)
nil
'fullboth)))
I’m now using my own translation of Panda Theme (now on melpa!).
(use-package panda-theme
:disabled
:config
(load-theme 'panda t))
I also like Solarized.
(use-package solarized-theme
:config
(load-theme 'solarized-light t))
Set a nice font.
(set-frame-font "Operator Mono 12" nil t)
;; (set-frame-font "Inconsolata 13" nil t)
;; (set-frame-font "SF Mono 12" nil t)
feebleline
is a minimalist mode line replacement.
(use-package feebleline
:config
(feebleline-mode 't))
Add emoji support. This is useful when working with html.
(use-package emojify)
Improve look and feel of titlebar on Macos. Set ns-appearance
to dark
for white title text and nil
for black title text.
(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
(add-to-list 'default-frame-alist '(ns-appearance . dark))
When programming I like my editor to try to help me with keeping parentheses balanced.
(use-package smartparens
:config
(add-hook 'prog-mode-hook 'smartparens-mode))
Highlight parens etc. for improved readability.
(use-package rainbow-delimiters
:config
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode))
Highlight strings which represent colours. I only want this in programming modes, and I don’t want colour names to be highlighted (x-colors
).
(use-package rainbow-mode
:config
(setq rainbow-x-colors nil)
(add-hook 'prog-mode-hook 'rainbow-mode))
Expand parentheses for me.
(add-hook 'prog-mode-hook 'electric-pair-mode)
fzf
is a fuzzy file finder which is very quick.
(use-package fzf)
deadgrep
uses rg
to search for strings, project.el
means it will automatically use the project root if (e.g.) it’s a git repository, which is my usual use case.
(use-package deadgrep)
By default Emacs doesn’t read from the same environment variables set in your terminal. This package fixes that.
(use-package exec-path-from-shell
:config
(exec-path-from-shell-initialize))
Individual language packages often support IDE features like jump to source, but dumb-jump
attempts to support many languages by simple searching. It’s quite effective even with dynamic libraries like JS and Python.
(use-package dumb-jump
:bind (("C-M-g" . dumb-jump-go)
("C-M-p" . dumb-jump-back)
("C-M-q" . dumb-jump-quick-look)))
Magit is an awesome interface to git. Summon it with `C-x g`.
(use-package magit
:bind ("C-x g" . magit-status))
Display line changes in gutter based on git history. Enable it everywhere.
(use-package git-gutter
:config
(global-git-gutter-mode 't))
Flycheck
is a general syntax highlighting framework which other packages hook into. It’s an improvment on the built in flymake
.
Setup is pretty simple - we just enable globally and turn on a custom eslint function, and also add a custom checker for proselint.
(use-package flycheck
:config
(add-hook 'after-init-hook 'global-flycheck-mode)
(add-hook 'flycheck-mode-hook 'jc/use-eslint-from-node-modules)
(add-to-list 'flycheck-checkers 'proselint)
(setq-default flycheck-highlighting-mode 'lines)
;; Define fringe indicator / warning levels
(define-fringe-bitmap 'flycheck-fringe-bitmap-ball
(vector #b00000000
#b00000000
#b00000000
#b00000000
#b00000000
#b00000000
#b00000000
#b00011100
#b00111110
#b00111110
#b00111110
#b00011100
#b00000000
#b00000000
#b00000000
#b00000000
#b00000000))
(flycheck-define-error-level 'error
:severity 2
:overlay-category 'flycheck-error-overlay
:fringe-bitmap 'flycheck-fringe-bitmap-ball
:fringe-face 'flycheck-fringe-error)
(flycheck-define-error-level 'warning
:severity 1
:overlay-category 'flycheck-warning-overlay
:fringe-bitmap 'flycheck-fringe-bitmap-ball
:fringe-face 'flycheck-fringe-warning)
(flycheck-define-error-level 'info
:severity 0
:overlay-category 'flycheck-info-overlay
:fringe-bitmap 'flycheck-fringe-bitmap-ball
:fringe-face 'flycheck-fringe-info))
Proselint is a syntax checker for English language. This defines a custom checker which will run in texty modes.
Proselint is an external program, install it with pip install proselint
for this to work.
(flycheck-define-checker proselint
"A linter for prose."
:command ("proselint" source-inplace)
:error-patterns
((warning line-start (file-name) ":" line ":" column ": "
(id (one-or-more (not (any " "))))
(message (one-or-more not-newline)
(zero-or-more "\n" (any " ") (one-or-more not-newline)))
line-end))
:modes (text-mode markdown-mode gfm-mode org-mode))
Eglot
is a client to Language Server Protocol servers.
(use-package eglot
:commands eglot
:config
(add-to-list 'eglot-server-programs '(elm-mode . ("elm-language-server" "--stdio"))))
Unlike autocomplete which suggests words / symbols, snippets are pre-prepared templates which you fill in.
Type the shortcut and press TAB
to complete, or M-/
to autosuggest a snippet.
(use-package yasnippet
:config
(add-to-list 'yas-snippet-dirs "~/.emacs.d/snippets")
(yas-global-mode 1))
Install some premade snippets (in addition to personal ones stored above)
(use-package yasnippet-snippets)
In JS indent to 2 spaces.
(setq-default js-indent-level 2)
JS2 mode improves on the built in JS mode.
(use-package js2-mode
:mode "\\.js\\'"
:config
(setq-default js2-ignored-warnings '("msg.extra.trailing.comma")))
js2-refactor
supports some useful refactoring options and builds on top of js2-mode
.
(use-package js2-refactor
:config
(js2r-add-keybindings-with-prefix "C-c C-m")
(add-hook 'js2-mode-hook 'js2-refactor-mode))
RJSX mode makes JSX work well.
(use-package rjsx-mode)
Prettier-js autoformats JS code - much like `gofmt` - and we hook it into JS2 and RJSX modes.
(use-package prettier-js
:config
(setq prettier-js-args '(
"--trailing-comma" "es5"
"--single-quote" "true"
"--print-width" "100"
))
(add-hook 'js2-mode-hook 'prettier-js-mode)
(add-hook 'rjsx-mode-hook 'prettier-js-mode))
js-doc
makes it easy to add jsdoc comments via Ctrl+c i
.
(use-package js-doc
:bind (:map js2-mode-map
("C-c i" . js-doc-insert-function-doc)
("@" . js-doc-insert-tag))
:config
(setq js-doc-mail-address "[email protected]"
js-doc-author (format "Jamie Collinson <%s>" js-doc-mail-address)
js-doc-url "jamiecollinson.com"
js-doc-license "MIT License"))
Sometimes it’s useful to use the local eslint provided by a project’s node_modules directory. We call this function from a flycheck hook to enable it automatically.
(defun jc/use-eslint-from-node-modules ()
"Set local eslint if available."
(let* ((root (locate-dominating-file
(or (buffer-file-name) default-directory)
"node_modules"))
(eslint (and root
(expand-file-name "node_modules/eslint/bin/eslint.js"
root))))
(when (and eslint (file-executable-p eslint))
(setq-local flycheck-javascript-eslint-executable eslint))))
We often want to use local packages instead of global ones.
(use-package add-node-modules-path)
Add handling for .svelte
files.
(use-package svelte-mode)
Web mode handles html/css/js.
(use-package web-mode
:mode ("\\.html\\'")
:config
(setq web-mode-markup-indent-offset 2)
(setq web-mode-engines-alist
'(("django" . "focus/.*\\.html\\'")
("ctemplate" . "realtimecrm/.*\\.html\\'"))))
Web beautify prettifies html / css / js using js-beautify - install with npm install -g js-beautify
.
(use-package web-beautify
:bind (:map web-mode-map
("C-c b" . web-beautify-html)
:map js2-mode-map
("C-c b" . web-beautify-js)))
Markdown support isn’t built into Emacs, add it with markdown-mode
.
(use-package markdown-mode
:commands (markdown-mode gfm-mode)
:mode (("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode))
:init (setq markdown-command "multimarkdown"))
Go-mode provides basic language support, we call gofmt
on each save to keep code tidy.
(use-package go-mode
:config
(add-hook 'before-save-hook 'gofmt-before-save))
Install haskell mode.
(use-package haskell-mode)
Code formatting is easier with hindent
.
(use-package hindent)
Pyvenv handles virtual environment support.
(use-package pyvenv)
Black is an opinionated pyton formatter. Install with pip install black
so the command line tool is available.
(use-package blacken
:config
(add-hook 'python-mode-hook 'blacken-mode))
Elixir highlighting is not built into emacs at present. Elixir-mode gives all the usual niceties, and alchemist improves interaction with tools like iex
, mix
and elixir-format
.
(use-package elixir-mode
:config
(use-package alchemist))
Open .v files with Proof General’s Coq mode
(use-package proof-general)
Elm is a delightful language for reliable webapps. It compiles to JS. First install elm with npm install -g elm elm-format
.
(use-package elm-mode
:config
(setq elm-format-on-save t))
Dotnet core runs on linux / macos. Let’s get syntax highlighting.
(use-package csharp-mode)
Rust is a zero-cost abstraction systems language.
(use-package rust-mode)
I should comment on these more…
(setq org-startup-indented 'f)
(setq org-directory "~/org")
(setq org-special-ctrl-a/e 't)
(setq org-default-notes-file (concat org-directory "/notes.org"))
(define-key global-map "\C-cc" 'org-capture)
(setq org-mobile-directory "~/Dropbox/Apps/MobileOrg")
(setq org-src-fontify-natively 't)
(setq org-src-tab-acts-natively t)
(setq org-src-window-setup 'current-window)
Customize appearance.
(let*
((base-font-color (face-foreground 'default nil 'default))
(headline `(:foreground ,base-font-color)))
(custom-theme-set-faces 'user
`(org-level-8 ((t (,@headline))))
`(org-level-7 ((t (,@headline))))
`(org-level-6 ((t (,@headline))))
`(org-level-5 ((t (,@headline))))
`(org-level-4 ((t (,@headline))))
`(org-level-3 ((t (,@headline :height 1.3))))
`(org-level-2 ((t (,@headline :height 1.3))))
`(org-level-1 ((t (,@headline :height 1.3 ))))
`(org-document-title ((t (,@headline :height 1))))))
writegood-mode
highlights bad word choices and has functions for calculating readability.
(use-package writegood-mode
:bind ("C-c g" . writegood-mode)
:config
(add-to-list 'writegood-weasel-words "actionable"))
notmuch
is a fast mail client. Install it externally, e.g. with brew install notmuch
and then use it within emacs.
(use-package notmuch)
[fn:1] I hesitate to say this is the emacs way, it’s just what I felt necessary.