Making Emacs Work For Me

Table of Contents

Zeekat Softwareontwikkeling (Nederlands)Articles (English)

Configuring Emacs: A Yak Shaving Diary

This is my ∞th attempt at creating a readable and maintainable Emacs configuration. I'm hopeful that using Org-Babel and a literate programming style will help tame the bit-rot and spaghettification.

How to use my configuration

Obtaining the source

You can find the latest public version of the configuration in the Github repository. The meat of it (and the source of this document) is the emacs.org file.

Installation

This configuration is intended to be installable from scratch. It uses Cask to specify the package dependencies. Pallet is installed to sync actual installed packages with the dependency spec.

Installation procedure

Issues installing

  • Emacs failing to load pallet can indicate that cask is attempting to install packages using a different Emacs version than the one you're starting. Check your $PATH and/or aliases.

Graphics and looks

Color theme

Leuven theme is awesome!

(load-theme 'leuven)

Load dark theme for compilation buffer. Leuven's colors are a little too washed out for errors etc to stand out.

(defun my/compilation-theme-hook ()
  (load-theme-buffer-local 'wombat))

(add-to-list 'compilation-mode-hook
             'my/compilation-theme-hook
             t)

Nyan Cat will show you where you are!

Because, why not?

;     (nyan-mode +1)

Font

I like Inconsolata, but it doesn't support a wide range of Unicode characters, so I fall back on DejaVu Sans for those.

(set-face-attribute 'default nil
                    :family "Inconsolata"
                    :height 140
                    :weight 'normal
                    :width 'normal)

(when (functionp 'set-fontset-font)
  (set-fontset-font "fontset-default"
                    'unicode
                    (font-spec :family "DejaVu Sans Mono"
                               :width 'normal
                               :size 12.4
                               :weight 'normal)))

Reduce clutter

Remove the toolbar. It's ugly and I never use it. Also remove the scroll bars; below, I set up the fringe to show my position in a buffer.

(when (window-system)
  (tool-bar-mode -1)
  (scroll-bar-mode -1))

Fringe decorations

The fringe is the vertical region at the right and left of the buffer. Emacs lets you customize it of course.

Here I set up git diffs and buffer position in the fringe.

(when (window-system)
  (require 'git-gutter-fringe))

(global-git-gutter-mode +1)
(setq-default indicate-buffer-boundaries 'left)
(setq-default indicate-empty-lines +1)

Mode line

The default Mode Line is confusing and boring. Right now I like powerline.

'(powerline-center-theme)

Scrolling behavior

Emacs's default scrolling behavior, like a lot of the default Emacs experience, is pretty idiosyncratic. The following snippet makes for a smoother scrolling behavior when using keyboard navigation.

(setq redisplay-dont-pause t
      scroll-margin 1
      scroll-step 1
      scroll-conservatively 10000
      scroll-preserve-screen-position 1)

This snippet makes mouse wheel and trackpad scrolling bearable. Scroll in 1-line increments the buffer under the mouse.

(setq mouse-wheel-follow-mouse 't)
(setq mouse-wheel-scroll-amount '(1 ((shift) . 1)))

Buffer names

Setup uniquify so that non-unique buffer names get the parent path included to make them unique.

(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)

Window configuration

(require 'sticky-windows)

(global-set-key     [(control x) (?0)]        'sticky-window-delete-window)
(global-set-key     [(control x) (?1)]        'sticky-window-delete-other-windows)
(global-set-key     [(control x) (?9)]        'sticky-window-keep-window-visible)

Start up

Start with the scratch buffer; no start up screen.

(setq inhibit-startup-screen +1)

Formatting and white-space

(setq-default indent-tabs-mode nil)

(defun my/clean-buffer-formatting ()
  "Indent and clean up the buffer"
  (interactive)
  (indent-region (point-min) (point-max))
  (whitespace-cleanup))

(global-set-key "\C-cn" 'my/clean-buffer-formatting)

(defun my/general-formatting-hooks ()
  (setq show-trailing-whitespace 't))

(dolist (mode-hook (my/normal-mode-hooks))
  (add-hook mode-hook 'my/general-formatting-hooks))

Text (non-code) formatting

For writing text, I prefer Emacs to do line wrapping for me. Also, superfluous white-space should be shown.

(defun my/text-formatting-hooks ()
  (my/turn-on 'auto-fill)) ; turn on automatic hard line wraps

(add-hook 'text-mode-hook
          'my/text-formatting-hooks)

Programming

General programming

I want to use pretty-symbols mode for all programming.

(defun my/prog-mode-hooks ()
  (my/turn-on 'pretty-symbols))

(add-hook 'prog-mode-hook
          'my/prog-mode-hooks)

Pair programming

Normally, I think line numbers in code editors just take up space, but they can be useful when pair programming; calling out a line number is probably more efficient than pointing at the screen.

I wrapped this in a global minor mode so turning that stuff on and off is easy.

(define-minor-mode my/pair-programming-mode
  "Toggle visualizations for pair programming.

Interactively with no argument, this command toggles the mode.  A
positive prefix argument enables the mode, any other prefix
argument disables it.  From Lisp, argument omitted or nil enables
the mode, `toggle' toggles the state."
  ;; The initial value.
  nil
  ;; The indicator for the mode line.
  " Pairing"
  ;; The minor mode bindings.
  '()
  :group 'my/pairing
  (linum-mode (if my/pair-programming-mode 1 -1)))

(define-global-minor-mode my/global-pair-programming-mode
  my/pair-programming-mode
  (lambda () (my/pair-programming-mode 1)))

(global-set-key "\C-c\M-p" 'my/global-pair-programming-mode)

Lisps

For lisp code, I want ParEdit plus general highlighting etc.

(setq my/lisps
      '(emacs-lisp lisp clojure))

(defun my/general-lisp-hooks ()
  (my/turn-on 'paredit
              'rainbow-delimiters
              'highlight-parentheses))

(dolist (mode (mapcar 'my/->mode-hook my/lisps))
  (add-hook mode
            'my/general-lisp-hooks))

Emacs Lisp

(defun my/emacs-lisp-hooks ()
  (my/turn-on 'eldoc-mode))

(add-hook 'emacs-lisp-mode-hook 'my/emacs-lisp-hooks)

Clojure

I'm using CIDER (formerly nrepl.el) for clojure source/repl interaction.

(defun my/cider-mode-hooks ()
  "Clojure specific setup code that should only be run when we
  have a CIDER REPL connection"
  (cider-turn-on-eldoc-mode))

(add-hook 'cider-mode-hook
          'my/cider-mode-hooks)

Also, I want clojure-test-mode available in source buffers.

(defun my/clojure-mode-hooks ()
  (my/turn-on 'clojure-test))

(add-hook 'clojure-mode-hook
          'my/clojure-mode-hooks)

Clojure-test-mode <C-c '> key binding conflicts with org-tangle, so I bind that to another key.

(defun my/fix-org-tangle-clojure-test-mode-conflict ()
  (define-key clojure-test-mode-map "\C-c\"" 'clojure-test-show-result)
  (define-key clojure-test-mode-map "\C-c'" nil))

(eval-after-load "clojure-test-mode"
  '(my/fix-org-tangle-clojure-test-mode-conflict))

I treat the REPL mode specially, since certain hooks that work in clojure-mode won't make sense or break functionality in cider-repl-mode.

(defun my/cider-repl-mode-hooks ()
  (my/turn-on 'paredit
              'rainbow-delimiters
              'highlight-parentheses))

(add-hook 'cider-repl-mode-hook
          'my/cider-repl-mode-hooks)

Ruby

Tell Emacs rake files are Ruby files.

(dolist (exp '("Rakefile\\'" "\\.rake\\'"))
  (add-to-list 'auto-mode-alist
               (cons exp 'ruby-mode)))

Having some kind of navigation support for rails projects can be very handy. I've contributed a few things to Emacs-Rails mode in the past, but for the moment, I'm going to try the slightly more light-weight Rinari mode.

(global-rinari-mode +1)

Perl

I still occasionally need to work on Perl code, so I have a few basic settings to make that reasonably painless.

(fset 'perl-mode 'cperl-mode) ;; force cperl mode

(defun my/cperl-mode-hooks ()
  (my/turn-on flymake-mode))

(add-hook 'cperl-mode-hook 'my/cperl-mode-hooks)

Javascript

I use JS2-mode for javascript source.

(add-to-list 'auto-mode-alist '("\\.js[x]?\\'" . js2-mode))

JSON

(add-to-list 'auto-mode-alist '("\\.json\\'\\|\\.jshintrc\\'" . json-mode))

HTML

(eval-after-load "sgml-mode"
  '(progn
     (require 'tagedit)
     (tagedit-add-paredit-like-keybindings)
     (tagedit-add-experimental-features)
     (add-hook 'html-mode-hook (lambda () (tagedit-mode 1)))))

Compilation mode improvements

ANSI Colors

See http://stackoverflow.com/questions/3072648/cucumbers-ansi-colors-messing-up-emacs-compilation-buffer

(require 'ansi-color)
(defun colorize-compilation-buffer ()
  (toggle-read-only)
  (ansi-color-apply-on-region (point-min) (point-max))
  (toggle-read-only))
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)

Follow output

(setq compilation-scroll-output t)

TODO Auto Complete

Getting auto completion to work right tends to be a messy process of trial and error, though in recent years the situation has improved, with auto-complete mode being more or less the defacto standard.

  • Fuzzy matching isn't working the way I expected, though. Need to work on that.
(require 'fuzzy)
(require 'auto-complete)
(setq ac-auto-show-menu t
      ac-quick-help-delay 0.5
      ac-use-fuzzy t)
(global-auto-complete-mode +1)

Global key bindings

As far as reasonable, I try to keep my custom key bindings within the "official" restraints. Specifically, I want my global key bindings to start with C-c [lower case letter].

(global-set-key "\C-cg" 'magit-status)
(global-set-key "\C-cq" 'delete-indentation)

Global navigation

I like ido and smex for narrowing down files, commands, buffers etc.

Projects

Projectile is useful. Especially, projectile-replace and projectile-find-file.

Projectile commands are bound with the default C-c p prefix. So I can type C-c p C-h to list all of them.

Org Mode

MobileOrg

Use Dropbox for syncing my agenda files with my Android phone. Worry about the security implications later.

(setq org-mobile-directory "~/Dropbox/MobileOrg")

Global keys

Short key bindings for capturing notes/links and switching to agenda.

(global-set-key "\C-cl" 'org-store-link)
(global-set-key "\C-cc" 'org-capture)
(global-set-key "\C-ca" 'org-agenda)
(global-set-key "\C-cb" 'org-iswitchb)

Org-Agenda needs to be loaded before calling org-agenda works.

(require 'org-agenda)

I prefer a forthnight's overview on the agenda.

(setq org-agenda-span 14)

Notes / Tasks / TODOs

Make custom markers for todo items:

TODO
something that needs to be done at some point. If it has a date, it should be done on that day but it may be moved.
PENDING
something that's awaiting feedback from someone else. If it has a date, it needs followup if there hasn't been any feedback at that time.
MEETING
a scheduled meeting and cannot easily be rescheduled.
DONE
done.
CANCELED
can be ignored. May include a note on why it's been cancelled.
(setq org-todo-keywords
      '((sequence "TODO(t)" "PENDING(p)" "MEETING(m)" "|" "DONE(d)" "CANCELED(c)")))

Automatically mark todo items with todo subitems as DONE when all subitems are done.

(defun my-org-autodone (n-done n-not-done)
  "Switch entry to DONE when all subentries are done, to TODO otherwise."
  (let (org-log-done org-log-states)   ; turn off logging
    (org-todo (if (= n-not-done 0) "DONE" "TODO"))))

(add-hook 'org-after-todo-statistics-hook 'my-org-autodone)

I want to file and refile notes to any main header in any file in my org-agenda-files list.

(setq org-refile-targets '((nil :level . 1)
                           (org-agenda-files :level . 1)))

Org-Babel

Fontifying source blocks

Enable yntax highlighting in src blocks.

(setq-default org-src-fontify-natively t)

Use the minted package for syntax highlighting source blocks in LaTeX / PDF exports. Configuration copied from a blog post by Florian Bergmann.

;; Include the latex-exporter
(require 'ox-latex)
;; Add minted to the defaults packages to include when exporting.
(add-to-list 'org-latex-packages-alist '("" "minted"))
;; Tell the latex export to use the minted package for source
;; code coloration.
(setq org-latex-listings 'minted)
;; Let the exporter use the -shell-escape option to let latex
;; execute external programs.
;; This obviously and can be dangerous to activate!

;; I use pdflatex instead of xelatex because that seems to work
;; much better with utf-8 files
(setq org-latex-pdf-process
      '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

Untangle files.

(global-set-key "\C-cu" 'my/org-babel-untangle)

(defun my/org-babel-untangle (path)
  (interactive "fFile to include: ")
  (message "Untangling '%s'..." path)
  (save-current-buffer
    (let ((lang (save-current-buffer
                  (set-buffer (find-file-noselect path))
                  (my/mode->language major-mode))))
      (insert (format "\n** %s\n\n#+BEGIN_SRC %s :tangle %s\n"
                      (capitalize (replace-regexp-in-string "\\[_-\\]" " " (file-name-base path)))
                      lang
                      (file-relative-name path)))
      (forward-char (cadr (insert-file-contents path)))
      (insert "\n#+" "END_SRC\n"))))

(defun my/mode->language (mode)
  "Return the language for the given mode"
  (intern (replace-regexp-in-string "\\-mode$" "" (my/->string mode))))

(defun my/org-babel-untangle-tree (path)
  (interactive "Droot directory to untangle: ")
  (mapc 'my/org-babel-untangle
        (cl-remove-if 'file-directory-p
                      (f-files path (lambda (p) t) t))))

Language evaluation support

Org-Babel needs to be told that evaluation of certain languages is allowed. I collect all languages here, then enable all of them at the end of the section.

(defvar my/org-babel-evaluated-languages
  '(emacs-lisp)
  "List of languages that may be evaluated in Org documents")

<<org-config-languages>>

(org-babel-do-load-languages
 'org-babel-load-languages
 (mapcar (lambda (lang)
           (cons lang t))
         my/org-babel-evaluated-languages))

Diagramming

I like Graphviz for generating graphs. It takes a few lines of code to link graphviz's dot mode to org-babel so I can include dot source in org mode and export with nice looking diagrams.

(add-to-list 'org-src-lang-modes (quote ("dot" . graphviz-dot)))

(add-to-list 'my/org-babel-evaluated-languages 'dot)

Ditaa is another nice package for turning ASCII art into PNG/EPS diagrams. Turn that on, too.

(add-to-list 'my/org-babel-evaluated-languages 'ditaa)

PlantUml is built on top of Graphviz.

(add-to-list 'my/org-babel-evaluated-languages 'plantuml)

Other libraries

Loading dired+ improves dired.

(require 'dired+)

Configuration file layout

Here I define the emacs.el file that gets generated by the code in this org file.

;;;; Do not modify this file by hand.  It was automatically generated
;;;; from `emacs.org` in the same directory. See that file for more
;;;; information.
;;;;
;;;; If you cannot find the `emacs.org` file, see the source
;;;; repository at https://github.com/joodie/emacs-literal-config

<<environment>>
<<tools>>
<<customize-config>>
<<look-and-feel>>
<<formatting>>
<<programming-setup>>
<<auto-complete>>
<<global-keys>>
<<global-navigation>>
<<org-config>>
<<libraries>>
<<startup>>

Tools

This section defines some functionality used elsewhere in this configuration.

Hooks and modes

(defun my/->string (str)
  (cond
   ((stringp str) str)
   ((symbolp str) (symbol-name str))))

(defun my/->mode-hook (name)
  "Turn mode name into hook symbol"
  (intern (replace-regexp-in-string "\\(-mode\\)?\\(-hook\\)?$"
                                    "-mode-hook"
                                    (my/->string name))))

(defun my/->mode (name)
  "Turn mode name into mode symbol"
  (intern (replace-regexp-in-string "\\(-mode\\)?$"
                                    "-mode"
                                    (my/->string name))))

(defun my/turn-on (&rest mode-list)
  "Turn on the given (minor) modes."
  (dolist (m mode-list)
    (funcall (my/->mode m) +1)))

(defvar my/normal-base-modes
  (mapcar 'my/->mode '(text prog))
  "The list of modes that are considered base modes for
  programming and text editing. In an ideal world, this should
  just be text-mode and prog-mode, however, some modes that
  should derive from prog-mode derive from fundamental-mode
  instead. They are added here.")

(defun my/normal-mode-hooks ()
  "Returns the mode-hooks for `my/normal-base-modes`"
  (mapcar 'my/->mode-hook my/normal-base-modes))

Environment

OSX doesn't set the environment from the shell init files for graphical applications, but I set PATH and a bunch of other stuff there. The exec-path-from-shell package will take care of that. Thanks to Ting-Yu Lin for pointing it out.

(when (memq window-system '(mac ns))
  (exec-path-from-shell-initialize))

External packages may be dropped in the .emacs.d/ext directory.

(add-to-list 'load-path "~/.emacs.d/ext")

Options set using the customize interface

By default, Emacs saves the options you set via the `customize-*` functions in the user init file, which is "~/.emacs.d/init.el" in this setup. I prefer to have it put that data in a seperate file.

(setq custom-file "~/.emacs.d/custom.el")
(load custom-file)