My Emacs Configuration

Written in org-babel, used in emacs.

This is my emacs configuration, as typed in org-babel. Its a form of literate programming which is really nice in that it basically goads me into writing a better documented emacs config.

Pre-install

  • ttf-anonymous-pro (Anonymous Pro font)
  • xclip
  • autojump
  • silversearcher-ag
  • golang-go
  • texlive
  • texlive-latex-extra
  • git
  • github.com/nsf/gocode
  • golang.org/x/tools/cmd/goimports
  • github.com/rogpeppe/godef
  • github.com/golang/lint

TODOs

  • persistent eshell history (maybe it pulls in normal shell history too)
  • good situation for sshing into other machines (ansi-term?)
  • implement ffap for python.
  • find-grep ignores pyc files & specific directories (build)
  • sql format tool
  • use find-java-imports to lookup require statements from our code.
  • Support for installing necessary system dependencies (such as markdown, etc)

Setup package.el

package.el is a package manager for emacs modes. It is official in emacs-24, but is back ported to emacs23. The setup for single files is trivial and multi-file simple things is also pretty easy. It gets complicated if you're doing something weird like CEDET. We tie into marmalade (a package repository that doesn't require you to release your plugins under GPL) and melpa which pulls in things from github.

;; package.el
(require 'package)

(add-to-list 'package-archives
             '("marmalade" . "http://marmalade-repo.org/packages/") t)
(add-to-list 'package-archives
             '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives 
             '("org" . "http://orgmode.org/elpa/") t)

(package-initialize)

Get an up-to-date list of packages available to us if a cached copy doesn't exist. Then loop over the list of packages we want around and install them.

(when (not package-archive-contents)
  (package-refresh-contents))

(defvar my-packages '(magit clojure-mode dedicated 
                            ;; elisp-cache ;; seems to be missing from marmalade
                            org paredit protobuf-mode rainbow-delimiters scpaste
                            ;; something in ESK is breaking ido for me
                            ;; starter-kit-lisp starter-kit-js starter-kit-eshell
                            scratch dizzee ctags-update virtualenvwrapper websocket znc
                            pastels-on-dark-theme textmate pony-mode slime flymake-jshint
                            js2-mode
                            json-mode ;; better syntax highlighting / error reporting for json
                            nginx-mode ;; nginx syntax highlighting
                            yaml-mode
                            haskell-mode ;; used for xmonad config
                            zencoding-mode ;; easy html authoring
                            idle-highlight-mode flymake-cursor dired-single
                            ;; flymake-less ; missing from melpa
                            git-link ;; link to line of code.
                            less-css-mode css-eldoc
                            dockerfile-mode ;; syntax highlighting for Dockerfiles
                            web-mode ;; multi-mode [sorta] for html files
                            pastels-on-dark-theme ;; dark and purple
                            material-theme ;; blue-ish
                            ir-black-theme ;; black/purple ish
                            lua-mode ;; for awesome-wm, but generally for lua.
                            textmate pony-mode
                            fill-column-indicator flycheck flymake-jshint
                            auto-complete scss-mode flymake-sass
                            htmlize ;; used for blog publishing
                            markdown-mode ;; markdown syntax highlighting, etc.
                            pymacs ;; python + emacs bridge for ropemacs refactoring
                            dired-details ;; makes dired buffer nice
                            go-mode go-eldoc go-autocomplete ;; golang
                            gotest ;; run tests for a go file.
                            yasnippet ;; programmable tab-completion
                            xclip ;; linux console copy+paste goodness
                            gnuplot ;; used for gnuplot graph exports from org-babel's publish
                            enh-ruby-mode ;; better ruby mode
                            inf-ruby ;; irb in a separate pane
                            rspec-mode ;; ruby testing mode
                            projectile ;; project based nativation
                            flx-ido ;; better ido fuzzy matching
                            ag ;; emacs frontend to ag (replacement for find-grep)
                            )
  "A list of packages to ensure are installed at launch.")

(dolist (p my-packages)
  (when (not (package-installed-p p))
    (package-install p)))

Packages not on ELPA

There aren't many packages that aren't installable via package.el. The most notable one is Martin Blais's beancount script which helps with finances.

(defun load-if-exists (f)
  (if (file-exists-p (expand-file-name f))
      (load-file (expand-file-name f))))

(load-if-exists "~/src/bitbucket.org/blais/beancount/src/elisp/beancount.el")

Generic Emacs Stuff

Emacs has a few things that nearly everyone changes. Minimize the chrome (emacs should be as command-line-like as possible), lose the overly-verbose and annoying prompts, etc. These are all documented inline.

(defalias 'qrr 'query-regexp-replace)
(fset 'yes-or-no-p 'y-or-n-p)  ;; only type `y` instead of `yes`
(setq inhibit-splash-screen t) ;; no splash screen
(setq-default indent-tabs-mode nil)      ;; no tabs!
(setq fill-column 80) ;; M-q should fill at 80 chars, not 75

;; general programming things
(menu-bar-mode -1) ;; minimal chrome
(tool-bar-mode -1) ;; no toolbar
(scroll-bar-mode -1) ;; disable scroll bars
(set-frame-font "Anonymous Pro-9") ;; Mmm. Delicious fonts.
(setq-default truncate-lines 1) ;; no wordwrap

(require 'uniquify)
(setq uniquify-buffer-name-style 'post-forward)  ;; buffernames that are foo<1>, foo<2> are hard to read. This makes them foo|dir  foo|otherdir
(setq desktop-load-locked-desktop "ask") ;; sometimes desktop is locked, ask if we want to load it.
(desktop-save-mode 1) ;; auto-save buffer state on close for a later time.
(setq abbrev-file-name "~/.emacs.d/abbrev_defs") ;; where to save auto-replace maps

;; colorize the output of the compilation mode.
(require 'ansi-color)
(defun colorize-compilation-buffer ()
  (toggle-read-only)
  (ansi-color-apply-on-region (point-min) (point-max))

  ;; mocha seems to output some non-standard control characters that
  ;; aren't recognized by ansi-color-apply-on-region, so we'll
  ;; manually convert these into the newlines they should be.
  (goto-char (point-min))
  (while (re-search-forward "\\[2K\\[0G" nil t)
    (progn
      (replace-match "
")))
  (toggle-read-only))
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)

Below are a few kill-ring related changes for nice pasting. Pulled from this thread on pasting errors on linux.

(setq kill-ring-max 100) 
(setq x-select-enable-clipboard t) 
(setq select-active-regions t) 
(setq save-interprogram-paste-before-kill 1) 
(setq yank-pop-change-selection t)

I like to use google-chrome, because it has a fast javascript engine and my settings sync across different computers. This ensures when I open links with M-x browse-url that it opens in chrome.

(setq
 browse-url-browser-function 'browse-url-generic
 browse-url-generic-program "google-chrome")

Some variables are file-specific and are denoted in a header function. I whitelist those in particular so that loading them in daemon mode doesn't prompt me. The mechanism here is to provide a callable that checks the value of that variable.

(put 'encoding 'safe-local-variable (lambda (val) #'stringp))
(put 'org-src-preserve-indentation 'safe-local-variable (lambda (val) #'booleanp))

Lisp

Paredit is a really neat lisp editing mode. One big thing it does for you is keep your parens balanced at all times, which turns out to be pretty important. It has a lot of mini-refactoring commands built-in (pull up, add parameter, etc) but I always forget them because I don't write lisp enough to commit it to memory.

eldoc mode is will update your minibuffer to show the parameters the function under your cursor takes, which can be a helpful for jogging your memory.

(eval-after-load 'paredit
  ;; need a binding that works in the terminal
  '(define-key paredit-mode-map (kbd "M-)") 'paredit-forward-slurp-sexp))

(show-paren-mode 1)  ;; highlight matching parenthasis
(add-hook 'emacs-lisp-mode-hook 'paredit-mode)

;; nifty documentation at point for lisp files
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'lisp-interaction-mode-hook 'turn-on-eldoc-mode)
(add-hook 'ielm-mode-hook 'turn-on-eldoc-mode)

Slime is the big part of a lisp IDE. Its a process that runs an inferior process (usually a lisp interpreter) in the background and you can send information to it.

(require 'slime)

Python

There's a weird history with python and emacs. The FSF maintains a copy of python-mode which ships with emacs. The Python community maintains a separate version. They have evolved away from each other and each supports different things. I'm currently using the FSF version, but I'm not sold on it quite yet. I've run into a few syntax highlighting bugs where the buffer won't fully fill out. I'm now using python.el, as it works with yasnippet and indentation seems to be a bit better.

  ;; python
  (add-hook 'python-mode-hook (lambda () 
                                ;; This breaks the blog export, as the
                                ;; python snippet doesn't actually have
                                ;; a filename. Need to investigate
                                ;; flycheck for options. We'll just
                                ;; spawn a new emacs without this
                                ;; enabled for now.
                                (setq fill-column 80)
                                (flycheck-mode 1)
                                (flycheck-select-checker 'python-pylint)
                                (fci-mode 1)))
  
  (add-to-list 'auto-mode-alist '("\\.py" . python-mode))

Virtualenv is a tool in the python community which sorts out your Python package dependencies into their own contained enviroments. This is similar to RVM and friends in the ruby community. virtualenvwrapper is a minor-mode which helps you operate within these from within emacs. It is pretty good!

(require 'virtualenvwrapper)
(venv-initialize-interactive-shells) ;; if you want interactive shell support
(venv-initialize-eshell) ;; if you want eshell support
(setq venv-location (expand-file-name "~/.virtualenvs/"))

Pony-mode is a Django helper mode which gives you access to many neat commands like runserver, manage, tests and more from handy keybindings. This is a small patch for the project which will take into account an directory which contains all of your apps and properly filter it out when determining app names.

  (setq pony-app-dir-prefix "apps")
  
  (defun pony-get-app ()
    "Return the name of the current app, or nil if no app
  found. Corrects for excluded prefix."
    (let* ((root (pony-project-root))
       (excluded-prefix (if (not (= (length pony-app-dir-prefix) 0)))
                    (concat root pony-app-dir-prefix "/")
                  root))
           (re (concat "^" (regexp-quote excluded-prefix) "\\([A-Za-z_]+\\)/"))
           (path (or buffer-file-name (expand-file-name default-directory))))
      (when (string-match re path)
        (match-string 1 path)))
  
  (defun pony-time ()
    "Helper function to get an immediate working setup after a reboot."
    (interactive)
    (if virtualenv-workon-session
        (progn
          (pony-runserver)
          (pony-manage-run '("celeryd" "-lINFO" "--traceback" "--autoreload"))
          (pony-shell)
          (sql-mysql))
      (error "setup your virtualenv first")))

I rely on having a few utilities around in python land, so let's track a requirements file.

coverage
rope
ropemacs
-e git+https://github.com/offbyone/Pymacs@fix-python_load_helper-call#egg=Pymacs

Ropemacs is a binding to rope, which supports refactoring of Python code. It turns out that pymacs isn't super well maintained. A patch since 2013 still hasn't landed. We use offbyone's fork instead (which, unfortunately, isn't super well maintained either.). It all works though.

There was an additional step wherein I had to go to the checkout location of Pymacs and type make to have it compile the correct things. Rope also shadows my recompile key (C-c g), so disable that.

  (ignore-errors
    (progn
      (require 'pymacs)
      (setq pymacs-python-command (expand-file-name "~/.virtualenvs/emacs/bin/python"))
      (pymacs-load "ropemacs" "rope-")
      (setq ropemacs-guess-project t)  ; don't prompt for project, try to figure it out.
      (setq ropemacs-enable-shortcuts nil) ; don't shadow C-c g for recompile.
      ;; Help found at http://stackoverflow.com/a/6806217/4972
      (eval-after-load "ropemacs-mode"
        (define-key ropemacs-local-keymap (kbd "C-c g") nil))))

Python-mode doesn't play well with electric-indent-mode. Instead of properly indenting things, it adds an extra level of indentation when you press RET. This is infuriating, so we turn it off.

;;; Indentation for python

;; Ignoring electric indentation
(defun electric-indent-ignore-python (char)
  "Ignore electric indentation for python-mode"
  (if (equal major-mode 'python-mode)
      'no-indent
    nil))
(add-hook 'electric-indent-functions 'electric-indent-ignore-python)

;; Enter key executes newline-and-indent
(defun set-newline-and-indent ()
  "Map the return key with `newline-and-indent'"
  (local-set-key (kbd "RET") 'newline-and-indent))
(add-hook 'python-mode-hook 'set-newline-and-indent)

Interactive Shell prompts

A few configurations and custom defined shell methods for eshell. Eshell is a terminal replacement implemented entirely in elisp. This sounds weird. It is weird. It has the benefit of having elisp as a first class language so you can do things like: cat foo/bar/baz > (switch-to-buffer "*test*") which opens the file contents in a new buffer names *test*.

  (if (file-exists-p "~/.shell/variables")
      ;; TODO: load $PATH from that file.
      ;; TODO: Add $PATH to exec-path
      nil)
  
  ;;; Necessary to make some modes aware of binaries, such as sql-mysql
  (push "/usr/local/bin" exec-path)
  
  (setenv "PATH" (concat (getenv "PATH") ":" "/usr/local/bin"))
  
  ;; if OSX...
  (if (equal window-system 'ns)
      (push "/Applications/Emacs.app/Contents/MacOS/bin" exec-path)) 
  
  (defun if-string-match-then-result (to-match pairs)
    "Takes a string to match and a list of pairs, the first element
  of the pairs is a regexp to test against the string, the second of
  which is a return value if it matches."
    (catch 'break
      (dolist (val pairs)
        (if (string-match-p (car val) to-match)
            (progn
              (throw 'break (cadr val)))))
      (throw 'break nil)))
  
  (setq eshell-history-size nil) ;; sets it to $HISTSIZE
  
  (defun eshell/extract (file)
    (eshell-command-result (concat (if-string-match-then-result
                                    file
                                    '((".*\.tar.bz2" "tar xjf")
                                      (".*\.tar.gz" "tar xzf")
                                      (".*\.bz2" "bunzip2")
                                      (".*\.rar" "unrar x")
                                      (".*\.gz" "gunzip")
                                      (".*\.tar" "tar xf")
                                      (".*\.tbz2" "tar xjf")
                                      (".*\.tgz" "tar xzf")
                                      (".*\.zip" "unzip")
                                      (".*\.jar" "unzip")
                                      (".*\.Z" "uncompress")
                                      (".*" "echo 'Could not extract the requested file:'")))
                         " " file)))
  
  (defun mass-create-eshells (names)
    "Creates several eshells at once with the provided names. Names
  are surrounded in astrisks."
    (dolist (name names)
      (let ((eshell-buffer-name (concat "*" name "*")))
        (eshell))))
  
  (defun eshell/clear ()
    "clear the eshell buffer."
    (interactive)
    (let ((inhibit-read-only t))
      (erase-buffer)))
  
  (defun eshell/mcd (dir)
    "make a directory and cd into it"
    (interactive)
    (eshell/mkdir "-p" dir)
    (eshell/cd dir))
  
  (defun eshell/git-delete-unreachable-remotes ()
    "Delete remote git branches which have been merged into master"
    (interactive)
    (if (not (string-equal "master" (magit-get-current-branch)))
        (message "Not on master. This probably doesn't do what you want."))
    (shell-command "git branch -r --merged | grep -v '/master$' | sed -E 's/origin\\/(.*)/:\\1/' | xargs git push origin"))

Emacs seems to have difficulty getting the proper PATH variable set. I managed to find this snippet at http://emacswiki.org/emacs/EmacsApp which seems to fix things.

  (if (not (getenv "TERM_PROGRAM"))
      (let ((path (shell-command-to-string
                 "source $HOME/.shell/variables && printf %s \"\$PATH\"")))
        (setenv "PATH" path)
        (setq exec-path (split-string path ":"))))

Javascript

Some generic javascript setup. There's a really neat thing called slime-js which I haven't setup yet. It allows you to have a slime process tied to a javascript REPL. The uptick of this is that you can also have that REPL tied to chrome's web inspector so the javascript you evaluate in it are also in the context of the currently opened webpage. I'm not yet sure how this will work in the context of our backbone app which uses closures everywhere, but we'll see.

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


(setq-default js2-basic-offset 2)
(setq js-indent-level 2)
(add-hook 'js2-mode-hook (lambda ()
                           (progn
                             (paredit-mode -1)
                             (flycheck-mode))))
;; (require 'slime-js)


(defun find-imports (ext import-syntax-fn root tag)
  "Searches for occurrences of `tag` in files under `root` with extension `ext`

  Slightly confusing bash command which will search for java
  imports in your `get-java-project-root` directory and present you
  with a list of options sorted in most-used order. It does not
  insert them into the buffer, however.

  import-syntax-fn is a fn, given a tag, which returns an line of import code.

  returns a list of strings indicating used imports, most used first
  "
  

  (let* ((command (concat
                     ;;; find all java files in project root (excluding symlinks)
                   "find -P " root " -name '*." ext "' -type f | "
                     ;;; filter out imports that match tag
                   "xargs grep -h '" (funcall import-syntax-fn tag) "' "
                     ;;; group occurrences, count unique entries, then sort DESC
                   " | sort | uniq -c | sort -nr "
                     ;;; trim whitespace and ditch the count
                   " | sed 's/^\s*//' | cut -f2- -d ' '"))
         (results (shell-command-to-string command)))
    (progn
      (message command)
      (if (not (eq 0 (length results)))
          (split-string
           results
           "\n" t)))))

(defun copy-js-imports ()
  (interactive)
  (kill-new
   (first (find-imports "js" 
                        (lambda (tag) (concat tag " = require")) 
                        (textmate-project-root) (thing-at-point 'word)))))

In order to maintain consistentcy with the company style-guide, we use jshint to highlight weird syntax errors. The jshint-configuration-path variable lives in a directory local variable which points to the project's jshintrc.

  (require 'flymake-jshint)
  (add-hook 'js-mode-hook 'flymake-jshint-load)
  (add-hook 'js-mode-hook 'flymake-mode)

A handy utility that will take a region and format is as JSON with nice indentation.

  (defun pretty-print-json(&optional b e)
    "Shells out to Python to pretty print JSON" 
    (interactive "r")
    (shell-command-on-region b e "python -m json.tool" (current-buffer) t)
  )

CSS & other general bits.

CSS mode is pretty well done. Just change the indentation to 2 spaces rather than 4.

  (setq css-indent-offset 2)

Linting is important in all languages, even CSS.

; (require 'flymake-less)
(require 'css-eldoc)

web-mode is an interesting new mode which bridges the gap with mixed-content template code. You get handy html syntax highlighting and basic controls, while simultaneously getting some help in the template code. This mostly manifests as control structures, pairing of open parens, etc.

(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.hb\\.html\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.jsp\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.hbs\\'" . web-mode))

;; everything is indented 2 spaces
(setq web-mode-markup-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2)

Java

I programmed Java with Emacs at Google on and off for 2 years (swapping between Eclipse on occasion). Thanks to some awesome tools they have internally, it was pretty great. Similar to programming Python in emacs with an up-to-date TAGS file. I don't know that I'd do it outside of Google beyond a super tiny project, but the slowness of the custom eclipse plugin they had was just really difficult for me to cope with.

(add-hook 'java-mode-hook (lambda ()
                            (setq c-basic-offset 2)
                            (setq fill-column 100)
                            (fci-mode t)
                            (subword-mode t)
                            (local-set-key (kbd "C-M-h") 'windmove-left)
                            (hs-minor-mode 1))
          )

SQL

sql-postgres mode must defer passwords to a special, postgres-specific password file because postgres doesn't allow you to specify passwords on the command line (presumably due to security reasons). As such, we pass the -w flag to tell it to look for a password file.

(setq sql-postgres-options '("-P" "pager=off" "-w"))

Miscellaneous stuff

encryption mode

I keep a file around of encrypted passwords that emacs needs to know about (simple stuff like IRC server password). I store that in a gpg encrypted file. Thankfully, emacs has nifty ways of building that stuff in.

  (require 'epa)
  (epa-file-enable)
  (setq epg-gpg-program "gpg")


  (load-if-exists "~/.emacs.d/secrets.el.gpg")
  (load-if-exists "~/.emacs.d/secrets.el")

Flymake

Navigating around flymake bits is a bit of a pain. This makes things a little easier.

(defun abrahms-flymake-show-error (prefix)
  (interactive "p")
  (if prefix
      (flymake-goto-next-error)
    (flymake-goto-prev-error))
  (flymake-display-err-menu-for-current-line))

(global-set-key "\C-c\C-v" 'abrahms-flymake-show-error)

Dedicated Mode

Dedicated mode fixes the issue in which emacs spawns a new window (for tab completion or help, for instance) and it replaces an existing buffer you had open which you wanted to be persistent. If you turn on the dedicated minor-mode, none of those transient buffers will open up over those buffers.

(require 'dedicated) ;; sticky windows

Fill Column Indicator

Fill column indicator will show you the current fill-column as a vertical line in your buffers. This is helpful for making sure your code doesn't go over 80 characters wide for things like python.

(require 'fill-column-indicator) ;; line indicating some edge column

scpaste

SCPaste is sort of like gists, but it uploads the paste to your own server. It was particularly helpful when dealing with things at Google when I couldn't post it publically (or even privately to an external service). One of the neat things it does is it uses your color scheme (if you use a colored emacs) in the paste.

  ;; scpaste
  (setq scpaste-http-destination "http://justin.abrah.ms/pastes"
        scpaste-scp-destination "justin.abrah.ms:/srv/justin.abrah.ms/pastes")

Keybindings

Just a few custom keybindings I have. The big ones here are my window moving commands. The emacs default is C-x o which will progress through the windows in some semi-sane order one at a time. What I find myself actually wanting is something akin to vim movement commands. The unfortunate situation is that the key-bindings I'm using aren't in the space of keybindings reserved for users to override. This has the unfortunate side effect of meaning that I need to override it in a half a dozen different modes. I'm still looking for a better solution. I think it might be to use the super key which is still reserved but less likely to be used.

;; Vim style keyboard moving
(global-set-key (kbd "C-M-l") 'windmove-right)
(global-set-key (kbd "C-M-h") 'windmove-left)
(global-set-key (kbd "C-M-j") 'windmove-down)
(global-set-key (kbd "C-M-k") 'windmove-up)
(global-set-key (kbd "M-o") 'other-window)
(global-set-key (kbd "C-c g") 'recompile)
(global-unset-key (kbd "C-x m")) ; I don't use mail
(global-unset-key (kbd "C-z")) ; suspending frame is useless with emacsclient and/or tmux
(add-hook 'perl-mode-hook (lambda ()
                            (local-set-key (kbd "C-M-h") 'windmove-left)))
(add-hook 'ruby-mode-hook (lambda ()
                            (local-set-key (kbd "C-M-h") 'windmove-left)))
(add-hook 'c-mode-common-hook (lambda ()
                                (local-set-key (kbd "C-M-h") 'windmove-left)))

(global-set-key (kbd "M-j") (lambda ()
                              (interactive)
                              (join-line -1)))

Platform Hacks

Using Emacs from within the terminal in OSX completely breaks copy+paste support. This chunk of code from emacswiki restores it.

  (defun copy-from-osx ()
    (shell-command-to-string "pbpaste"))
  
  (defun paste-to-osx (text &optional push)
    (let ((process-connection-type nil))
      (let ((proc (start-process "pbcopy" "*Messages*" "pbcopy")))
        (process-send-string proc text)
        (process-send-eof proc))))
  (if (eq system-type 'darwin)
      (progn
        (setq interprogram-cut-function 'paste-to-osx)
        (setq interprogram-paste-function 'copy-from-osx)))

Beancounter

I track my finances using Martin Blais's beancount program. It's a little neckbeardy, but I like the flexibility it offers and the option to write small python scripts against my finances. Because it runs on python3, my virtualenv setup isn't quite there to support it. As such, we'll manually set the path to the bean-check file which does the linting of my balance sheets.

(setq beancount-check-program (expand-file-name "~/.virtualenvs3/beancount/bin/bean-check"))

Snippets

It's quite nice to have tab completing expansion similar to textmate snippets. There's a thing that does this in emacs called yasnippets.

(require 'yasnippet)
(yas-global-mode 1)

(add-to-list 'yas-snippet-dirs (expand-file-name "~/src/github.com/dominikh/yasnippet-go/"))

We can set up some snippets that are based on specific modes.

# key: pdb
# name: Debugging Statement
# contributor: Justin Abrahms <justin@abrah.ms>
# --
import pdb; pdb.set_trace()

Magit

From this Stack Overflow question, this snippet of code advises makes it so magit commits always show the verbose commit stuff when you're writing your commit message. This is helpful to me because I often forget what it is that I'm committing and need a simple refresher.

(advice-add #'magit-key-mode-popup-committing :after
            (lambda ()
              (magit-key-mode-toggle-option (quote committing) "--verbose")))

From this blog post (specifically the comments), provide a single command (v) to bring up the PR editing window for a given branch.

(defun jrh-magit-visit-pull-request ()
  "Visit the current branch's PR on GitHub."
  (interactive)
  (let ((remote-branch (magit-get-remote-branch)))
    (cond
     ((null remote-branch)
      (message "No remote branch"))
     (t
      (browse-url
       (format "https://github.com/%s/pull/new/%s"
               (replace-regexp-in-string
                "\\`.+github\\.com:\\(.+\\)\\.git\\'" "\\1"
                (magit-get "remote"
                           (magit-get-remote)
                           "url"))
               (cdr remote-branch)))))))

(eval-after-load 'magit
  '(define-key magit-mode-map "v"
     #'jrh-magit-visit-pull-request))

ido mode

ido mode handles searching open buffers and things like this. Projectile suggests installing flx-ido, which has a slightly different search weighting, so I'm trying that out for a while.

(require 'flx-ido)
(ido-mode t);; fuzzy matching on find-file, buffer switch
(ido-everywhere t)
(add-to-list 'ido-ignore-files "\\.pyc")
(flx-ido-mode 1)
(setq ido-enable-flex-matching t)

gc stops

According to flx (the matcher for flx-ido), GC triggers every 0.76MB by default. Given we're on a modern machine, we can up it to a still-conservative 100MB.

(setq gc-cons-threshold 100000000)

Projectile

Projectile lets you move around a project, wherein project is defined as something like "git repository".

(projectile-global-mode)
(setq projectile-enable-caching t)

ag

Ag is a tool which makes searching easier and faster. It ignores things like .git directories and obeys your .gitignore. It's like ack, but 10x faster.

Because old habits die hard, alias find-grep to ag.

(defalias 'find-grep 'ag)

golang

Go is nice enough to ship with a formatter. The least we could do is run it. Furthermore, there are helpful plugins around showing the method parameters via eldoc. This requires us to set up our exec-path to point to the gocode binary, which can be found here. Following along with gocode, that library provides an autocomplete setup, which I'd like to use.

;; based on the assumption that the following repos are installed and
;; available on exec-path
;; 
;; - github.com/nsf/gocode
;; - golang.org/x/tools/cmd/goimports
;; - github.com/rogpeppe/godef
;; - github.com/golang/lint

(add-hook 'before-save-hook #'gofmt-before-save)
(require 'go-eldoc)
(add-hook 'go-mode-hook 'go-eldoc-setup)

;; setting up autocomplete should happen after yasnippet so we don't duplciate tab bindings.
(require 'auto-complete-config)
(require 'go-autocomplete)
(setq gofmt-command "goimports")

There's also a nice linter which we should probably use too.

  (add-to-list 'load-path (expand-file-name "~/src/github.com/golang/lint/misc/emacs"))
  (require 'golint)

Jumping around in source code is quite helpful, but let's shadow normal ctags support.

(defun ja-gomode-hook ()
  (if not (string-match "go" compile-command)
    (set (make-local-variable 'compile-command)
         "go generate && go build -v && go test -v --coverprofile=cover.out && go vet"))
  (flycheck-mode)
  (local-set-key (kbd "M-.") 'godef-jump))
(add-hook 'go-mode-hook 'ja-gomode-hook)

Emacs Built-ins

tramp

Tramp is one of those features that you don't really make use of in the beginning, but as you get more familiar with it, the more indespensible it is. Tramp allows you to edit files on remote servers as if they were on your local machine. From the find-file prompt, you can type things like: /ssh:user@host:/home/user/myfile.txt which will ssh in to host as user and open up myfile.txt in emacs. When you save, changes are pushed back to the remote host. You can also edit files as root (I do it via sudo) like /sudo:host:/etc/nginx/nginx.conf

If I access something via root@host, actually ssh into the service using my default username (which is the username of my current system user) and sudo to root. I disable root access on my servers (Ubuntu default) which stops a reasonable number of attacks.

(require 'tramp) 

; if I use tramp to access /ssh:root@..., then actually ssh into it
;; and sudo, not login as root.
(set-default 'tramp-default-proxies-alist (quote ((".*" "\\`root\\'" "/sudo:%h:"))))

server-mode

Emacs has this really interesting feature called server-mode. Emacs is notoriously slow to start (this happens if you have a giant emacs config that does stupid things). To combat this, you can start a single server process which will accept multiple clients. The server maintains the state of everything (files open, variables defined, processes running) and your client can attach / disconnect as necessary. The connecting is super fast (vim speeds).

(if (not server-mode)
    (server-start nil t))

ERC

ERC is an IRC mode for emacs. Its nothing special. ZNC is a plugin which makes it simpler to connect to a ZNC server. ZNC is an IRC bouncer, which is a long-running process which keeps you on IRC. You can join and quit as you like, but you stay online throughout. Very similar to emacs's server-mode. Thanks to @bitprophet for letting me use his ZNC server.

  ;;; erc
;; by default, erc alerts you on any activity. I only want to hear
;; about mentions of nick or keyword
(require 'znc)
(custom-set-variables
 '(znc-servers
   `(("justin.abrah.ms" 5000 t
      ((freenode "justinabrahms" ,znc-password)
       (synirc "justinabrahms/synirc" ,znc-password))))))
(setq erc-current-nick-highlight-type 'all)
(setq erc-keywords '("jlilly"))
(setq erc-track-exclude-types '("JOIN" "PART" "NICK" "MODE" "QUIT"))
(setq erc-track-use-faces t)
(setq erc-track-faces-priority-list
      '(erc-current-nick-face erc-keyword-face))
(setq erc-track-priority-faces-only 'all)

ibuffer

Having lots of buffers is a pretty common occurance in emacs, especially with a long-lived emacs process thanks to server-mode. As I'm writing this, I have 616 buffers open in emacs. Managing all that is difficult without some really helpful tools. ido-mode gets most of the way there as I can fuzzy find buffers based on their filename (and parent directories in the case of duplicates). For other times, I turn to ibuffer which presents a list of buffers. You can group these based on several parameters. I tend to do it based on project path or major mode.

  ;; ibuffer configs
  (setq ibuffer-saved-filter-groups
     '(("default"
        ("sprintly-main" (filename . "/src/sprintly/sprint.ly/snowbird/"))
        ("sprintly-js" (filename . "/src/sprintly/sprint.ly/html/"))
        ("sprintly-misc" (filename . "/src/sprintly/sprint.ly/"))
        ("sprintly-chef" (filename . "/src/sprintly/sprint.ly-chef/"))
        ("holiday-extras" (filename . "/src/holiday_extras/"))
        ("glider" (filename . "/src/glider/"))
        ("gitstreams" (filename . "/src/gitstreams/"))
        ("irc" (mode . erc-mode))
        ("background" (name . "^*.**$")))))
  
  
  (add-hook 'ibuffer-mode-hook ; refresh buffer groups on ibuffer mode.
            (lambda ()
              (ibuffer-switch-to-saved-filter-groups "default")))

Fancy Macros

(fset 'testify
   (lambda (&optional arg) "Converts test words into actual test functions.
  
  Converts something like `has token is 200` into `def
  test_has_token_is_200(self):\n\tpass` so I can easily type out my
  python test methods." (interactive "p") (kmacro-exec-ring-item (quote ([100 101 102 32 67108896 5 134217765 32 return 95 return 33 5 40 115 101 108 102 41 58 return 112 97 115 115 14 1 134217830 134217826] 0 "%d")) arg)))

Org-Mode

When tangling Makefiles, it's important that we preserve indentation of the resulting file, else we'll lose the tabs that make it a valid Makefile. One of these days, I'll bother to go hunting for a better build tool that's installed on every system always.

(setq org-src-preserve-indentation t)

I'm experimenting with org-agenda for keeping an engineering log of my daily progress. Let's tell org-mode where to find the agenda file and set up a keyboard shortcut to invoke the agenda menu.

(setq org-agenda-files '("~/Dropbox/eng-log.org" "~/Dropbox/personal.org"))

(define-key global-map "\C-ca" 'org-agenda)

Undocumented

These are things, for whatever reason, I haven't had a chance to document. Some of it, I forgot why I added it, but assume it was for a reason (I already feel ashamed. Let's not talk about it.) Others are temporary. The rest are so small, I didn't have much to say about them.

(setq path-to-etags "/Applications/Emacs.app/Contents/MacOS/bin/etags")

(ac-config-default)


;;; set the trigger key so that it can work together with yasnippet on tab key,
;;; if the word exists in yasnippet, pressing tab will cause yasnippet to
;;; activate, otherwise, auto-complete will
(ac-set-trigger-key "TAB")
(ac-set-trigger-key "<tab>")

(require 'auto-complete-config)
  
  (defun create-tags (dir-name)
    "Create tags file."
    (interactive "DDirectory: ")
    (shell-command
     (format "find %s -type f | xargs %s -a -o %s/TAGS" dir-name path-to-etags dir-name)))
  
  (setq auto-mode-alist ;; files called .bashrc should be opened in sh-mode
        (append
         '(("\\.bashrc" . sh-mode))
         '(("Vagrantfile" . ruby-mode))
         auto-mode-alist))
  
  ;; tempfiles, stolen from github://defunkt/emacs
  (defvar user-temporary-file-directory
    (concat temporary-file-directory user-login-name "/"))
  (make-directory user-temporary-file-directory t)
  (setq backup-by-copying t
        backup-directory-alist `(("." . ,user-temporary-file-directory))
        auto-save-list-file-prefix (concat user-temporary-file-directory ".auto-saves-")
        auto-save-file-name-transforms `((".*" ,user-temporary-file-directory)))
  
  
  ;;; hooks
  (require 'dired-x)
  (add-hook 'dired-mode-hook (lambda ()
                               (dired-omit-mode 1)))
  
  ;; Fantastic dired thing which will hide most `ls -l` output. 
  ;; ) and ( to toggle it
  (require 'dired-details)
  (dired-details-install)
  
  ;; scala
  (let ((ensime-load-path "~/src/ensime/elisp/")
        (sbt-bin "~/bin/")
        (scala-bin "~/src/scala-2.9.2/bin/"))
    (if (file-exists-p ensime-load-path)
        (progn
          (add-to-list 'load-path ensime-load-path)
          (require 'scala-mode)
          (require 'ensime)
          (push scala-bin exec-path)
          (push sbt-bin exec-path)
          (add-to-list 'auto-mode-alist '("\\.scala$" . scala-mode))
          (add-hook 'scala-mode-hook '(lambda ()
                                        (scala-mode-feature-electric-mode)
                                        ))
  
          
          (add-hook 'scala-mode-hook 'ensime-scala-mode-hook))))
  
  
  ;; org mode
  (setq org-todo-keywords
        '((sequence "TODO" "WAITING" "DONE")))
  
  ;; minibuffer command history
  (setq savehist-additional-variables    ;; also save...
    '(search-ring regexp-search-ring)    ;; ... my search entries
    savehist-file "~/.emacs.d/savehist") ;; keep my home clean
  (savehist-mode t)                      ;; do customization before activate
  
  (defun jump-to-next-char (c &optional count)
    "Jump forward or backward to a specific character.  With a
  count, move that many copies of the character."
    (interactive "cchar: \np")
    (when (string= (string c) (buffer-substring (point) (+ 1 (point))))
      (setq count (+ 1 count)))
    (and
     (search-forward (string c) nil t count)
     (> count 0)
     (backward-char)))
  (global-set-key (kbd "C-:") 'jump-to-next-char)
  
  (setq compilation-scroll-output 'first-error)
  
  ;; turning on autofill everywhere seems to give errors like "error in
  ;; process filter: Wrong type argument: stringp, nil" and other randomness.
  (remove-hook 'text-mode-hook 'turn-on-auto-fill)
  
  (put 'upcase-region 'disabled nil)
  (put 'downcase-region 'disabled nil)
  (put 'set-goal-column 'disabled nil)
  (put 'narrow-to-region 'disabled nil)
  
  (custom-set-variables
   ;; custom-set-variables was added by Custom.
   ;; If you edit it by hand, you could mess it up, so be careful.
   ;; Your init file should contain only one such instance.
   ;; If there is more than one, they won't work right.
   '(custom-safe-themes (quote ("870a63a25a2756074e53e5ee28f3f890332ddc21f9e87d583c5387285e882099" "159bb8f86836ea30261ece64ac695dc490e871d57107016c09f286146f0dae64" "5e1d1564b6a2435a2054aa345e81c89539a72c4cad8536cfe02583e0b7d5e2fa" "211bb9b24001d066a646809727efb9c9a2665c270c753aa125bace5e899cb523" "5727ad01be0a0d371f6e26c72f2ef2bafdc483063de26c88eaceea0674deb3d9" "30fe7e72186c728bd7c3e1b8d67bc10b846119c45a0f35c972ed427c45bacc19" default)))
   '(display-time-mode t)
   '(elisp-cache-byte-compile-files nil)
   '(erc-truncate-mode t)
   '(google-imports-file-for-tag (quote (("ServiceException" . "javax.xml.rpc.ServiceException") ("MalformedURLException" . "java.net.MalformedURLException") ("URL" . "java.net.URL") ("Named" . "com.google.inject.name.Named") ("Inject" . "com.google.inject.Inject") ("FormattingLogger" . "java/com/google/common/logging/FormattingLogger.java"))))
   '(menu-bar-mode nil)
   '(minibuffer-prompt-properties (quote (read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt)))
   '(safe-local-variable-values (quote ((virtualenv-default-directory . "/Users/justinlilly/src/prbot/") (virtualenv-workon . "prbot") (Mode . js))))
   '(tool-bar-mode nil))
  (custom-set-faces
   ;; custom-set-faces was added by Custom.
   ;; If you edit it by hand, you could mess it up, so be careful.
   ;; Your init file should contain only one such instance.
   ;; If there is more than one, they won't work right.
   '(mode-line-inactive ((t (:inherit mode-line :background "color-20" :foreground "white" :box (:line-width -1 :color "grey40") :weight light)))))