Permalink
Switch branches/tags
Nothing to show
Find file Copy path
c699171 May 31, 2018
1 contributor

Users who have contributed to this file

1040 lines (759 sloc) 28.8 KB

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

Setup

Bootstrap use-package

If use-package is not installed, install it.

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install '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)

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.

(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(setq package-enable-at-startup nil)

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

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

Todo: evil leader etc?

Command completion

smart M-x suggests M-x commands based on recency and frequency. I don’t tend to use it directly but counsel uses it to order suggestions.

(use-package smex)

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
    :diminish ivy-mode
    :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)))

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)

Suggest next key

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

(use-package which-key
  :diminish which-key-mode
  :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
  :diminish global-undo-tree-mode
  :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))

File tree

I don’t use this a whole lot, preferring to navigate via searches such as counsel-find-file, but it’s occasionally useful to have a file tree.

(use-package neotree
  :config
  (global-set-key (kbd "C-c t") 'neotree-toggle))

When I open the tree try to jump to current file.

(setq neo-smart-open t)

Use a simple theme for the file tree.

(setq neo-theme 'arrow)

Trying out treemacs:

(use-package treemacs)

Appearance

I’m now using my own translation of Panda Theme (now on melpa!).

(use-package panda-theme
  :config
  (load-theme 'panda t))

Set a nice font.

(set-frame-font "Operator 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)

Sometimes it helps to focus on the thing currently under the cursor. This turns off syntax highlighting for everything but the current thing. It’s useful sometimes but a bit buggy in certain modes. I wonder if I could improve the config / find a better alternative?

(use-package focus)

Highlight the current line.

(global-hl-line-mode 1)

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
  :diminish smartparens-mode
  :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))

Keep things indented correctly for me.

(use-package aggressive-indent)

Expand parentheses for me.

(add-hook 'prog-mode-hook 'electric-pair-mode)

Smart dash guesses _ vs - depending on context.

(use-package smart-dash
  :config
  (add-hook 'python-mode-hook 'smart-dash-mode))

Project management

Projectile handles folders which are in version control.

(use-package projectile
  :config
  (projectile-mode))

Tell projectile to integrate with ivy for completion.

(setq projectile-completion-system 'ivy)

Add some extra completion options via integration with counsel. In particular this enables C-c p SPC for smart buffer / file search, and C-c p s s for search via ag.

There is no function for projectile-grep, but we could use counsel-git-grep which is similar. Should I bind that to C-c p s g?

(use-package counsel-projectile
  :config
  (add-hook 'after-init-hook 'counsel-projectile-mode))

Fuzzy search

fzf is a fuzzy file finder which is very quick.

(use-package fzf)

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
  :diminish dumb-jump-mode
  :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)
  :diminish git-gutter-mode)

TimeMachine lets us step through the history of a file as recorded in git.

(use-package git-timemachine)

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

Company mode provides good autocomplete options. Perhaps I should add company-quickhelp for documentation (https://github.com/expez/company-quickhelp)?

It would also be good to improve integration with yasnippet as I don’t feel I’m making the best use there.

(use-package company
  :diminish
  :config
  (add-hook 'after-init-hook 'global-company-mode)

  (setq company-idle-delay t)

  (use-package company-go
    :config
    (add-to-list 'company-backends 'company-go))

  (use-package company-anaconda
    :config
    (add-to-list 'company-backends 'company-anaconda)))

I don’t want suggestions from open files / buffers to be automatically lowercased as these are often camelcase function names.

(setq company-dabbrev-downcase nil)

Snippets

Unlike autocomplete which suggests words / symbols, snippets are pre-prepared templates which you fill in.

I’m using a community library ([[https://github.com/AndreaCrotti/yasnippet-snippets]]) with lots of ready made options, and have my own directory of custom snippets I’ve added. Not sure if I should unify these by forking yasnippet-snippets.

Type the shortcut and press TAB to complete, or M-/ to autosuggest a snippet.

(use-package yasnippet
    :diminish yas-minor-mode
    :config
    (add-to-list 'yas-snippet-dirs "~/.emacs.d/yasnippet-snippets")
    (add-to-list 'yas-snippet-dirs "~/.emacs.d/snippets")
    (yas-global-mode)
    (global-set-key (kbd "M-/") 'company-yasnippet))

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

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 eldoc to display documentation and add guru / doctor for IDE functionality.

(use-package go-mode
  :config
  (add-hook 'before-save-hook 'gofmt-before-save)

  (use-package go-eldoc
    :config
    (add-hook 'go-mode-hook 'go-eldoc-setup))

  (use-package godoctor)

  (use-package go-guru))

Go guru needs a scope to look at, this function sets it to the current package.

(defun jc/go-guru-set-current-package-as-main ()
  "GoGuru requires the scope to be set to a go package which
   contains a main, this function will make the current package the
   active go guru scope, assuming it contains a main"
  (interactive)
  (let* ((filename (buffer-file-name))
         (gopath-src-path (concat (file-name-as-directory (go-guess-gopath)) "src"))
         (relative-package-path (directory-file-name (file-name-directory (file-relative-name filename gopath-src-path)))))
    (setq go-guru-scope relative-package-path)))

Haskell

Install haskell mode.

(use-package haskell-mode)

Code formatting is easier with hindent.

(use-package hindent)

Completion is via ghc-mod / company. Install the former separately with cabal install ghc-mod.

(use-package ghc
  :config
  (add-hook 'haskell-mode-hook (lambda () (ghc-init))))

(use-package company-ghc
  :config
  (add-to-list 'company-backends 'company-ghc))

Python

Emacs handles python quite well, but we can improve things with anaconda mode.

(use-package anaconda-mode
  :config
  (add-hook 'python-mode-hook 'anaconda-mode)
  (add-hook 'python-mode-hook 'anaconda-eldoc-mode))

Black is an opinionated pyton formatter. Install with pip install black so the command line tool is available.

(use-package blacken)

Sometimes I use kivy.

(use-package kivy-mode
  :mode ("\\.kv\\'" . kivy-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))

Cucumber

Cucumber (gherkin) is a syntax for specifying behaviour driven development tests.

(use-package feature-mode)

Rebol / Red

Red is an updated open source implementation of Rebol.

(use-package rebol)

Idris

(use-package idris-mode)

Coq

Proof general must be installed separately via:

git clone https://github.com/ProofGeneral/PG ~/.emacs.d/lisp/PG
make -C ~/.emacs.d/lisp/PG

Open .v files with Proof General’s Coq mode

(require 'proof-site "~/.emacs.d/lisp/PG/generic/proof-site")
(use-package company-coq
  :hook (coq-mode . company-coq-mode))

Elm

Elm is a delightful language for reliable webapps. It compiles to JS. First install elm with npm install -g elm elm-oracle elm-format.

(use-package elm-mode
  :config
  (setq elm-format-on-save t)
  (add-to-list 'company-backends 'company-elm))

C

Emacs has a great built in C/C++ mode, but we can improve on it with irony-mode for code completion via libclang.

(use-package irony
  :hook (c-mode . irony-mode))

Add company mode support.

(use-package company-irony
  :config
  (add-to-list 'company-backends 'company-irony))

Add flycheck support.

(use-package flycheck-irony
  :hook (flycheck-mode . flycheck-irony-setup))

C#

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

(use-package csharp-mode)

Omnisharp gives completion / refactoring support, and hooks into company-mode.

(use-package omnisharp
  :hook ((csharp-mode . omnisharp-mode)
         ;; TODO: 'before-save runs globally - make this buffer local?
         (before-save . omnisharp-code-format-entire-file))
  :config
  (add-to-list 'company-backends 'company-omnisharp))

F#

Since we have dotnet core why not?

(use-package fsharp-mode)

Org

General settings.

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)

Appearance

Improve the display of bullet points.

(use-package org-bullets
  :config
  (setq org-bullets-bullet-list '(""))
  (add-hook 'org-mode-hook 'org-bullets-mode))

Customize appearance.

(let*
    ((variable-tuple (cond
                      ((x-list-fonts "Source Sans Pro") '(:font "Source Sans Pro"))
                      ((x-list-fonts "Lucida Grande")   '(:font "Lucida Grande"))
                      ((x-list-fonts "Verdana")         '(:font "Verdana"))
                      ((x-family-fonts "Sans Serif")    '(:family "Sans Serif"))
                      (nil (warn "Cannot find a Sans Serif Font.  Install Source Sans Pro."))))
     (base-font-color     (face-foreground 'default nil 'default))
     (headline           `(:inherit default :weight normal :foreground ,base-font-color)))

  (custom-theme-set-faces 'user
                          `(org-level-8 ((t (,@headline ,@variable-tuple))))
                          `(org-level-7 ((t (,@headline ,@variable-tuple))))
                          `(org-level-6 ((t (,@headline ,@variable-tuple))))
                          `(org-level-5 ((t (,@headline ,@variable-tuple))))
                          `(org-level-4 ((t (,@headline ,@variable-tuple))))
                          `(org-level-3 ((t (,@headline ,@variable-tuple :height 1.33))))
                          `(org-level-2 ((t (,@headline ,@variable-tuple :height 1.33))))
                          `(org-level-1 ((t (,@headline ,@variable-tuple :height 1.33 ))))
                          `(org-document-title ((t (,@headline ,@variable-tuple :height 1.33 :underline nil))))))

Export

Add bootstrap styled export.

(use-package ox-twbs)

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

Stack Overflow

SX is a full stack overflow client within Emacs.

(use-package sx
  :config
  (bind-keys :prefix "C-c s"
             :prefix-map my-sx-map
             :prefix-docstring "Global keymap for SX."
             ("q" . sx-tab-all-questions)
             ("i" . sx-inbox)
             ("o" . sx-open-link)
             ("u" . sx-tab-unanswered-my-tags)
             ("a" . sx-ask)
             ("s" . sx-search)))

Web browsing

w3m is a terminal based browser. Emacs now has eww built in, but I prefer w3m. Install it separately, e.g. with brew install w3m.

There’s an emacs package to interface with it.

(use-package w3m)

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)

Rest client

Sometimes I need to explore REST services. Why not Emacs?

(use-package restclient)

Slack

I use slack for work.

(use-package slack
  :commands (slack-start))

Google

It’s always useful to look things up.

(use-package google-this)

[fn:1] I hesitate to say this is the emacs way, it’s just what I felt necessary.