My Emacs configuration (contents of ~/.emacs.d/)
Emacs Lisp Shell
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
.gitignore Ignore Jan 15, 2018
.travis.yml Allow test failures on emacs snapshot Aug 21, 2017 Make also be Jul 21, 2014 Add autoload for cl-lib-highlight-warn-cl-initialize Feb 3, 2018
custom.el Custom changes Feb 3, 2018
init.el Add MELPA to package-archives Aug 20, 2017

Introductory Notes

This is my Emacs configuration. Most of it is stored as Emacs Lisp source blocks in in this Org-mode file.

This file is called “” and not “” because during the loading process, it creates a correspondingly-named “.el” file, and there already is an “init.el” here. Note also that “” is a link here.

How to install this config

In order to install this config, simply clone this repository into ~/.emacs.d/:

git clone ~/.emacs.d

If you have an old .emacs file or .emacs.d directory that you want to save, move them out of the way first so that they don’t interfere with the new config.

First run slow startup

The first time you start up Emacs after installing this configuration, the code in init.el will install org-mode and then use that to run the Emacs lisp code in this file, which will in turn install the many packages used by this config. This could take quite a while. Subsequent startups will skip the install process and should be relatively fast.

Enable Lexical Binding

In order to enable lexical binding in the config, this must be the first line of Emacs Lisp:

;; -*- lexical-binding: t -*-


The init.el file does all the bootstrapping necessary to load the configuration in this Org-mode file. It is required as a separate non-Org-mode file because this config does not use the version of Org-mode included with Emacs, so the proper version of Org mode must be installed before loading this file. Otherwise, loading this file first would auto-load the built-in version of Org-mode and prevent use of the newly-installed version until Emacs was restarted.

Initial Quelpa setup

This config uses quelpa-use-package to implement declarative package configuration with Quelpa and use-package. This section installs those packages so that they can be used to set up all other packages.

Install Quelpa

First, we try to load Quelpa, and install it if it isn’t already installed. This uses the standard Quelpa bootstrapping code.

  (if (require 'quelpa nil t)
(require 'quelpa)

(Don’t) Prefer MELPA stable

Next, we tell Quelpa to use the stable version of each package by default.

(setq quelpa-stable-p nil)

Install quelpa-use-package

Finally we use Quelpa to install quelpa-use-package, which allows use-package to install packages using Quelpa.

   :fetcher github
   :repo "quelpa/quelpa-use-package")
 :stable nil)
(require 'quelpa-use-package)

Preliminary environment setup

This section sets up a functions and other aspects of the environment that will be needed later in the configuration, so they need to be defined/prepared up front.

Install and load basic libraries

This installs the s and f packages for string and filename manipulation.

(use-package f :quelpa)
(use-package s :quelpa)

Fixing Default Directory

Regardless of which directory Emacs is started from, I want the initial non-file buffers such as *scratch* and *Messages* to have their default-directory set to my home directory. This code goes through all non-file buffers whose default directories are the emacs starting directory or the root directory, and changes their default directories to my home directory.

This code only runs during init. If the config is reloaded later after init, this will not run again.

(unless after-init-time
  (let ((startup-dir default-directory))
    (unless (f-same? default-directory "~")
      (dolist (buf (buffer-list))
          (with-current-buffer buf
            (when (and (null (buffer-file-name buf))
                       (not (bound-and-true-p dired-directory))
                       (or (f-same? default-directory startup-dir)
                           (f-root? default-directory)))
              (message "Changing default dir from %s to ~/ in %s"
                       default-directory (buffer-name buf))
              (cd "~"))))))))

Define eval after init function

We define a function to defer evaluation until the end of initialization.

(defun eval-after-init (form)
  "Like `eval', but waits until after init.

During emacs initialization, this registers FORM to be evaluated
in `after-init-hook'. After initialization, this is equivalent
to `(eval FORM)'."
  (if after-init-time
      (eval form)
    (add-to-list 'after-init-hook `(lambda () ,form))))

Define macro to protect buffer modified status

This defines a macro that saves the modified status of current buffer and restores it after evaluating body.

(defmacro preserve-buffer-modified-p (&rest body)
  "Evaluate BODY, then restore buffer modified status.

This can be used to edit the contents of a buffer while telling
Emacs that the buffer is still not modified."
  (declare (indent 0))
  `(let ((bmp (buffer-modified-p)))
         (progn ,@body)
       (set-buffer-modified-p bmp))))

Ensure persistence directory exists

For any code that wants to save some state to disk (e.g. undo-tree), I configure it to save its state somewhere in this directory.

(make-directory (f-join user-emacs-directory "persistence") 'recursive)

Set PATH and MANPATH from shell

My shell configuration adds a lot of things to PATH dynamically (pyenv, perlbrew, etc.), so rather than emulate that in Emacs, we simply run a shell and tell it to print out the environment variables we care about. Then we set them in Emacs. For PATH, we also set the Elisp variable exec-path, which is not auto-updated when you modify the environment variable.

This step needs to be done early, because some later configuration items depend on having the full PATH available.

(use-package exec-path-from-shell
  :demand t
  :config (exec-path-from-shell-initialize))

Set up and load a separate custom file

This is the file where everything set via M-x customize goes.

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (f-exists? custom-file)
  (load custom-file))

Install req-package

req-package is an extension of use-package that automatically loads all packages in the correct order, based on dependencies declared in the config. So if the setup code for one package depends on having another package loaded, req-package can handle that.

(use-package req-package :quelpa)

Package configuration

This section declares all the packages required by the config and sets up variables, key bindings, and such for some of them.

Eval the following Elisp code to re-sort the below entries (this code line is not in a source block because it is not part of the actual configuration and should not be executed upon init):

CODE (mapc (apply-partially #'org-sort-entries nil) (nreverse '(?O ?a))) CODE


This allows TAB and S-TAB to increase and decrease the nesting depth (and corresponding indentation) of the current lisp expression.

(req-package adjust-parens
  :demand t
  :commands adjust-parens-mode
  :init (progn
          (defun turn-on-adjust-parens-mode ()
            (adjust-parens-mode 1))
          (dolist (hook '(lisp-interaction-mode-hook
            (add-hook hook #'turn-on-adjust-parens-mode)))
  :ensure t)


amx is an enhanced M-x.

(req-package amx


Anzu mode displays the total number of matches and which one is currently highlighted while doing an isearch.

(req-package anzu :quelpa)


This loads apache-mode and sets it up to detect the vim “syntax=apache” declaration.

(req-package apache-mode
  :mode ("/apache2/.*\\.conf\\'" . apache-mode)
  :init (progn
          (defun apache-magic-mode-detect ()
            (string-match-p "^\\s-*#.*\\bsyntax=apache\\b" (buffer-string)))
          (add-to-list 'magic-mode-alist '(apache-magic-mode-detect . apache-mode)))


Auto-complete mode provides IDE-style popup completions while editing.

(req-package auto-complete
  :init (global-auto-complete-mode 1)


This package slightly dims the background of inactive windows so as to highlight which window is currently active.

(req-package auto-dim-other-buffers :quelpa)


(req-package autopair
    (autopair-global-mode 1)
    (setq autopair-skip-whitespace 'chomp)
    (setq autopair-skip-criteria 'always))
  :defer nil


This changes the cursor from a 1-character block to a bar in between characters.

(req-package bar-cursor


Beacon mode causes the “spotlight” to shine on the cursor whenever the window scrolls, in order to highlight the new position of the cursor.

(req-package beacon :quelpa)


(req-package bind-key :quelpa)


This binds Shift+click to open a link

(req-package browse-url
  :bind ("<s-mouse-1>" . browse-url-at-mouse)

bs (Buffer Show)

(req-package bs
  :bind ("C-x C-b" . bs-show)


Buttercup is a testing framework that I use to test several of my Emacs Lisp packages.

(req-package buttercup
  :quelpa (buttercup
           :fetcher github
           :repo "DarwinAwardWinner/emacs-buttercup"))


(req-package cask :quelpa)
(req-package cask-mode :quelpa)


(req-package cl-lib)


This package higlights cl-lib functions and macros, and also higlights old-styls cl functions and macros in orange as a reminder not to use them.

(req-package cl-lib-highlight
  :defer nil)


Replace perl-mode with cperl-mode in auto-mode-alist and interpreter-mode-alist. Also associate the “.t” extension with perl (perl test files). Last, define a keyboard shortcut for cperl-perldoc.

(req-package cperl-mode
     (lambda (x)
       (when (eq (cdr x) 'perl-mode)
         (setcdr x 'cperl-mode)))
     (lambda (x)
       (when (eq (cdr x) 'perl-mode)
         (setcdr x 'cperl-mode)))
  :bind (:map cperl-mode-map
              ("C-c C-d". cperl-perldoc))
  :mode ("\\.[tT]\\'" . cperl-mode))


(req-package creole-mode
  :mode (".creole\\'" . creole-mode))


This provides some useful utility functions, many of which are more advanced versions of existing commands.

(req-package crux
  :bind (("C-k" . crux-smart-kill-line)
         ("C-c C-e" . crux-eval-and-replace)
         ("C-x 4 t" . crux-transpose-windows)
         ([remap move-beginning-of-line] . crux-move-beginning-of-line)))


This hides or shortens the names of minor modes in the modeline.

The below code sets up a custom variable diminished-minor-modes to control the diminishing of modes.

(req-package diminish
  :demand t
  (defun diminish-undo (mode)
    "Restore mode-line display of diminished mode MODE to its minor-mode value.
Do nothing if the arg is a minor mode that hasn't been diminished.

Interactively, enter (with completion) the name of any diminished mode (a
mode that was formerly a minor mode on which you invoked M-x diminish).
To restore all diminished modes to minor status, answer `all'.
The response to the prompt shouldn't be quoted.  However, in Lisp code,
the arg must be quoted as a symbol, as in (diminish-undo 'all)."
     (if diminished-mode-alist
         (list (read (completing-read
                      "Restore what diminished mode: "
                      (cons (list "all")
                            (mapcar (lambda (x) (list (symbol-name (car x))))
                      nil t nil 'diminish-history-symbols)))
       (error "No minor modes are currently diminished.")))
    (if (eq mode 'all)
        (cl-loop for dmode in diminished-mode-alist
                 for mode-name = (car dmode)
                 do (diminish-undo mode-name))
      (let ((minor      (assq mode      minor-mode-alist))
            (diminished (assq mode diminished-mode-alist)))
        (or minor
            (error "%S is not currently registered as a minor mode" mode))
        (when diminished
          (setq diminished-mode-alist (remove diminished diminished-mode-alist))
          (setcdr minor (cdr diminished))))))

  (defun diminish-setup (symbol newlist)
    ;; Replace symbols with one-element lists, so that each element of
    ;; NEWLIST is a valid arglist for `diminish'.
    (setq newlist
          (mapcar (lambda (x) (if (listp x) x (list x)))
    (set-default symbol newlist)
    ;; Un-diminish all modes
    (diminish-undo 'all)
    ;; Diminish each mode the new list
    (mapc (lambda (x)
            (unless (listp x)
              (setq x (list x)))
            (when (assq (car x) minor-mode-alist)
              (message "Diminishing %S" x)
              (diminish (car x) (cdr x))))

  (defcustom diminished-minor-modes '()
    "Minor modes to be diminished, and their diminished text, if any."
    :group 'diminish
    :type '(alist :key-type (symbol :tag "Mode")
                  :value-type (choice :tag "To What"
                                      (const :tag "Hide completely" "")
                                      (string :tag "Abbreviation")))
    :set 'diminish-setup)

  (defun diminish-init ()
    (diminish-setup 'diminished-minor-modes diminished-minor-modes))



This allows Emacs to support EditorConfig files. See

(req-package editorconfig
  :demand t
  (editorconfig-mode 1))


(req-package ess
  ;; Ensure that TRAMP is loaded before ESS, since loading ESS before
  ;; TRAMP causes problems
  (require 'tramp)
  (setq ess-default-style 'OWN)
   ;; Based on (cdr (assoc 'C++ ess-style-alist))
   '((ess-indent-offset . 4)
     (ess-offset-arguments . open-delim)
     (ess-offset-arguments-newline . prev-line)
     (ess-offset-block . prev-call)
     (ess-offset-continued . straight)
     (ess-align-nested-calls "ifelse")
     (ess-align-arguments-in-calls "function[ 	]*(")
     (ess-align-continuations-in-calls . t)
     (ess-align-blocks control-flow)
     (ess-indent-from-lhs arguments)
     (ess-indent-from-chain-start . t)
  (define-advice ess-smart-S-assign
      (:around (orig-fun &rest args) underscore-unless-space)
    "Always insert underscores unless point is after a space.

This advice is only active if `ess-smart-S-assign-key' is \"_\"."
    (if (and (looking-back "[^[:space:]]" 1)
             (string= ess-smart-S-assign-key "_"))
        (insert ess-smart-S-assign-key)
      (apply orig-fun args)))
  (add-to-list 'auto-mode-alist '("\\.Rprofile\\'" . R-mode))
  ;; TODO: Put this function in a more appropriate location
  (defun twiddle-mode (mode)
    "If MODE is activated, then deactivate it and then activate it again.
If MODE is not active, do nothing."
    (when (eval mode)
      (funcall mode 0)
      (funcall mode 1)))


(req-package esup
  :defer t


This package puts change indicators in the buffer fringe to indicate what parts of the file have been added, deleted, or modified since the last Git commit.

(req-package git-gutter
(req-package git-gutter-fringe


Git-wip saves a hidden commit after each file is saved, thus saving a full history of all your edits since the last real commit. We need a special config to install the git-wip command-line script along with the Emacs Lisp file, and to tell Emacs where to find the script.

(req-package git-wip-mode
  :quelpa (git-wip-mode
           :fetcher github :repo bartman/git-wip
           :files ("emacs/git-wip-mode.el" "git-wip"))
  (setq git-wip-path
        (f-join (f-dirname (locate-library "git-wip-mode"))


This automatically inserts a header into any new elisp file.

(req-package header2
    (define-advice make-header (:after (&rest args) add-lexbind-variable)
      "Add `lexical-binding: t' to header."
      (when (eq major-mode 'emacs-lisp-mode)
          (add-file-local-variable-prop-line 'lexical-binding t))))
    (defsubst header-not-part-of-emacs ()
      "Insert line declaring that this file is not part of Emacs."
      (when (eq major-mode 'emacs-lisp-mode)
        (insert header-prefix-string "This file is NOT part of GNU Emacs.\n")))
    (defsubst header-completely-blank ()
      "Insert an empty line to file header (not even `header-prefix-string')."
      (insert "\n"))
    (setq header-copyright-notice
          (format-time-string "Copyright (C) %Y Ryan C. Thompson\n"))
    ;; Set up headers when creating an elisp file
    (add-hook 'emacs-lisp-mode-hook #'auto-make-header)
    ;; Update headers on save
    (add-hook 'write-file-hooks #'auto-update-file-header)
    ;; Override `header-eof' to not insert a separator line
    (defun header-eof ()
      "Insert comment indicating end of file."
      (goto-char (point-max))
      (insert "\n")
      (insert comment-start
              (concat (and (= 1 (length comment-start)) header-prefix-string)
                      (if (buffer-file-name)
                          (file-name-nondirectory (buffer-file-name))
                      " ends here"
                      (or (nonempty-comment-end) "\n"))))
    ;; Function to insert `provide' statement at end of file; This is
    ;; used in `make-header-hook'.
    (defun header-provide-statement ()
      "Insert `provide' statement."
        (goto-char (point-max))
         (format "\n%s"
                 (pp-to-string `(provide ',(intern (f-base (buffer-file-name)))))))))
    ;; Prevent `auto-make-header' from setting the buffer modified flag
    (define-advice auto-make-header
        (:around (orig-fun &rest args) dont-set-buffer-modified)
      "Don't set the buffer modified flag."
        (apply orig-fun args))))
  :demand t


This mode highlights all defined Emacs Lisp symbols instead of just built-in ones.

(req-package highlight-defined
  :init (progn
          (defun turn-on-highlight-defined-mode ()
            (highlight-defined-mode 1))
          (add-hook 'emacs-lisp-mode-hook 'turn-on-highlight-defined-mode)
          (add-hook 'lisp-interaction-mode-hook 'turn-on-highlight-defined-mode)
          (eval-after-load 'ielm
            (add-hook 'inferior-emacs-lisp-mode-hook 'turn-on-highlight-defined-mode)))


(req-package highlight-stages :quelpa)


My personal fork of ido-complete-space-or-hyphen. It converts the package into a full-fledged minor mode.

TODO: Switch back to stock version after this is merged:

(req-package ido-complete-space-or-hyphen
  :quelpa (ido-complete-space-or-hyphen
           :fetcher github
           :repo DarwinAwardWinner/ido-complete-space-or-hyphen))


This uses my bleeding-edge branch of ido-completing-read+.

(req-package ido-completing-read+
  :quelpa (ido-completing-read+
           :fetcher github
           :repo DarwinAwardWinner/ido-completing-read-plus))


(req-package ido-yes-or-no :quelpa)


(req-package image+
  :after image)


This package provides shortcuts to quickly open the user’s init file for editing.

(req-package iqa
  :demand t
  (setq iqa-user-init-file (f-join user-emacs-directory "")))


This indicates in the mode-line for each Emacs Lisp file whether lexical binding is enabled for that file.

(req-package lexbind-mode
  :init (add-hook 'emacs-lisp-mode-hook (apply-partially #'lexbind-mode 1))


This package allows Emacs to emulate the Mac OS behavior of staying open after the last window is closed, by creating a new window and hiding it until Emacs is reactivated.

(req-package mac-pseudo-daemon


This sets up magit, the Emacs Git interface.

Magit itself

The defvar suppresses Magit upgrade instructions. The magit-init advice causes Magit to display the status buffer for an new repository immediately after a git init (but only when called interactively).

It’s nice to have the added features of GitHub’s hub command, but magit makes a lot of calls to git, and so redirectig it to hub all the time slows it down considerably. So we use advice to tell only magit-git-command to use hub if it is available. This allows the user to interactively invoke hub commands without slowing down Magit in normal operation.

(req-package magit
  :bind (("C-c g" . magit-status))
  ;; This needs to be set or else magit will warn about things.
  (defvar magit-last-seen-setup-instructions "1.4.0")
  (define-advice magit-init (:after (&rest args) show-status)
    "Show the status buffer after initialization if interactive."
    (when (called-interactively-p 'interactive)
      (magit-status-internal directory)))
  ;; Redirect "git" command to "hub" for interactive use only
  (defvar magit-hub-executable (when (executable-find "hub") "hub"))
  (define-advice magit-git-command (:around (orig-fun &rest args) use-hub)
    "Use `hub' instead of `git' if available."
    (let ((magit-git-executable
           (or magit-hub-executable magit-git-executable)))
      (apply orig-fun args)))

;; Magit is apparently missing an autoload for `magit-process-file',
;; which is called by a function in `post-command-hook', resulting in
;; an unusable emacs unless the autoload is added manually.
(req-package magit-process
  :require magit
  :commands (magit-process-file))


This package allows magit to refresh the status buffer whenever a file is modified. This mode causes problems on remote (TRAMP) files, so we only enable it for local files.

(req-package magit-filenotify
  :require magit
  (defun turn-on-magit-filenotify-mode-if-local ()
     (if (file-remote-p default-directory)
  (add-hook 'magit-status-mode-hook


This is an optional dependency of markdown-mode.

(req-package edit-indirect
  :defer t)


This mode is for editing Markdown files.

(req-package markdown-mode
  :mode ("\\.\\(md\\|mkdn\\)$" . markdown-mode)


Noflet provides an enhanced version of flet, and more importantly, provides proper indentation support for flet-like macros.

(req-package noflet


This package allows the user to dynamically change the number of context lines around matches in an occur-mode buffer using the plus and minus keys (and 0 key to reset)

(req-package occur-context-resize
  :init (add-hook 'occur-mode-hook 'occur-context-resize-mode)


The default implementation of the org-in-src-block-p function is broken and always returns nil, so we reimplement it correctly here. We also add a function to insert a new src block into an org-mode buffer.

Note that there’s no :ensure t or :quelpa here, since org-mode must already be installed if this code is running.

(req-package org
  :commands org-clocking-buffer
  (require 'ox-extra)
  (ox-extras-activate '(latex-header-blocks ignore-headlines))
  ;; Custom src-block behaviors
    (defun org-in-src-block-p (&optional inside)
      "Whether point is in a code source block.
     When INSIDE is non-nil, don't consider we are within a src block
     when point is at #+BEGIN_SRC or #+END_SRC."
        (let* ((elem (org-element-at-point))
               (elem-type (car elem))
               (props (cadr elem))
               (end (plist-get props :end))
               (pb (plist-get props :post-blank))
                  (goto-char end)
                  (forward-line (- pb))
               (case-fold-search t))
           ;; Elem is a src block
           (eq elem-type 'src-block)
           ;; Make sure point is not on one of the blank lines after the
           ;; element.
           (< (point) content-end)
           ;; If INSIDE is non-nil, then must not be at block delimiter
               (looking-at ".*#\\+\\(begin\\|end\\)_src"))))))))
    (defun org-insert-src-block (src-code-type)
      "Insert a `SRC-CODE-TYPE' type source code block in org-mode."
       (let ((src-code-types
              '("emacs-lisp" "python" "C" "sh" "java" "js" "clojure" "C++" "css"
                "calc" "asymptote" "dot" "gnuplot" "ledger" "lilypond" "mscgen"
                "octave" "oz" "plantuml" "R" "sass" "screen" "sql" "awk" "ditaa"
                "haskell" "latex" "lisp" "matlab" "ocaml" "org" "perl" "ruby"
                "scheme" "sqlite")))
         (list (ido-completing-read "Source code type: " src-code-types))))
        (insert (format "#+BEGIN_SRC %s\n" src-code-type))
        (insert "#+END_SRC\n")
        (forward-line -2)
    (defun org-insert-or-edit-src-block (src-code-type &optional interactive-call)
      "Insert a source code block in org-mode or edit an existing one."
      (interactive (list nil t))
      (if (org-in-src-block-p)
        (if interactive-call
            (call-interactively 'org-insert-src-block)
          (org-insert-src-block src-code-type)))))
  ;; Allow an emphasized expression to extend over 5 lines
    (setcar (nthcdr 2 org-emphasis-regexp-components) " \t\r\n\"'")
    (setcar (nthcdr 4 org-emphasis-regexp-components) 15)
    (org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components))
  :bind (:map org-mode-map
              ("C-c C-'" . org-insert-or-edit-src-block)
              :map org-src-mode-map
              ("C-c C-'" . org-edit-src-exit)
              ("C-c C-c" . org-edit-src-exit)))


This provides pretty-looking bullets for org-mode.

(req-package org-bullets
  :init (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))

Stable-ish HTML anchors for org export

This code makes the anchors IDs generated when exporting org files to HTML less random. It sets the seed to a specific value before executing the export, which means that it should always generate the same anchors IDs given the same set of headlines.

(defmacro with-reproducible-rng (seed &rest body)
  "Execute BODY with reproducible RNG.

Before executing BODY, the random number generator will be
initialized with SEED, which should be a string (see `random').
Hence, the sequence of random numbers returned by `random' within
BODY will be reproducible. After BODY finishes, the random number
generatore will be reinitialized from system entropy, and will
therefore no longer be predictable.

\(There does not seem to be a way to save and restore a specific
RNG state, so the RNG state after executing this macro will not
be the same as it was prior.)"
  (declare (indent 1))
         (random (or ,seed ""))
     (random t)))

(define-advice org-html-export-to-html (:around (orig-fun &rest args) reproducible-rng)
  "Use a reproducible RNG stream for HTML export.

This results in the same pseudo-random anchor IDs for
the same set of headlines every time."
  (with-reproducible-rng "org-html-export"
    (apply orig-fun args)))


(req-package paradox :quelpa)


(req-package pointback :quelpa)


This mode allows editing files with multiple major modes, such as Rmarkdown files, where some parts of the file are Markdown and others are R code.

(req-package polymode
  :mode ("\\.Rmd\\'" . poly-markdown+r-mode))


This package allows replacing certain words with symbols, for example replacing “lambda” with λ in Lisp code. The replacement is purely visual, and the files are saved with the original words.

(req-package pretty-symbols
    (defun pretty-symbols-enable-if-available ()
      "Enable pretty-symbols in buffer if applicable.

If current buffer's `major-mode' has any pretty symbol
   substitution rules associated with it, then enable
   `pretty-symbols-mode', otherwise do nothing."
      (when (apply #'derived-mode-p
                   (cl-mapcan (lambda (x) (cl-copy-list (nth 3 x)))
       (pretty-symbols-mode 1)))
    (add-hook 'after-change-major-mode-hook #'pretty-symbols-enable-if-available))


“.pyi” is the file extension for the Python typeshed’s type annotations. These files are valid (but incomplete) Python syntax, so regular python-mode is just fine.

(req-package python
  :mode ("\\.pyi" . python-mode))


(req-package rainbow-delimiters
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode-enable))


(req-package reveal-in-osx-finder


(req-package shrink-whitespace
  :commands shrink-whitespace)


(req-package slime


(req-package sml-modeline
  :quelpa (sml-modeline
           :fetcher github
           :repo emacsmirror/sml-modeline))


(req-package smooth-scrolling
   :fetcher github
   :repo "DarwinAwardWinner/smooth-scrolling"))


(req-package snakemake-mode


(req-package system-specific-settings :quelpa)


(req-package tempbuf
  :quelpa (tempbuf
           :fetcher github
           :repo emacsattic/tempbuf)
  (defun mode-symbol (sym)
    "Append \"-mode\" to SYM unless it already ends in it."
    (let ((symname (symbol-name sym)))
       (concat symname
               (unless (s-suffix? "-mode" symname)

  (defun tempbuf-major-mode-hook ()
    "Turn on `tempbuf-mode' in current buffer if buffer's `major-mode' is in `tempbuf-temporary-major-modes'.

Else turn off `tempbuf-mode'."
    (if (apply #'derived-mode-p tempbuf-temporary-major-modes)

  (defun tempbuf-setup-temporary-major-modes (symbol newval)
    (set-default symbol (mapcar 'mode-symbol newval))
    ;; Set tempbuf-mode correctly in existing buffers.
    (mapc (lambda (buf)
            (with-current-buffer buf

  (defcustom tempbuf-temporary-major-modes nil
    "Major modes in which `tempbuf-mode' should be activated.

This will cause buffers of these modes to be automatically killed
if they are inactive for a short while."
    :group 'tempbuf
    :set 'tempbuf-setup-temporary-major-modes
    :type '(repeat (symbol :tag "Mode")))

  (add-hook 'after-change-major-mode-hook 'tempbuf-major-mode-hook)
  ;; This mode requires special handling because it somehow avoids
  ;; using `after-change-major-mode-hook', I think.
  (eval-after-load 'ess-custom
    '(add-hook 'ess-help-mode-hook 'tempbuf-major-mode-hook)))


(req-package undo-tree :quelpa)


(req-package volatile-highlights
  (put 'vhl/define-extension 'lisp-indent-function 1)
  (vhl/define-extension 'adjust-parens
  (vhl/install-extension 'adjust-parens)
  (vhl/define-extension 'undo-tree
    'undo-tree-yank 'undo-tree-move)
  (vhl/install-extension 'undo-tree)
  ;; Clear volatile highlights after 1 second
  (setq vhl/idle-clear-timer
        (run-with-idle-timer 1 t #'vhl/clear-all)))

(define-advice vhl/add-range (:before-while (&rest args) disable-in-read-only-buffers)
  "Don't do volatile highlights in read-only buffers"
  (not buffer-read-only))


(req-package which-key
  :defer t)


(req-package with-simulated-input


(req-package ws-butler :quelpa)


(req-package yaml-mode :quelpa)

Install and load all configured packages

The req-package forms above only declare the set of packages to be installed and loaded. They don’t actually do anything until the line of code below is run. At this time, req-package resolves any dependencies between packages and then installs and loads them in the correct order to satisfy those dependencies.



This section contains a set of tweaks to Emacs behavior that are not specific to a single package and cannot be accomplished by customizing variables.

Fixes for packages

(Currently none)

Environment tweaks

Use GNU ls for insert-directory if possible

On OS X (and probably other platforms), “ls” may not refer to GNU ls. If GNU ls is installed on these platforms, it is typically installed under the name “gls” instead. So if “gls” is available, we prefer to use it.

(if (executable-find "gls")
    (setq insert-directory-program "gls"))

Use external mailer for bug reports

This calls report-emacs-bug, then report-emacs-insert-to-mailer, then cleans up the bug buffers.

The backquoting interpolation is used to copy the interactive form from report-emacs-bug.

 `(defun report-emacs-bug-via-mailer (&rest args)
    "Report a bug in GNU Emacs.

Prompts for bug subject. Opens external mailer."
    ,(interactive-form 'report-emacs-bug)
      (apply 'report-emacs-bug args)
      (mapc (lambda (buf)
              (with-current-buffer buf
                (let ((buffer-file-name nil))
                  (kill-buffer (current-buffer)))))
            (list "*Bug Help*" (current-buffer))))))

Tell Emacs where to find its C source code

This is where I keep Emacs, but you’ll probably need to edit this if you want look at the definitions of Emacs primitive functions.

(setq find-function-C-source-directory "~/Projects/emacs/src")

Fix OSX movement keys (unswap some Command/Option shortcuts)

I map Option -> Super and Command -> Meta in Emacs on Mac OS, which is the opposite of what it is by default, because I need Emacs’ meta key to be directly below X. However, there are a handful of shortcuts involving Command/Option that I don’t want swapped, so I need to swap their Super/Meta bindings to cancel out the swapping of Super and Meta themselves.

;; Use `eval-after-load' to ensure that this always happens after
;; loading custom.el, since that sets the Command/Option modifiers.
 ;; Only swap on Mac OS
 '(when (or (featurep 'ns)
            (eq system-type 'darwin))
    ;; Only swap bindings if keys were actually swapped
    (when (and (eq ns-command-modifier 'meta)
               (eq ns-option-modifier 'super))
      ;; Super is the Alt/option key
      (bind-key "s-<left>" 'left-word)
      (bind-key "s-<right>" 'right-word)
      (bind-key "s-<backspace>" 'backward-kill-word)
      (bind-key "s-<kp-delete>" 'kill-word)
      (bind-key "s-`" 'tmm-menubar)
      ;; Meta is the command key
      (bind-key "M-<left>" 'move-beginning-of-line)
      (bind-key "M-<right>" 'move-end-of-line)
      (bind-key "M-<backspace>" 'ignore)
      (bind-key "M-<kp-delete>" 'ignore)
      (bind-key "M-`" 'other-frame)
      (require 'cl)
      ;; Need to fix `org-meta(left|right)' as well. TODO: switch to
      ;; noflet after this is merged:
      (define-advice org-metaleft (:around (orig-fun &rest args) osx-command)
        (flet ((backward-word (&rest args)))
          (defun backward-word (&rest args)
            (call-interactively #'move-beginning-of-line))
          (apply orig-fun args)))
      (define-advice org-metaright (:around (orig-fun &rest args) osx-command)
        (flet ((forward-word (&rest args)))
          (defun forward-word (&rest args)
            (call-interactively #'move-end-of-line))
          (apply orig-fun args))))))

Have indent-region indent containing defun if mark is inactive

(define-advice indent-region
    (:around (orig-fun &rest args) indent-defun)
  "Indent containing defun if mark is not active."
  (if (and transient-mark-mode
           (not mark-active))
        (call-interactively #'indent-region))
    (apply orig-fun args)))

Always indent after newline

(bind-key "RET" #'newline-and-indent)

Turn off electric-indent-mode in markdown buffers

electric-indent-mode has a bad interaction with markdown-mode, so we disable it in markdown buffers only.

(add-hook 'markdown-mode-hook
          (apply-partially #'electric-indent-local-mode 0))

Turn on eldoc mode in elisp modes

(loop for hook in
      '(lisp-interaction-mode-hook emacs-lisp-mode-hook)
      do (add-hook hook #'eldoc-mode))


Tramp remote sudo

This allows TRAMP to use sudo on remote hosts.

(require 'tramp)
(add-to-list 'tramp-default-proxies-alist
             '(nil "\\`root\\'" "/ssh:%h:"))
(add-to-list 'tramp-default-proxies-alist
             (list (regexp-quote (system-name)) nil nil))

Tramp remote backup files

This ensures that backup files for remote files are created on the same host as the file.

(setq tramp-backup-directory-alist backup-directory-alist)

Use conf-mode for .gitignore files

(add-to-list 'auto-mode-alist '("\\.gitignore\\'" . conf-mode))

Macros for running a function without user input

This code builds up the without-user-input macro, which is like progn except that if BODY makes any attempt to read user input, all further execution is canceled and the form returns nil (note that it does not signal an error, it simply returns).

(require 'cl-macs)

(defmacro without-minibuffer (&rest body)
  "Like `progn', but stop and return nil if BODY tries to use the minibuffer.

Also disable dialogs while evaluating BODY forms, since dialogs
are just an alternative to the minibuffer."
  (declare (indent 0))
  `(catch 'tried-to-use-minibuffer
         (lambda (&rest args) (throw 'tried-to-use-minibuffer nil))
       (let ((use-dialog-box))          ; No cheating by using dialogs instead of minibuffer

(defmacro without-functions (flist &rest body)
  "Evaluate BODY, but stop and return nil if BODY calls any of the functions named in FLIST."
  (declare (indent 1))
  (let* (;; Functions are disabled by setting their body to this
         ;; temporarily.
          '((&rest args) (throw 'forbidden-function nil)))
         ;; This will form the first argument to `flet'
          (mapcar (lambda (fname) (cons fname fbody)) flist)))
    `(catch 'forbidden-function
       (cl-flet ,function-redefinitions

(defmacro without-user-input (&rest body)
  "Like `progn', but prevent any user interaction in BODY."
  (declare (indent 0))
  `(without-functions (read-event)

Macro for suppressing messages

(defmacro without-messages (&rest body)
  "Evaluate BODY but ignore all messages.

This temporarily binds the `message' function to `ignore' while
executing BODY."
  (declare (indent 0))
  `(noflet ((message (&rest ignore) nil))

Emacs desktop additions

The following additions ensure that the saved desktop file is always up-to-date.

Add a desktop-save function that gives up if user input is required

When running in hooks, it’s not disastrous if we can’t save the desktop for some reason, and we don’t want to bother the user, so we wrap the normal saving function to force it to do nothing instead of asking for user input.

(defun desktop-autosave-in-desktop-dir ()
  "Like `desktop-save-in-desktop-dir' but aborts if input is required.

If `desktop-save-in-desktop-dir' tries to solicit user input,
this aborts and returns nil instead. Also, it disables all
messages during desktop saving. This is intended for use in place
of `desktop-save-in-desktop-dir' in hooks where you don't want to
bother the user if something weird happens."

Save desktop with every autosave

(add-hook 'auto-save-hook 'desktop-autosave-in-desktop-dir)

Save desktop after opening or closing a file

This will ensure that all open files are saved in the desktop. An idle timer and tripwire variable are used used to avoid saving the desktop multiple times when multiple files are opened or closed in rapid succession.

(defvar desktop-mode-desktop-is-stale nil
  "This is set to non-nil when a file is opened or closed.")

(defun desktop-mode-set-stale ()
  "If current buffer has a file, set the stale desktop flag."
  (when buffer-file-name
    (setq desktop-mode-desktop-is-stale t)))
(defun desktop-mode-set-current ()
  "Unconditionally clear the stale desktop flag."
  (setq desktop-mode-desktop-is-stale nil))
(add-hook 'kill-buffer-hook #'desktop-mode-set-stale)
(add-hook 'find-file-hook #'desktop-mode-set-stale)
(add-hook 'desktop-after-read-hook #'desktop-mode-set-current)

(defun desktop-mode-save-if-stale ()
  (when desktop-mode-desktop-is-stale

;; Desktop will be saved 0.1 seconds after any file is opened or
;; closed.
(run-with-idle-timer 0.1 t #'desktop-mode-save-if-stale)

Auto-steal desktop if current owner is dead

The desktop-owner function should only ever return the PID of an Emacs process that’s currently running. This advice replaces the PID of a dead or non-Emacs process with nil, thus allowing the current Emacs to pry the desktop file from the cold dead hands of the previous one without asking permisssion.

(defun pid-command-line (pid)
  "Return the command line for process with the specified PID.

If PID is not a currently running process, returns nil."
      (car (process-lines "ps" "-p" (format "%s" pid) "-o" "args="))))

(define-advice desktop-owner (:filter-return (retval) pry-from-cold-dead-hands)
  "Only return the PID of an Emacs process or nil.

If the return value is not the PID of a currently running Emacs
owned by the current user, it is replaced with nil on the
assumption that the previous owner died an untimely death, so
that the current emacs can cleanly claim its inheritence."
    (let ((owner-cmd (pid-command-line retval)))
      (unless (and owner-cmd
                    (downcase (file-name-base owner-cmd))))
        (setq retval nil))))

Prevent recursive invocations of desktop-save

If desktop-save needs to ask a question and Emacs is idle for a long time (multiple auto-save intervals), it is possible to get multiple nested calls to save the desktop. This is obviously undesirable. The below code turns any recursive call to desktop-save with the same DIRNAME into a no-op.

(defvar desktop-save-recursion-guard-dirname nil)

(define-advice desktop-save (:around (orig-fun dirname &rest args) prevent-recursion)
  "Prevent recursive calls to `desktop-save'.

Recursive calls will only be prevented when they have the same
  (if (string= dirname desktop-save-recursion-guard-dirname)
      (message "Preventing recursive call to `desktop-save' for %S" dirname)
    (let ((desktop-save-recursion-guard-dirname dirname))
      (apply orig-fun dirname args))))

Put enabled/disabled commands in custom.el

By default, enable-command and disable-command append their declarations to user-init-file. But I want them appended to custom.el instead.

(define-advice en/disable-command (:around (orig-fun &rest args) put-in-custom-file)
  "Put declarations in `custom-file'."
  (let ((user-init-file custom-file))
    (apply orig-fun args)))

Fix diff behavior when backup file is not in same directory

My settings put all backup files in one directory. So when diff prmopts for the second file, it starts in that backup directory. I would rather have it start in the same directory as the first file.

(define-advice diff (:before (&rest args) same-dir-for-both-files)
  "Only prompt with backup file in same directory.

When called interactively, `diff' normally offers to compare
against the latest backup file of the selected file. But this
isn't great if that backup file is in a dedicated backup
directory far away from the original directory. So this advice
only allows it to offer backup files from the same directory.

This advice doesn't actually modify the function's behavior in
any way. It simply overrides the interactive form."
   (let* ((newf (if (and buffer-file-name (file-exists-p buffer-file-name))
                     (concat "Diff new file (default "
                             (file-name-nondirectory buffer-file-name) "): ")
                     nil buffer-file-name t)
                  (read-file-name "Diff new file: " nil nil t)))
          (oldf (file-newest-backup newf)))
     (setq oldf (if (and oldf (file-exists-p oldf)
                         (f-same? (f-dirname newf) (f-dirname oldf)))
                     (concat "Diff original file (default "
                             (file-name-nondirectory oldf) "): ")
                     (file-name-directory oldf) oldf t)
                  (read-file-name "Diff original file: "
                                  (file-name-directory newf) nil t)))
     (list oldf newf (diff-switches)))))

Report this as a bug in Emacs

Fix value of x-colors

For some reason the x-colors variable has started to get the wrong value, so I’ve copied the code to set it correctly out of common-win.el.

(setq x-colors
  (if (featurep 'ns) (funcall #'ns-list-colors)
     '("gray100" "grey100" "gray99" "grey99" "gray98" "grey98" "gray97"
       "grey97" "gray96" "grey96" "gray95" "grey95" "gray94" "grey94"
       "gray93" "grey93" "gray92" "grey92" "gray91" "grey91" "gray90"
       "grey90" "gray89" "grey89" "gray88" "grey88" "gray87" "grey87"
       "gray86" "grey86" "gray85" "grey85" "gray84" "grey84" "gray83"
       "grey83" "gray82" "grey82" "gray81" "grey81" "gray80" "grey80"
       "gray79" "grey79" "gray78" "grey78" "gray77" "grey77" "gray76"
       "grey76" "gray75" "grey75" "gray74" "grey74" "gray73" "grey73"
       "gray72" "grey72" "gray71" "grey71" "gray70" "grey70" "gray69"
       "grey69" "gray68" "grey68" "gray67" "grey67" "gray66" "grey66"
       "gray65" "grey65" "gray64" "grey64" "gray63" "grey63" "gray62"
       "grey62" "gray61" "grey61" "gray60" "grey60" "gray59" "grey59"
       "gray58" "grey58" "gray57" "grey57" "gray56" "grey56" "gray55"
       "grey55" "gray54" "grey54" "gray53" "grey53" "gray52" "grey52"
       "gray51" "grey51" "gray50" "grey50" "gray49" "grey49" "gray48"
       "grey48" "gray47" "grey47" "gray46" "grey46" "gray45" "grey45"
       "gray44" "grey44" "gray43" "grey43" "gray42" "grey42" "gray41"
       "grey41" "gray40" "grey40" "gray39" "grey39" "gray38" "grey38"
       "gray37" "grey37" "gray36" "grey36" "gray35" "grey35" "gray34"
       "grey34" "gray33" "grey33" "gray32" "grey32" "gray31" "grey31"
       "gray30" "grey30" "gray29" "grey29" "gray28" "grey28" "gray27"
       "grey27" "gray26" "grey26" "gray25" "grey25" "gray24" "grey24"
       "gray23" "grey23" "gray22" "grey22" "gray21" "grey21" "gray20"
       "grey20" "gray19" "grey19" "gray18" "grey18" "gray17" "grey17"
       "gray16" "grey16" "gray15" "grey15" "gray14" "grey14" "gray13"
       "grey13" "gray12" "grey12" "gray11" "grey11" "gray10" "grey10"
       "gray9" "grey9" "gray8" "grey8" "gray7" "grey7" "gray6" "grey6"
       "gray5" "grey5" "gray4" "grey4" "gray3" "grey3" "gray2" "grey2"
       "gray1" "grey1" "gray0" "grey0"
       "LightPink1" "LightPink2" "LightPink3" "LightPink4"
       "pink1" "pink2" "pink3" "pink4"
       "PaleVioletRed1" "PaleVioletRed2" "PaleVioletRed3" "PaleVioletRed4"
       "LavenderBlush1" "LavenderBlush2" "LavenderBlush3" "LavenderBlush4"
       "VioletRed1" "VioletRed2" "VioletRed3" "VioletRed4"
       "HotPink1" "HotPink2" "HotPink3" "HotPink4"
       "DeepPink1" "DeepPink2" "DeepPink3" "DeepPink4"
       "maroon1" "maroon2" "maroon3" "maroon4"
       "orchid1" "orchid2" "orchid3" "orchid4"
       "plum1" "plum2" "plum3" "plum4"
       "thistle1" "thistle2" "thistle3" "thistle4"
       "MediumOrchid1" "MediumOrchid2" "MediumOrchid3" "MediumOrchid4"
       "DarkOrchid1" "DarkOrchid2" "DarkOrchid3" "DarkOrchid4"
       "purple1" "purple2" "purple3" "purple4"
       "MediumPurple1" "MediumPurple2" "MediumPurple3" "MediumPurple4"
       "SlateBlue1" "SlateBlue2" "SlateBlue3" "SlateBlue4"
       "RoyalBlue1" "RoyalBlue2" "RoyalBlue3" "RoyalBlue4"
       "LightSteelBlue1" "LightSteelBlue2" "LightSteelBlue3" "LightSteelBlue4"
       "SlateGray1" "SlateGray2" "SlateGray3" "SlateGray4"
       "DodgerBlue1" "DodgerBlue2" "DodgerBlue3" "DodgerBlue4"
       "SteelBlue1" "SteelBlue2" "SteelBlue3" "SteelBlue4"
       "SkyBlue1" "SkyBlue2" "SkyBlue3" "SkyBlue4"
       "LightSkyBlue1" "LightSkyBlue2" "LightSkyBlue3" "LightSkyBlue4"
       "LightBlue1" "LightBlue2" "LightBlue3" "LightBlue4"
       "CadetBlue1" "CadetBlue2" "CadetBlue3" "CadetBlue4"
       "azure1" "azure2" "azure3" "azure4"
       "LightCyan1" "LightCyan2" "LightCyan3" "LightCyan4"
       "PaleTurquoise1" "PaleTurquoise2" "PaleTurquoise3" "PaleTurquoise4"
       "DarkSlateGray1" "DarkSlateGray2" "DarkSlateGray3" "DarkSlateGray4"
       "aquamarine1" "aquamarine2" "aquamarine3" "aquamarine4"
       "SeaGreen1" "SeaGreen2" "SeaGreen3" "SeaGreen4"
       "honeydew1" "honeydew2" "honeydew3" "honeydew4"
       "DarkSeaGreen1" "DarkSeaGreen2" "DarkSeaGreen3" "DarkSeaGreen4"
       "PaleGreen1" "PaleGreen2" "PaleGreen3" "PaleGreen4"
       "DarkOliveGreen1" "DarkOliveGreen2" "DarkOliveGreen3" "DarkOliveGreen4"
       "OliveDrab1" "OliveDrab2" "OliveDrab3" "OliveDrab4"
       "ivory1" "ivory2" "ivory3" "ivory4"
       "LightYellow1" "LightYellow2" "LightYellow3" "LightYellow4"
       "khaki1" "khaki2" "khaki3" "khaki4"
       "LemonChiffon1" "LemonChiffon2" "LemonChiffon3" "LemonChiffon4"
       "LightGoldenrod1" "LightGoldenrod2" "LightGoldenrod3" "LightGoldenrod4"
       "cornsilk1" "cornsilk2" "cornsilk3" "cornsilk4"
       "goldenrod1" "goldenrod2" "goldenrod3" "goldenrod4"
       "DarkGoldenrod1" "DarkGoldenrod2" "DarkGoldenrod3" "DarkGoldenrod4"
       "wheat1" "wheat2" "wheat3" "wheat4"
       "NavajoWhite1" "NavajoWhite2" "NavajoWhite3" "NavajoWhite4"
       "burlywood1" "burlywood2" "burlywood3" "burlywood4"
       "AntiqueWhite1" "AntiqueWhite2" "AntiqueWhite3" "AntiqueWhite4"
       "bisque1" "bisque2" "bisque3" "bisque4"
       "tan1" "tan2" "tan3" "tan4"
       "PeachPuff1" "PeachPuff2" "PeachPuff3" "PeachPuff4"
       "seashell1" "seashell2" "seashell3" "seashell4"
       "chocolate1" "chocolate2" "chocolate3" "chocolate4"
       "sienna1" "sienna2" "sienna3" "sienna4"
       "LightSalmon1" "LightSalmon2" "LightSalmon3" "LightSalmon4"
       "salmon1" "salmon2" "salmon3" "salmon4"
       "coral1" "coral2" "coral3" "coral4"
       "tomato1" "tomato2" "tomato3" "tomato4"
       "MistyRose1" "MistyRose2" "MistyRose3" "MistyRose4"
       "snow1" "snow2" "snow3" "snow4"
       "RosyBrown1" "RosyBrown2" "RosyBrown3" "RosyBrown4"
       "IndianRed1" "IndianRed2" "IndianRed3" "IndianRed4"
       "firebrick1" "firebrick2" "firebrick3" "firebrick4"
       "brown1" "brown2" "brown3" "brown4"
       "magenta1" "magenta2" "magenta3" "magenta4"
       "blue1" "blue2" "blue3" "blue4"
       "DeepSkyBlue1" "DeepSkyBlue2" "DeepSkyBlue3" "DeepSkyBlue4"
       "turquoise1" "turquoise2" "turquoise3" "turquoise4"
       "cyan1" "cyan2" "cyan3" "cyan4"
       "SpringGreen1" "SpringGreen2" "SpringGreen3" "SpringGreen4"
       "green1" "green2" "green3" "green4"
       "chartreuse1" "chartreuse2" "chartreuse3" "chartreuse4"
       "yellow1" "yellow2" "yellow3" "yellow4"
       "gold1" "gold2" "gold3" "gold4"
       "orange1" "orange2" "orange3" "orange4"
       "DarkOrange1" "DarkOrange2" "DarkOrange3" "DarkOrange4"
       "OrangeRed1" "OrangeRed2" "OrangeRed3" "OrangeRed4"
       "red1" "red2" "red3" "red4"
       "lavender blush" "LavenderBlush" "ghost white" "GhostWhite"
       "lavender" "alice blue" "AliceBlue" "azure" "light cyan"
       "LightCyan" "mint cream" "MintCream" "honeydew" "ivory"
       "light goldenrod yellow" "LightGoldenrodYellow" "light yellow"
       "LightYellow" "beige" "floral white" "FloralWhite" "old lace"
       "OldLace" "blanched almond" "BlanchedAlmond" "moccasin"
       "papaya whip" "PapayaWhip" "bisque" "antique white"
       "AntiqueWhite" "linen" "peach puff" "PeachPuff" "seashell"
       "misty rose" "MistyRose" "snow" "light pink" "LightPink" "pink"
       "hot pink" "HotPink" "deep pink" "DeepPink" "maroon"
       "pale violet red" "PaleVioletRed" "violet red" "VioletRed"
       "medium violet red" "MediumVioletRed" "violet" "plum" "thistle"
       "orchid" "medium orchid" "MediumOrchid" "dark orchid"
       "DarkOrchid" "purple" "blue violet" "BlueViolet" "medium purple"
       "MediumPurple" "light slate blue" "LightSlateBlue"
       "medium slate blue" "MediumSlateBlue" "slate blue" "SlateBlue"
       "dark slate blue" "DarkSlateBlue" "midnight blue" "MidnightBlue"
       "navy" "navy blue" "NavyBlue" "dark blue" "DarkBlue"
       "light steel blue" "LightSteelBlue" "cornflower blue"
       "CornflowerBlue" "dodger blue" "DodgerBlue" "royal blue"
       "RoyalBlue" "light slate gray" "light slate grey"
       "LightSlateGray" "LightSlateGrey" "slate gray" "slate grey"
       "SlateGray" "SlateGrey" "dark slate gray" "dark slate grey"
       "DarkSlateGray" "DarkSlateGrey" "steel blue" "SteelBlue"
       "cadet blue" "CadetBlue" "light sky blue" "LightSkyBlue"
       "sky blue" "SkyBlue" "light blue" "LightBlue" "powder blue"
       "PowderBlue" "pale turquoise" "PaleTurquoise" "turquoise"
       "medium turquoise" "MediumTurquoise" "dark turquoise"
       "DarkTurquoise"  "dark cyan" "DarkCyan" "aquamarine"
       "medium aquamarine" "MediumAquamarine" "light sea green"
       "LightSeaGreen" "medium sea green" "MediumSeaGreen" "sea green"
       "SeaGreen" "dark sea green" "DarkSeaGreen" "pale green"
       "PaleGreen" "lime green" "LimeGreen" "dark green" "DarkGreen"
       "forest green" "ForestGreen" "light green" "LightGreen"
       "green yellow" "GreenYellow" "yellow green" "YellowGreen"
       "olive drab" "OliveDrab" "dark olive green" "DarkOliveGreen"
       "lemon chiffon" "LemonChiffon" "khaki" "dark khaki" "DarkKhaki"
       "cornsilk" "pale goldenrod" "PaleGoldenrod" "light goldenrod"
       "LightGoldenrod" "goldenrod" "dark goldenrod" "DarkGoldenrod"
       "wheat" "navajo white" "NavajoWhite" "tan" "burlywood"
       "sandy brown" "SandyBrown" "peru" "chocolate" "saddle brown"
       "SaddleBrown" "sienna" "rosy brown" "RosyBrown" "dark salmon"
       "DarkSalmon" "coral" "tomato" "light salmon" "LightSalmon"
       "salmon" "light coral" "LightCoral" "indian red" "IndianRed"
       "firebrick" "brown" "dark red" "DarkRed" "magenta"
       "dark magenta" "DarkMagenta" "dark violet" "DarkViolet"
       "medium blue" "MediumBlue" "blue" "deep sky blue" "DeepSkyBlue"
       "cyan" "medium spring green" "MediumSpringGreen" "spring green"
       "SpringGreen" "green" "lawn green" "LawnGreen" "chartreuse"
       "yellow" "gold" "orange" "dark orange" "DarkOrange" "orange red"
       "OrangeRed" "red" "white" "white smoke" "WhiteSmoke" "gainsboro"
       "light gray" "light grey" "LightGray" "LightGrey" "gray" "grey"
       "dark gray" "dark grey" "DarkGray" "DarkGrey" "dim gray"
       "dim grey" "DimGray" "DimGrey" "black"))))

Associate “*.latex” with latex-mode

By default “.ltx” is assoiated with LaTeX files, but not “.latex”.

(add-to-list 'auto-mode-alist '("\\.latex\\'" . latex-mode))

Use conf-mode for git config files

(add-to-list 'auto-mode-alist
             '("\\.gitconfig\\'" . conf-mode))
(add-to-list 'auto-mode-alist
             (cons (concat (regexp-quote (f-join ".git" "config")) "\\'")

Fix report-emacs-bug-insert-to-mailer

For some unknown reason, on my system xdg-email does nothing (but still exits successfully) when started through start-process. So we use call-process instead.

(define-advice report-emacs-bug-insert-to-mailer
    (:around (orig-fun &rest args) use-call-process)
  "Use `call-process' instead of `start-process'.

For some reason \"xdg-email\" doesn't work from `start-process',
so we use `call-process' instead. This is fine because both the
OS X \"open\" and unix \"xdg-email\" commands exit
  (noflet ((start-process (name buffer program &rest program-args)
                          (apply #'call-process program nil buffer nil program-args)))
    (apply orig-fun args)))

Define functions for initiating external mailer composition

Function to send en email to external mailer

(defun insert-to-mailer (&optional arg-ignored)
  "Send the message to your preferred mail client.
This requires either the macOS \"open\" command, or the freedesktop
\"xdg-email\" command to be available.

This function accepts a prefix argument for consistency with
`message-send', but the prefix argument has no effect."
    ;; FIXME? use mail-fetch-field?
    (let* ((to (progn
                 (goto-char (point-min))
                 (and (looking-at "^To: \\(.*\\)")
                      (match-string-no-properties 1))))
           (subject (progn
                      (and (looking-at "^Subject: \\(.*\\)")
                           (match-string-no-properties 1))))
           (body (progn
                   (forward-line 2)
                   (buffer-substring-no-properties (point) (point-max)))))
      (if (and to subject body)
          (if (report-emacs-bug-can-use-osx-open)
              (start-process "/usr/bin/open" nil "open"
                             (concat "mailto:" to
                                     "?subject=" (url-hexify-string subject)
                                     "&body=" (url-hexify-string body)))
            (start-process "xdg-email" nil "xdg-email"
                           "--subject" subject
                           "--body" body
                           (concat "mailto:" to)))
        (error "Subject, To or body not found")))))

(defun insert-to-mailer-and-exit (&optional arg)
  "Send message like `insert-to-mailer', then, if no errors, exit from mail buffer.

This function accepts a prefix argument for consistency with
`message-send-and-exit', but the prefix argument has no effect."
  (interactive "P")
  (let ((buf (current-buffer))
        (actions message-exit-actions))
    (when (and (insert-to-mailer arg)
               (buffer-name buf))
      (message-bury buf)
      (if message-kill-buffer-on-exit
          (kill-buffer buf))
      (message-do-actions actions)

Define mail-user-agent for external mailer

(define-mail-user-agent 'external-mailer-user-agent
  (get 'message-user-agent 'composefunc)
  (get 'message-user-agent 'abortfunc)
  (get 'message-user-agent 'hookvar))

Eliminate trailing semicolon in propline variable list

Emacs functions that modify the local variables in the propline also add an extraneous trailing semicolon. This advice deletes it.

(define-advice modify-file-local-variable-prop-line
    (:around (orig-fun &rest args) cleanup-semicolon)
  "Delete the trailing semicolon."
    (apply orig-fun args)
      (goto-char (point-min))
      (let ((replace-lax-whitespace t))
        (replace-string "; -*-" " -*-" nil
                        (point) (progn (end-of-line) (point)))))))

Associate .zsh files with zshell in sh-mode

Emacs sh-mode doesn’t automatically associate *.zsh with zsh. This enables that. It also enables it for a few other zsh-related files.

;; Files ending in .zsh
(add-to-list 'auto-mode-alist '("\\.zsh\\'" . sh-mode))
;; zsh startup files
(add-to-list 'auto-mode-alist '("\\.\\(zshrc\\|zshenv\\|zprofile\\|zlogin\\|zlogout\\)\\>" . sh-mode))
;; Ensure that sh-mode uses zsh as shell for these files
(defun sh-mode-set-zsh-by-file-name ()
  (when (and buffer-file-name
             (string-match-p "\\.zsh\\(rc\\|env\\|\\'\\)" buffer-file-name))
    (sh-set-shell "zsh")))
(add-hook 'sh-mode-hook 'sh-mode-set-zsh-by-file-name)

Add sort-words command

Emacs has a command to sort lines, but not to sort words in a region.

(defun sort-words (reverse beg end)
  "Sort words in region alphabetically, in REVERSE if negative.
Prefixed with negative \\[universal-argument], sorts in reverse.

The variable `sort-fold-case' determines whether alphabetic case
affects the sort order.

See `sort-regexp-fields'."
  (interactive "*P\nr")
  (sort-regexp-fields reverse "\\w+" "\\&" beg end))

Only enable git-gutter in local files

Git-gutter doesn’t play nice with TRAMP remotes

(defun git-gutter-find-file-hook ()
   (if (file-remote-p (buffer-file-name))
(add-hook 'find-file-hook #'git-gutter-find-file-hook)

Make scripts executable on save

If a file begins with a shebang (i.e. “#!”), make it executable after saving it.

(add-hook 'after-save-hook

Make electric-indent-mode and python-mode play nice

(defun python-newline-and-indent ()
  "Custom python indentation function.

  This works like normal, except that if point is in the
  indentation of the current line, the newly created line will
  not be indented any further than the current line. This fixes
  the annoying tendency of python-mode to always indent to the
  maximum possible indentation level on every new line."
  (let* ((starting-column (current-column))
         (starting-indentation (current-indentation))
         (started-in-indentation (<= starting-column starting-indentation)))
    (when (and started-in-indentation
               (> (current-indentation) starting-indentation))
        (delete-region (point) (progn (forward-line 0) (point)))
        (indent-to-column starting-indentation))
(define-key python-mode-map (kbd "RET") #'python-newline-and-indent)
(defun turn-off-electric-indent-local-mode ()
    (electric-indent-local-mode 0))
(add-hook 'python-mode-hook #'turn-off-electric-indent-local-mode)

ESS default directory fix

When an R script is in a directory named “scripts”, suggest the parent directory as the starting directory.

(require 'f)
(defun my-ess-directory-function ()
  (cond (ess-directory)
        ((string= "scripts" (f-filename (f-full default-directory)))
         (f-parent default-directory))
        (t nil)))
(setq ess-directory-function #'my-ess-directory-function)

Call req-package-finish when evaluating req-package forms interactively

When I use “C-x C-e” on a req-package declaration, I usually want the package to be installed immediately without having to call req-package-finish manually. This advice does that.

(define-advice eval-last-sexp (:around (orig-fun &rest args) req-package-eagerly)
  "Call `req-package-finish' afterward if evaluating a `req-package' form."
  (let ((is-req-package
         (eq (car-safe (elisp--preceding-sexp))
    (prog1 (apply orig-fun args)
        (when is-req-package

Enable Fira Code font ligatures

These are disabled for now due to the error documented here:

;; (let ((alist '((33 . ".\\(?:\\(?:==\\|!!\\)\\|[!=]\\)")
;;                (35 . ".\\(?:###\\|##\\|_(\\|[#(?[_{]\\)")
;;                (36 . ".\\(?:>\\)")
;;                (37 . ".\\(?:\\(?:%%\\)\\|%\\)")
;;                (38 . ".\\(?:\\(?:&&\\)\\|&\\)")
;;                (42 . ".\\(?:\\(?:\\*\\*/\\)\\|\\(?:\\*[*/]\\)\\|[*/>]\\)")
;;                (43 . ".\\(?:\\(?:\\+\\+\\)\\|[+>]\\)")
;;                (45 . ".\\(?:\\(?:-[>-]\\|<<\\|>>\\)\\|[<>}~-]\\)")
;;                (46 . ".\\(?:\\(?:\\.[.<]\\)\\|[.=-]\\)")
;;                (47 . ".\\(?:\\(?:\\*\\*\\|//\\|==\\)\\|[*/=>]\\)")
;;                (48 . ".\\(?:x[a-zA-Z]\\)")
;;                (58 . ".\\(?:::\\|[:=]\\)")
;;                (59 . ".\\(?:;;\\|;\\)")
;;                (60 . ".\\(?:\\(?:!--\\)\\|\\(?:~~\\|->\\|\\$>\\|\\*>\\|\\+>\\|--\\|<[<=-]\\|=[<=>]\\||>\\)\\|[*$+~/<=>|-]\\)")
;;                (61 . ".\\(?:\\(?:/=\\|:=\\|<<\\|=[=>]\\|>>\\)\\|[<=>~]\\)")
;;                (62 . ".\\(?:\\(?:=>\\|>[=>-]\\)\\|[=>-]\\)")
;;                (63 . ".\\(?:\\(\\?\\?\\)\\|[:=?]\\)")
;;                (91 . ".\\(?:]\\)")
;;                (92 . ".\\(?:\\(?:\\\\\\\\\\)\\|\\\\\\)")
;;                (94 . ".\\(?:=\\)")
;;                (119 . ".\\(?:ww\\)")
;;                (123 . ".\\(?:-\\)")
;;                (124 . ".\\(?:\\(?:|[=|]\\)\\|[=>|]\\)")
;;                (126 . ".\\(?:~>\\|~~\\|[>=@~-]\\)")
;;                )
;;              ))
;;   (dolist (char-regexp alist)
;;     (set-char-table-range composition-function-table (car char-regexp)
;;                           `([,(cdr char-regexp) 0 font-shape-gstring]))))

Ignore left/right scroll events

If wheel-left and wheel-right are left unbound, Emacs rings the bell when they are used, which can easily happen when scrolling on a touchpad.

(bind-key "<wheel-left>" 'ignore)
(bind-key "<wheel-right>" 'ignore)

Environment-specific settings

This section uses the macros defined in system-specific-settings to set options that should vary depending on which system Emacs is running on.

Set up tool-bars

Normally we want the scroll bar and menu bar disabled for maximum text space. But in Mac OS, disabling them causes various things to break, so we want to enabled them there.

(let ((mode-arg (if-system-type-match 'darwin 1 -1)))
  (menu-bar-mode mode-arg)
  (scroll-bar-mode mode-arg))

Use system trash bin

(when-system-type-match 'darwin
  (defvar trash-command "trash")

  (defun system-move-file-to-trash (filename)
    "Move file to OS X trash.

This assumes that a program called `trash' is in your $PATH and
that this program will, when passed a single file path as an
argument, move that file to the trash."
    (call-process trash-command nil nil nil filename)))

Use GNU df (gdf) on OSX if available

On OSX, the standard df command (BSD version, I think) is insufficient, and we want GNU df instead, which is typically installed as gdf. And we may as well use gdf over df on any other system which provides both as well. This implementation uses /opt/local/bin/gdf preferentially, since that is the version installed by Macports.

(when (executable-find "gdf")
  (setq directory-free-space-program "gdf"))

Start emacs server

This allows emacsclient to connect. We avoid starting the server in batch mode since there is no point in that case.

Errors are ignored in case there are two instances of Emacs running, or in case the current Emacs is already running a server. The first will start the server, and the second will silently fail, since a server is already running.

(unless noninteractive
  (ignore-errors (server-start)))