Emacs Config

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 it1.

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

Setup

Configure package sources

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/")))

Bootstrap use-package

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 startup

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))))

Increase garbage collector threshold

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)))

Make it easy to edit this file

  (defun find-config ()
    "Edit config.org"
    (interactive)
    (find-file "~/dotfiles/config.org"))

  (global-set-key (kbd "C-c I") 'find-config)

Set custom settings to load in own file

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"))

Add custom packages to load path

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/")

Record key frequency

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))

Preferences

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)

Interface

Basics

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

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

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?

Goto last change

Sometimes it's useful to step to the last changes in a buffer.

  (use-package goto-last-change
    :bind (("C-;" . goto-last-change)))

Command completion

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 key

Suggest next keys to me based on currently entered key combination.

  (use-package which-key
    :config
    (add-hook 'after-init-hook 'which-key-mode))

Better undo

undo-tree visualises undo history as a tree for easy navigation.

  (use-package undo-tree
    :defer 5
    :config
    (global-undo-tree-mode 1))

Navigation

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)))

Easier selection

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))

Fullscreen

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)))

Appearance

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))

Coding

Programming specific interface improvements

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)

Fuzzy search

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)

Environment management

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))

Jump to source

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)))

Git

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))

Syntax checking

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))

Autocomplete

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"))))

Snippets

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)

Javascript

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 "jamiecollinson@gmail.com"
         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)

Web 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

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

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"))

Golang

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))

Haskell

Install haskell mode.

  (use-package haskell-mode)

Code formatting is easier with hindent.

  (use-package hindent)

Python

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

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))

Coq

Open .v files with Proof General's Coq mode

  (use-package proof-general)

Elm

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))

C#

Dotnet core runs on linux / macos. Let's get syntax highlighting.

  (use-package csharp-mode)

Rust

Rust is a zero-cost abstraction systems language.

  (use-package rust-mode)

Org

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))))))

Extras

Writing

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"))

Email

notmuch is a fast mail client. Install it externally, e.g. with brew install notmuch and then use it within emacs.

  (use-package notmuch)

1

I hesitate to say this is the emacs way, it's just what I felt necessary.