init.el for Noah Hoffman (deprecated)

This configuration served me well for many years, but I have transitioned to a much simpler setup (https://github.com/nhoffman/emacs-config). I'm leaving this here for (my own) historical interest

In a fit of literate programming yak-shaving, I implemented my Emacs configuration as an org-mode file. I have also tried to provide a complete-ish description of my environment for anyone interested in starting more or less from scratch.

Table of Contents

1 Initial setup

You can install this configuration by either cloning it directly from my repository, or by forking on GitHub and installing yours (obviously you will want to do the latter if you want to retain your own modifications:

cd ~
git clone https://github.com/nhoffman/.emacs.d.git

After cloning the repository into your home directory, just a bit of setup is required before first use.

1.1 set up a shell environment

This configuration provides some commands (tested with bash and zsh) that are useful for using Emacs from the shell. In particular, if you are on a mac and have installed Emacs for OS X or compiled the Cocoa version, Emacs will be installed to /Applications/Emacs.app/Contents/MacOS/Emacs and emacsclient is found in /Applications/Emacs.app/Contents/MacOS/bin/emacsclient. These are aliased to emacs and emacsclient, respectively.

In addition, there are some shell commands that simplify running and using Emacs in server mode:

edaemon
launch the Emacs server daemon, removing any locked desktop files.
ec
attach to the Emacs server in GUI mode (emacsclient -c) in the background.
enw
attach to the Emacs server in terminal mode in place (emacsclient -nw).
e
open a file in an already open window (emacsclient -n).

Rather than copying the functions defined in init.bash elsewhere, I'd recommend sourcing it instead. For example, just place the following in your ~/.bash_login or ~/.zshrc or whatever (depending on your shell):

if [[ -f ~/.emacs.d/init.bash ]]; then
    source ~/.emacs.d/init.bash
fi

Here's what this file looks like

if [[ $(uname) == 'Darwin' ]]; then
    EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
    EMACSCLIENT=/Applications/Emacs.app/Contents/MacOS/bin/emacsclient
    alias emacs="$EMACS"
    alias emacsclient="$EMACSCLIENT"
else
    EMACS=emacs
    EMACSCLIENT=emacsclient
fi

emacs-venv-activate(){
    local venv=~/.emacs.d/emacs-env
    if [[ -f "$venv/bin/activate" && -z "$VIRTUAL_ENV" ]]; then
	echo "using virtualenv in $venv"
	source "$venv/bin/activate"
    else
	echo "no virtualenv"
    fi
}

edaemon(){
    rm -f ~/.emacs.desktop.lock ~/.emacs.d/.emacs.desktop.lock
    (cd ~ && "$EMACS" --daemon)
}

ec(){
    "$EMACSCLIENT" -c "$@" &
}

enw(){
    "$EMACSCLIENT" -nw "$@"
}

e(){
    # open file in an existing server process
    re='^[0-9]+$'
    if [[ $2 =~ $re ]]; then
	"$EMACSCLIENT" -n +$2:0 $1
    else
	"$EMACSCLIENT" -n "$@"
    fi
}

# if [[ $(basename $SHELL) == "zsh" ]]; then
#     compdef ec=ls
#     compdef enw=ls
#     compdef e=ls
# fi

You will have to open a new terminal window for the shell commands above to become available. Once they are, you can launch the graphical version of emacs using:

emacs -c &

or the terminal version using

emacs -nw

A bit of explanation about Emacs server: the above two commands launch Emacs in an entirely new process. Using the Emacs server, you can run an Emacs server instance in the background and then "attach" either a graphical or terminal window as necessary. Working locally, you would do this by first starting the server:

edaemon

And then opening either a graphical or terminal window, for example:

ec

If you are subsequently working at the command line and you want to open somefile in an already-open Emacs window, you can use:

e somefile

(You can of course always open a file from within Emacs using many mechanisms, eg using C-c f).

Emacs server is particularly useful when you are running Emacs remotely on a server and you want to be able to log out and return to your work later: if you quit the terminal process using C-x C-c (M-x save-buffers-kill-terminal), the server continues running in the background. You can kill the server from within Emacs using M-x save-buffers-kill-emacs.

1.2 option as Meta (M-) on a Mac

I use Emacs from a variety of terminal types on my machines running OS X:

  • the Cocoa version when working locally
  • X11 when working remotely over a fast connection
  • a terminal application when working remotely over a slow connection

I have done my best to configure all three to provide an experience that's as comparable as possible. Here are some configuration suggestions to use the option key as Meta (M-), as opposed to Esc.

1.2.1 Cocoa

I just download it from http://emacsformacosx.com/ - as far as I can tell, option is used as Meta by default.

1.2.2 X11

I use XQuartz

Create the file ~/.Xmodmap as follows to use option as Meta in X11 (you'll need to quit X11 for the changes to take effect):

cat > ~/.Xmodmap <<EOF
clear Mod1
clear Mod2
keycode 63 = Mode_switch
keycode 66 = Meta_L
add Mod1 = Meta_L
add Mod2 = Mode_switch
EOF

This post has more information on configuring X11.

1.2.3 Terminal

I prefer iTerm2 over Terminal.app

Head over to Preferences –> Profiles –> Keys and do these things:

  • select "Left/right option key acts as": +Esc (to use option as Meta)
  • + –> Keyboard shortcut "OPT+<left arrow>": Send Escape sequence "b"
  • + –> Keyboard shortcut "OPT+<right arrow>": Send Escape sequence "f"

The last two items cause option plus the right and left arrows to perform the same actions as M-f (forward-word) and M-b (backward-word) in both Emacs and in contexts that support default readline key bindings (which is just about everywhere).

1.2.4 What next?

If you are completely new to Emacs, the very first thing to do is to become acquainted with the built-in help system. You can get to the help menu by typing <f1> or C-h ?.

Next, I'd recommend starting with the built in tutorial by typing C-h t.

1.3 install packages from ELPA

The only required step to use this configuration is to install packages from EPLA, the Emacs Lisp Package Archive. See the "ELPA" section below for a list of packages installed by this configuration (defined in my-package-list). First, launch Emacs; I'd recommend launching without emacs-desktop, for example emacs -nw --no-desktop. Install specified packages with M-x install-packages (see the ELPA section below). At this point it's usually a good idea to quit and relaunch Emacs.

1.4 create virtualenv

The packages used here (particularly elpy) require some python bits. The easiest way to provide them is to install them in a virtualenv. There's a script to do this - just run:

bin/venv.sh

This will create ~/.emacs.d/python2-env and ~/.emacs.d/python3-env. See the Python section below for more about virtualenvs.

1.5 initialize org-export submodule (optional)

If you want to compile init.org to html using the provided build script, you'll need to initialize and update the git submodule containing the org-export project (https://github.com/nhoffman/org-export). This only needs to be done once after checking out this repository:

git submodule update --init

To update the org-export repository, first try

git submodule update

This will update to whatever commit is associated with the project, eg

git submodule status
197f69ae1f421f4b183d66b5a47dc7d3654ed7e2 org-export (heads/master)

If this doesn't do anything, try

(cd org-export && git checkout org-export && git pull origin master)

If there were any changes, you'll need to make a commit in .emacs.d. Ugh, submodules.

2 Using and maintaining this configuration

All changes to the configuration should be made within code blocks in this file. After any changes, this file must be "tangled" to produce init.el. The elisp version of the configuration is committed to the git repository (even though it is a derived file) to make it easier to get started when first cloning the repository onto a new system. An html-exported version of this file is also published to GitHub pages. All of this is automated using scons. The default target is init.el, so after changing this file, you can compile init.el by simply typing

scons

If you'd rather tangle the file interactively, use C-c C-v t (org-babel-tangle).

Additional targets include scons html to compile html/index.html and scons publish to update the gh-pages branch of the repo on GitHub.

To help keep track of functions I've defined, I like to make aliases that prepend the value of `my-alias-prefix'. Here's a function to help with making aliases.

(defvar my-alias-prefix "my/")

(defun make-alias (fun &optional prefix)
  "Create an alias for function `fun' by prepending the value of
  `my-alias-prefix' to the symbol name. Use `prefix' to provide
  an alternative prefix string. Example:

  (defun bar () (message \"I am bar\"))
  (make-alias 'bar \"foo-\")
  (foo-bar) => \"I am bar\""

  (interactive)
  (defalias
    (intern (concat (or prefix my-alias-prefix) (symbol-name fun)))
    fun))

I edit this file so frequently, let's make some functions to find, tangle, and load it.

(defvar my/init-org "~/.emacs.d/init.org" "org-mode version of init file")
(defvar my/init-el "~/.emacs.d/init.el" "tangled version of `my/init-org'")

(defun init-edit ()
  "Edit org-mode version of init file specified by `my/init-org'"
  (interactive)
  (find-file my/init-org))
(make-alias 'init-edit)

(defun init-load ()
  (interactive)
  (load my/init-el))
(make-alias 'init-load)

(defun init-tangle-and-load ()
  "Tangle `my/init-org' and load the result"
  (interactive)
  (init-edit)
  (org-babel-tangle)
  (init-load)
  (switch-to-buffer "*Messages*"))
(make-alias 'init-tangle-and-load)

3 Startup

This will only work with emacs >= 24.x

(unless (>= emacs-major-version 24)
  (error "Emacs version 24 or higher is required"))
(message "loading ~/.emacs.d/init.el")

3.1 Auto-refresh

automatically refresh buffers from disk (default is every 5 sec) see http://www.cs.cmu.edu/cgi-bin/info2www?(emacs)Reverting

(global-auto-revert-mode 1)

3.2 Enable debugging

;; (setq debug-on-error t)
;; (setq debug-on-signal t)

3.3 dir-local variables

I can't explain why, but I started getting errors that a .dir-locals.el file high up in the file system could not be found when opening a new file in emacsclient. This seems to have stopped the error (conveniently, I don't use this feature):

(setq enable-dir-local-variables nil)

4 ELPA

Set up and initialize ELPA package manager.

Some useful ELPA variables and functions:

M-x package-list-packages open list of packages
package-activated-list variable containing list of the names of currently activated packages
package-install install a package
package-installed-p return true if package is installed

4.1 define repositories

Add some extra package repositories. The default value of package-archives is (("gnu" . "http://elpa.gnu.org/packages/"))

(when (>= emacs-major-version 24)
  (require 'package)
  (setq package-archives
        '(("ELPA" . "http://tromey.com/elpa/")
          ("gnu" . "http://elpa.gnu.org/packages/")
          ("melpa" . "http://melpa.org/packages/")
          ("melpa-stable" . "http://stable.melpa.org/packages/")
          ("marmalade" . "http://marmalade-repo.org/packages/")
          ("org" . "http://orgmode.org/elpa/")
          ("elpy" . "http://jorgenschaefer.github.io/packages/")
          ))

  ;; Check if we're on Emacs 24.4 or newer, if so, use the pinned package feature
  (when (boundp 'package-pinned-packages)
    (setq package-pinned-packages
          '((elpy . "elpy")
            (flycheck . "melpa-stable")
            (helm-descbinds . "melpa-stable")
            (helm-swoop . "melpa-stable")
            (highlight-indentation . "elpy") ;; fixes error in elpy 1.6
            (hydra . "gnu")
            (magit . "melpa-stable")
            (markdown-mode . "melpa-stable")
            (org . "org")
            (smart-mode-line . "melpa-stable")
            (swiper . "melpa-stable")
            (web-mode . "melpa")
            (which-key . "melpa-stable")
            )))

  (package-initialize))

Starting in emacs 25.1, repositories can be assigned a priority, which can be used to hide packages in low priority repositories also represented in higher-priority repositories.

(setq package-archive-priorities
      '(("org" . 30)
        ("elpy" . 30)
        ("melpa-stable" . 20)
        ("marmalade" . 10)
        ("gnu" . 10)
        ("melpa" . 5)))
(setq package-menu-hide-low-priority t)

4.2 use-package

This init file is designed to fail gracefully when packages are not yet installed. use-package can automate the installation of missing packages (when :ensure t is specified), or provides a warning that a package is not installed. However, we have a bit of a problem when it comes to use-package itself, which must be installed to avoid failures on startup. Here's the solution that I've arrived at

  • when use-package is not available, give the user the option of installing it
  • if yes, do so
  • otherwise, define a macro that generates warning messages wherever use-package is invoked.
(unless (package-installed-p 'use-package)
  (if (yes-or-no-p "use-package is not installed yet - install it? ")
      (progn
        ;; bootstrap use-package
        (message "** installing use-package")
        (package-refresh-contents)
        (package-install 'use-package))
    (message "** defining fake use-package macro")
    (defmacro use-package (pkg &rest args)
      (warn
       "use-package is not installed - could not activate %s" (symbol-name pkg))
      )))

4.3 define a list of packages

I could not find an obvious way to define a list of packages to automatically install, so here are some functions to do so. Execute M-x install-packages to install any missing packages. Note that when installing org-mode from ELPA for the first time, you must be sure that the builtin version of org-mode has not been loaded since emacs was first started.

(defun package-installed-not-builtin-p (package &optional min-version)
  "Return true if PACKAGE, of MIN-VERSION or newer, is installed
(ignoring built-in versions).  MIN-VERSION should be a version list"

  (unless package--initialized (error "package.el is not yet initialized!"))
(if (< emacs-major-version 4)
    ;; < emacs 24.4
    (let ((pkg-desc (assq package package-alist)))
      (if pkg-desc
          (version-list-<= min-version
                           (package-desc-vers (cdr pkg-desc)))))
  ;; >= emacs 24.4
  (let ((pkg-descs (cdr (assq package package-alist))))
    (and pkg-descs
         (version-list-<= min-version
                          (package-desc-version (car pkg-descs)))))
  ))

(defun package-install-list (pkg-list)
  ;; Install each package in pkg-list if necessary.
  (mapcar
   (lambda (pkg)
     (unless (package-installed-not-builtin-p pkg)
       (package-install pkg)))
   pkg-list)
  (message "done installing packages"))

(defvar my-package-list
  '(auctex
    ;; csv-mode
    discover
    dash-at-point
    edit-server
    elpy
    ess
    expand-region
    flycheck
    gist
    git-timemachine
    groovy-mode
    helm
    helm-descbinds
    helm-swoop
    helm-projectile
    htmlize
    hydra
    jinja2-mode
    magit
    markdown-mode
    ;; moinmoin-mode
    org
    org-re-reveal
    ox-minutes
    polymode
    poly-R
    projectile
    rainbow-delimiters
    smart-mode-line
    swiper
    visual-regexp
    visual-regexp-steroids
    web-mode
    which-key
    yaml-mode
    yasnippet
    yas-jit))

(defun install-packages ()
  ;; Install packages listed in global 'my-package-list'
  (interactive)
  (package-list-packages)
  (package-install-list my-package-list))
(make-alias 'install-packages)

5 hydra

Hydra is "a package for GNU Emacs that can be used to tie related commands into a family of short bindings with a common prefix." I define various hyrdas as entry points to various commands below. For now, I'll just be sure to test if hydra is installed each time I call defhydra. For example:

(if (require 'hydra nil 'noerror)
    (progn (message "** hydra is installed"))
  (message "** hydra is not installed"))

5.1 hydra-toggle-mode

A hydra for toggling modes. Activate via hydra-launcher using C-\ g

(if (require 'hydra nil 'noerror)
    (progn
      (defhydra hydra-toggle-mode (:color blue :columns 4 :post (redraw-display))
        "hydra-toggle-mode"
        ("RET" redraw-display "<quit>")
        ;; ("c" csv-mode "csv-mode")
        ("h" html-mode "html-mode")
        ("j" jinja2-mode "jinja2-mode")
        ("k" markdown-mode "markdown-mode")
        ("l" lineum-mode "lineum-mode")
        ("m" moinmoin-mode "moinmoin-mode")
        ("o" org-mode "org-mode")
        ("p" python-mode "python-mode")
        ("r" R-mode "R-mode")
        ("s" sql-mode "sql-mode")
        ("t" text-mode "text-mode")
        ("v" visual-line-mode "visual-line-mode")
        ("w" web-mode "web-mode")
        ("y" yaml-mode "yaml-mode")
        ))
  (message "** hydra is not installed"))

6 sidestepping customize

Cribbed from a post on oremacs, this macro provides "a setq that is aware of the custom-set property of a variable."

(defmacro csetq (variable value)
  `(funcall (or (get ',variable 'custom-set)
                'set-default)
            ',variable ,value))

7 Navigation

7.1 electric-buffer-list

Replace default list-buffers with electric-buffer-list for buffer selection.

(global-set-key (kbd "C-x C-b") 'electric-buffer-list)

7.2 Switch windows with arrow keys

Note that other-window is bound by default to C-x o

(defun back-window ()
  (interactive)
  (other-window -1))
(global-set-key (kbd "C-<right>") 'other-window)
(global-set-key (kbd "C-<left>") 'back-window)

7.3 helm

Helm is a pretty intense change to the default behavior for executing commands, switching buffers, finding files, etc. It takes some getting used to, but woah.

See http://tuhdo.github.io/helm-intro.html for setup suggestions.

Using the configuration below, some hints:

  • When in the helm-M-x buffer, TAB shows documentation for the selected command.
  • As suggested, I've replaced the default behavior of M-y to use helm's equivalent, which shows a menu of recently copied regions (rather than cycling through entries of the kill ring after a yank).
(condition-case nil
    (progn
      (require 'helm-config)
      (helm-mode 1)
      (global-set-key (kbd "M-x") 'helm-M-x)
      (global-set-key (kbd "C-x C-f") 'helm-find-files)
      (global-set-key (kbd "M-y") 'helm-show-kill-ring)
      (global-set-key (kbd "C-c h o") 'helm-occur)
      (global-set-key (kbd "C-h SPC") 'helm-all-mark-rings)
      (define-key helm-map (kbd "<tab>") 'helm-execute-persistent-action)
      (define-key helm-map (kbd "C-i") 'helm-execute-persistent-action)
      (define-key helm-map (kbd "C-z")  'helm-select-action)
      )
  (error (message "** could not activate helm")))

7.4 helm-descbinds

Describe key bindings for the current modes: see https://github.com/emacs-helm/helm-descbinds

(condition-case nil
    (progn
      (require 'helm-descbinds)
      (global-set-key (kbd "C-h b") 'helm-descbinds))
  (error (message "** could not activate helm-descbinds")))

7.5 hydra for helm

A hydra for activating helm commands that I can't otherwise remember. Activate via hydra-launcher using C-\ h

(if (require 'hydra nil 'noerror)
    (progn
      (defhydra hydra-helm (:color blue :columns 4 :post (redraw-display))
        "hydra-toggle-mode"
        ("RET" redraw-display "<quit>")
        ("b" helm-browse-project "helm-browse-project")
        ("d" helm-descbinds "helm-descbinds")
        ("f" helm-projectile-find-file "helm-projectile-find-file")
        ("g" helm-projectile-grep "helm-projectile-grep")
        ("j" helm-projectile-switch-project "helm-projectile-switch-project")
        ("o" helm-occur "helm-occur")
        ("O" helm-org-in-buffer-headings "helm-org-in-buffer-headings")
        ("p" helm-projectile "helm-projectile")
        ("s" helm-swoop "helm-swoop")
        ))
  (message "** hydra is not installed"))

7.6 projectile and helm-projectile

Project-centric file and directory navigation - see https://github.com/bbatsov/projectile

Installed using ELPA.

Basic key bindings (see the url above for a complete list).

keybinding description
C-c p C-h Help with projectile key bindings
C-c p f Display a list of all files in the project.
C-c p d Display a list of all directories in the project.

projectile is integrated with helm by the package helm-projectile. Usage information is here: http://tuhdo.github.io/helm-projectile.html

We'll configure both together

(if (and (package-installed-p 'projectile) (package-installed-p 'helm-projectile))
    (progn
      (projectile-global-mode)
      (setq projectile-completion-system 'helm)
      (helm-projectile-on)
      (if (executable-find "fd")
          (setq projectile-generic-command "fd . -0"))
      (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map))
  (message "** not using projectile or helm-projectile - one or both not installed"))

7.6.1 ignoring directories using grep functions

Native emacs grep functions (like M-x rgrep) as well as projectile-grep and helm-projectile-grep all ignore directories specified by the variable grep-find-ignored-directories. Let's add some to the defaults.

(when (boundp 'grep-find-ignored-directories)
  (add-to-list 'grep-find-ignored-directories ".eggs")
  (add-to-list 'grep-find-ignored-directories "src"))

Make a function to ignore contents of a project's virtualenv, and advise functions using grep to apply it before execution.

(defun grep-ignore-venv-current-project (&rest args)
  (interactive)
  (let ((venv (find-venv-current-project)))
    (if venv
        (progn
          (setq venv (file-name-nondirectory
                      (replace-regexp-in-string "/$" "" venv)))
          (message "adding '%s' to grep-find-ignored-directories" venv)
          (add-to-list 'grep-find-ignored-directories venv))
      (message "no virtualenv at this location")
      )))

(advice-add 'rgrep :before #'grep-ignore-venv-current-project)
(advice-add 'projectile-grep :before #'grep-ignore-venv-current-project)
(advice-add 'helm-projectile-grep :before #'grep-ignore-venv-current-project)

7.7 uniquify

Distinguish buffer names for identically-named files.

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

7.8 swiper

Swiper is an isearch replacement.

(use-package swiper
             :ensure t
             :bind (("C-s" . swiper)))

8 Marking, cursor movement and appearance

8.1 avy

https://github.com/abo-abo/avy

Jump to the start of a word in the visible text of any window by choosing the character at the start of the word. Provides a replacement for ace-jump-mode.

(use-package avy
             :ensure t
             :bind (("M-'" . avy-goto-word-1)))

8.2 expand-region

Expand region increases the selected region by semantic units. Just keep pressing the key until it selects what you want.

Installed using ELPA.

Expand-region doesn't do what I want inside a quoted string (which is to mark the region within the quotes), so here's a function to do so. It's pretty naive, and doesn't enforce that the quote characters are matching.

(defun my/mark-inside-quotes ()
  "Mark string between two quote charactes (double, single, or grave accent)"
  (interactive)
  (unless (re-search-forward "[\"'`]" nil t)
    (error "No quote character after the cursor"))
  (backward-char 1)
  (set-mark (point))
  (unless (re-search-backward "[\"'`]" nil t)
    (error "No matching quote character before the cursor"))
  (forward-char 1)
  (exchange-point-and-mark))

(defun my/mark-first-quoted-string ()
  (interactive)
  (re-search-forward "[\"'`]" nil t)
  (my/mark-inside-quotes))
(if (require 'hydra nil 'noerror)
    (defhydra hydra-expand-region (global-map "M-=")
      "hydra-expand-region"
      ("=" er/expand-region "er/expand-region")
      ("-" er/contract-region "er/contract-region")
      ("q" my/mark-inside-quotes "my/mark-inside-quotes" :color blue)
      ("Q" my/mark-first-quoted-string "my/mark-first-quoted-string" :color blue))
  (message "** hydra is not installed"))

9 Define a "launcher" keymap with hydra

Use a hydra to define a key map containing a grab-bag of commonly used functions. Much easier than trying to find unused key combinations.

(if (require 'hydra nil 'noerror)
    (progn
      (defhydra hydra-launcher (:color teal :columns 4 :post (redraw-display))
        "hydra-launcher"
        ("C-g" redraw-display "<quit>")
        ("RET" redraw-display "<quit>")
        ("b" copy-buffer-file-name "copy-buffer-file-name")
        ("d" insert-date "insert-date")
        ("D" dash-at-point "dash-at-point")
        ("e" save-buffers-kill-emacs "save-buffers-kill-emacs")
        ("f" fix-frame "fix-frame")
        ("g" hydra-toggle-mode/body "toggle mode")
        ("h" hydra-helm/body "helm commands")
        ("i" init-edit "init-edit")
        ("n" my/find-org-index "my/find-org-index")
        ("N" my/org-index-add-entry "my/org-index-add-entry")
        ("m" magit-status "magit-status")
        ("o" hydra-org-navigation/body "hydra-org-navigation")
        ("O" copy-region-or-line-other-window "copy-region-or-line-other-window")
        ("p" hydra-python/body "python menu")
        ("P" list-processes "list-processes")
        ("s" ssh-refresh "ssh-refresh")
        ("t" org-todo-list "org-todo-list")
        ("T" transpose-buffers "transpose-buffers")
        ("u" untabify "untabify")
        ("w" hydra-web-mode/body "web-mode commands"))
      (global-set-key (kbd "C-\\") 'hydra-launcher/body))
  (message "** hydra is not installed"))

10 which-key

"Emacs package that displays available keybindings in popup"

A popup appears showing completions for an uncompleted prefix after a short delay. Eg, type C-x and pause… a list of available completions appears, each identified by the bound function!

see https://github.com/justbur/emacs-which-key

(use-package which-key
  :ensure t
  :pin melpa-stable  ;; this has no effect but I'll leave it here
                     ;; until the apparent bug in use-package is fixed
                     ;; (in the meantime, the repo is pinned using
                     ;; package-pinned-packages)
  :config (which-key-mode))

11 Function keys.

It's kind of surprising that the function keys aren't either defined or bound to more commonly used functions by default.

key default binding also bound to my binding
f1 view-order-manuals C-h  
f2 2C-command C-x 6 fix-frame
f3 kmacro-start-macro-or-insert-counter    
f4 kmacro-end-or-call-macro    
f5     call-last-kbd-macro
f6     lineum-mode
f7     visual-line-mode
f8     ns-toggle-fullscreen
f9      
f10 menu-bar-open    
f11 (OS X: Show Desktop)    
f12 (OS X: Show Dashboard)    
(global-set-key (kbd "<f6>") 'linum-mode)
(global-set-key (kbd "<f7>") 'visual-line-mode)
(global-set-key (kbd "<f8>") 'flymake-popup-current-error-menu)
(defalias 'dtw 'delete-trailing-whitespace)

12 General appearance

(setq column-number-mode t)
(setq inhibit-splash-screen t)
(setq require-final-newline t)
(setq make-backup-files nil)
(setq initial-scratch-message nil)
(setq suggest-key-bindings 4)
(show-paren-mode 1)

12.1 smart-mode-line

https://github.com/Malabarba/smart-mode-line

(if (require 'smart-mode-line nil 'noerror)
    (progn
      (setq sml/no-confirm-load-theme t)
      (setq sml/theme 'light)
      (setq sml/name-width 30)
      ;; (setq sml/mode-width 'full)
      (setq sml/time-format "%H:%M")
      (sml/setup))
  (message "** smart-mode-line is not installed"))

12.2 title bar

File path in title bar. See http://stackoverflow.com/questions/3669511/the-function-to-show-current-files-full-path-in-mini-buffer

(setq frame-title-format
      (list (format "%s %%S: %%j " (system-name))
            '(buffer-file-name "%f" (dired-directory dired-directory "%b"))))

12.3 Prettier cursor

(set-cursor-color "red")
(blink-cursor-mode 1)

13 Environment

13.1 update load path

Store packages not available via elpa in ~./.emacs.d/elisp

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

13.2 update SSH_AUTH_SOCK

If you 1) forward ssh authentication (ie, ssh -A), 2) have a long-running emacs –daemon and 3) set an expiration on your ssh authentication, then you will lose the ability to perform ssh public key authentication once the authentication expires. So actions like pushing/pulling using magit will fail. This can be addressed by updating the value of the SSH_AUTH_SOCK environment variable. Here's a function to fix this.

After installing El Capitan, I've had to follow the instructions here to make ssh-agent work with the version of openssh installed via Homebrew. Using this scheme, ~/.ssh-auth-sock stores the value of SSH_AUTH_SOCK.

(defun ssh-refresh ()
  "Reset the environment variable SSH_AUTH_SOCK"
  (interactive)
  (let (ssh-auth-sock-old (getenv "SSH_AUTH_SOCK"))
    (setenv "SSH_AUTH_SOCK"
            (car (split-string
                  (shell-command-to-string
                   (if (eq system-type 'darwin)
                       "cat ~/.ssh-auth-sock"
                     ;; "ls -t $(find /tmp/* -user $USER -name Listeners 2> /dev/null)"
                     "ls -t $(find /tmp/ssh-* -user $USER -name 'agent.*' 2> /dev/null)"
                     )))))
    (message
     (format "SSH_AUTH_SOCK %s --> %s"
             ssh-auth-sock-old (getenv "SSH_AUTH_SOCK")))))
(make-alias 'ssh-refresh)

13.3 PATH setup

Add paths to 'exec-path' so that Emacs can find executables not otherwise defined in PATH.

(add-to-list 'exec-path "~/.emacs.d/bin")

Also update the $PATH environment variable inherited by shell commands run from within Emacs.

(defun prepend-path (path)
  "Add `path' to the beginning of $PATH unless already present."
  (interactive)
  (unless (string-match path (getenv "PATH"))
    (setenv "PATH" (concat path ":" (getenv "PATH")))))

(prepend-path "~/.emacs.d/bin")

13.4 exec-path-from-shell

Initialize the PATH environment variable when starting up the Emacs app from the finder. Found this tip here: https://plus.google.com/104330705025733851532/posts/K6YPSVEB9Nx

Commenting out for now, but seems promising….

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

14 Exiting and saving

Require prompt before exit on C-x C-c

(global-set-key [(control x) (control c)]
                (function
                 (lambda () (interactive)
                   (cond ((y-or-n-p "Quit? (save-buffers-kill-terminal) ")
                          (save-buffers-kill-terminal))))))

Delete trailing whitespace before save.

(setq delete-trailing-lines nil)
(add-hook 'before-save-hook 'delete-trailing-whitespace)

15 Platform and display-specific settings

Detect platform and window system and set up fonts and other system-specific settings accordingly. It may be necessary to run M-x fix-frame after opening a new frame attached to a running emacs server process.

(defun set-default-font-verbosely (font-name)
  (interactive)
  (message (format "** setting default font to %s" font-name))
  (condition-case nil
      (set-default-font font-name)
    (error (message (format "** Error: could not set to font %s" font-name)))))

(defun fix-frame (&optional frame)
  "Apply platform-specific settings."
  (interactive)
  (menu-bar-mode -1)    ;; hide menu bar
  (tool-bar-mode -1)    ;; hide tool bar
  (scroll-bar-mode -1)  ;; hide scroll bar
  (cond ((string= "ns" window-system) ;; cocoa
         (progn
           (message (format "** running %s windowing system" window-system))
           ;; key bindings for mac - see
           ;; http://stuff-things.net/2009/01/06/emacs-on-the-mac/
           ;; http://osx.iusethis.com/app/carbonemacspackage
           (set-keyboard-coding-system 'mac-roman)
           (setq mac-option-modifier 'meta)
           (setq mac-command-key-is-meta nil)
           (set-default-font-verbosely "Menlo-14")))
        ((string= "x" window-system)
         (progn
           (message (format "** running %s windowing system" window-system))
           (set-default-font-verbosely "Liberation Mono-10")
           ;; M-w or C-w copies to system clipboard
           ;; see http://www.gnu.org/software/emacs/elisp/html_node/Window-System-Selections.html
           (setq x-select-enable-clipboard t)))
        (t
         (message "** running in terminal mode"))))
(global-set-key (kbd "<f2>") 'fix-frame)
(make-alias 'fix-frame)
(fix-frame)

16 Scrolling

See http://www.emacswiki.org/emacs/SmoothScrolling

(setq mouse-wheel-scroll-amount '(3 ((shift) . 3))) ;; number of lines at a time
(setq mouse-wheel-progressive-speed nil)            ;; don't accelerate scrolling
(setq mouse-wheel-follow-mosue 't)                  ;; scroll window under mouse
(setq scroll-step 1)                                ;; keyboard scroll one line at a time
(setq scroll-conservatively 1)                      ;; scroll by one line to follow cursor off screen
(setq scroll-margin 2)                              ;; Start scrolling when 2 lines from top/bottom

17 Keyboard macros

See http://www.emacswiki.org/emacs/KeyboardMacros note that default bindings for macros are:

C-x ( start defining a keyboard macro
C-x ) stop defining the keyboard macro
C-x e execute the keyboard macro

Some additional keyboard macro bindings.

(global-set-key (kbd "<f5>") 'call-last-kbd-macro)

18 ediff

Always split windows horizontally.

(csetq ediff-split-window-function 'split-window-horizontally)

19 emacs desktop

References:

Save desktop periodically instead of just on exit, but not if emacs is started with --no-desktop. Note that "–no-desktop" is deleted from `command-line-args' when desktop is activated, so we have to check before that.

(defun desktop-save-no-p ()
  "Save desktop without prompting (replaces `desktop-save-in-desktop-dir')"
  (interactive)
  ;; (message (format "Saving desktop in %s" desktop-dirname))
  (desktop-save desktop-dirname))

(if (member "--no-desktop" command-line-args)
    (message "** desktop auto-save is disabled")
  (progn
    (require 'desktop)
    (desktop-save-mode 1)
    (message "** desktop auto-save is enabled")
    (add-hook 'auto-save-hook 'desktop-save-no-p)))

When the server is running, start with the first buffer with a name not starting with a star or space.

(not working yet, alas!)

;; (defun buffer-list-nostar ()
;;     (delq nil (mapcar
;;                (lambda (buf)
;;                  (unless (string-match "^[* ]" (buffer-name buf)) buf))
;;                (buffer-list))))

;; (add-hook 'before-make-frame-hook
;;           (lambda ()
;;             (message "** running 'before-make-frame-hook")
;;             ;; (let ((buf (buffer-file-name (car (buffer-list-nostar)))))
;;             ;;   (print (buffer-list-nostar))
;;             ;;   (when buf
;;             ;;     (setq initial-buffer-choice buf)
;;             ;;     (message "** setting initial buffer to %s" buf)))

;;             (print (buffer-list))
;;             (setq initial-buffer-choice (buffer-file-name (car (delq nil (mapcar
;;                (lambda (buf)
;;                  (unless (string-match "^[* ]" (buffer-name buf)) buf))
;;                (buffer-list))))))
;;             ))

20 Move lines up and down with arrow keys

See http://stackoverflow.com/questions/2423834/move-line-region-up-and-down-in-emacs

Move line up

(defun move-line-up ()
  (interactive)
  (transpose-lines 1)
  (previous-line 2))
(global-set-key (kbd "M-<up>") 'move-line-up)

Move line down.

(defun move-line-down ()
  (interactive)
  (next-line 1)
  (transpose-lines 1)
  (previous-line 1))
(global-set-key (kbd "M-<down>") 'move-line-down)

21 Buffers and windows

21.1 Transpose buffers

(defun transpose-buffers (arg)
  "Transpose the buffers shown in two windows."
  (interactive "p")
  (let ((selector (if (>= arg 0) 'next-window 'previous-window)))
    (while (/= arg 0)
      (let ((this-win (window-buffer))
            (next-win (window-buffer (funcall selector))))
        (set-window-buffer (selected-window) next-win)
        (set-window-buffer (funcall selector) this-win)
        (select-window (funcall selector)))
      ;; (setq arg (if (plusp arg) (1- arg) (1+ arg)))
      (setq arg (if (>= arg 0) (1- arg) (1+ arg)))
      )))
(global-set-key (kbd "C-x 4") 'transpose-buffers)

21.2 Switch buffers between frames

Also from http://www.emacswiki.org/emacs/SwitchingBuffers

(defun switch-buffers-between-frames ()
  "switch-buffers-between-frames switches the buffers between the two last frames"
  (interactive)
  (let ((this-frame-buffer nil)
        (other-frame-buffer nil))
    (setq this-frame-buffer (car (frame-parameter nil 'buffer-list)))
    (other-frame 1)
    (setq other-frame-buffer (car (frame-parameter nil 'buffer-list)))
    (switch-to-buffer this-frame-buffer)
    (other-frame 1)
    (switch-to-buffer other-frame-buffer)))
(global-set-key (kbd "C-x 5") 'switch-buffers-between-frames)

21.3 Toggle frame split

Toggles between a horizontal and vertical split (two frames only).

Copied from http://www.emacswiki.org/emacs/ToggleWindowSplit (submitted by Wilfred).

(defun toggle-frame-split ()
  "If the frame is split vertically, split it horizontally or vice versa.
Assumes that the frame is only split into two."
  (interactive)
  (unless (= (length (window-list)) 2) (error "Can only toggle a frame split in two"))
  (let ((split-vertically-p (window-combined-p)))
    (delete-window) ; closes current window
    (if split-vertically-p
        (split-window-horizontally)
      (split-window-vertically)) ; gives us a split with the other window twice
    (switch-to-buffer nil))) ; restore the original window in this part of the frame

(global-set-key (kbd "C-x 6") 'toggle-frame-split)

21.4 Force horizontal splits

(setq split-height-threshold nil)

22 spelling

Use hunspell or aspell instead of ispell if one is available. For hunspell, use dictionaries included in ~/.emacs.d/dictionaries. If a spell check program is available, enable flyspell-mode.

(defvar enable-flyspell-p)

;; (setq debug-on-error t)
;; (setq debug-on-signal t)

(if (cond
     ;; ((executable-find "hunspell")
     ;;  (setq ispell-local-dictionary "en_US")
     ;;  (setq ispell-local-dictionary-alist
     ;;        '(("en_US"                                  ;; DICTIONARY-name
     ;;           "[[:alpha:]]"                            ;; CASECHARS
     ;;           "[^[:alpha:]]"                           ;; NOT-CASECHARS
     ;;           "[']"                                    ;; OTHERCHARS
     ;;           nil                                      ;; MANY-OTHERCHARS-P
     ;;           ("-d" "~/.emacs.d/dictionaries/en_US")   ;; ISPELL-ARGS
     ;;           ;; ("-d" "en_US")   ;; ISPELL-ARGS
     ;;           nil                                      ;; EXTENDED-CHARACTER-MODE
     ;;           utf-8                                    ;; CHARACTER-SET
     ;;           )))
     ;;  (setenv "DICPATH" "~/.emacs.d/dictionaries")
     ;;  (setq-default ispell-program-name "hunspell")
     ;;  (setq ispell-really-hunspell t))
     ((executable-find "aspell")
      (setq ispell-dictionary "en")
      (setq ispell-program-name "aspell")))
    (progn
      (message "** using %s for flyspell" ispell-program-name)
      (autoload 'flyspell-mode "flyspell" "On-the-fly spelling checker." t)
      (setq flyspell-issue-welcome-flag nil)
      (setq enable-flyspell-p t))
  (setq enable-flyspell-p nil)
  (message "** could not find hunspell or aspell"))

23 pine/alpine

http://snarfed.org/space/emacs font-lock faces for composing email

(add-hook 'find-file-hooks
          '(lambda ()
             (if (equal "pico." (substring (buffer-name (current-buffer)) 0 5))
                 ;; (message "** running hook for pine/alpine")
                 (mail-mode))))

24 yasnippet

Use yasnippet. The after-save-hook causes all snippets to be reloaded after saving a snippet file.

(use-package yasnippet
  :init
  (progn
    (add-hook 'after-save-hook
              (lambda ()
                (when (eql major-mode 'snippet-mode)
                  (yas-reload-all)))))
  :commands (yas-global-mode)
  :mode ("\\.yasnippet" . snippet-mode))

25 LaTeX

Install AuxTeX from ELPA.

26 ESS

Installed using ELPA, but seems to need require to be called explicitly.

(condition-case nil
    (require 'ess-site)
  (error (message "** could not load ESS")))

On systems using "modules", use this function to specify the R interpreter for inferior R processes.

(defun set-inferior-ess-r-program-name ()
     "Set `inferior-ess-r-program-name' as the absolute path to
the R interpreter. On systems using
'modules' (http://modules.sourceforge.net/), load the R module
before defining the path."
     (interactive)
     (setq inferior-ess-r-program-name
           (replace-regexp-in-string
            "\n" ""
            (shell-command-to-string
             "which ml > /dev/null && (ml R; which R) || which R"))))
(make-alias 'set-inferior-ess-r-program-name)

Hooks

(add-hook 'ess-mode-hook
          '(lambda()
             (message "Loading ess-mode hooks")
             ;; leave my underscore key alone!
             (setq ess-S-assign "_")
             ;; (ess-toggle-underscore nil)
             ;; set ESS indentation style
             ;; choose from GNU, BSD, K&R, CLB, and C++
             (ess-set-style 'GNU 'quiet)
             (if enable-flyspell-p (flyspell-mode))
             (set-inferior-ess-r-program-name)))

27 markdown-mode

Installed using ELPA.

Some useful key bindings to keep in mind:

S-TAB visibility cycling for headings and content

use-package fails to catch errors when a package is not installed, so the use-package declaration is only evaluated when markdown-mode is actually installed.

(if (require 'markdown-mode nil 'noerror)
    (use-package markdown-mode
      :commands (markdown-mode gfm-mode)
      :mode (("README\\.md" . gfm-mode)
             ("\\.md" . markdown-mode)
             ("\\.markdown" . markdown-mode))
      :bind (:map markdown-mode-map
                  ;; don't redefine =M-<left>= and =M-<right>= in this mode
                  ("M-<right>" . nil)
                  ("M-<left>" . nil))
      :init  (setq markdown-command "multimarkdown")
      :config (custom-set-faces
               '(markdown-code-face
                 ((t (:inherit fixed-pitch :background "lavender"))))))
  (message "** markdown-mode is not installed"))

28 rmarkdown and polymode

Polymode is what you might expect given the name: it supports multiple major modes in the same buffer depending on context. Install polymode using ELPA, then enable it:

Rmarkdown is a format consisting of markdown containing R code chunks. Using polymode, code chunks are in R-mode, and text is markdown-mode. C-c C-c evaluates a code chunk.

(condition-case nil
    (progn
      (require 'poly-R)
      (require 'poly-markdown)
      (add-to-list 'auto-mode-alist '("\\.Rmd" . poly-markdown+r-mode))
      ;; (add-to-list 'auto-mode-alist '("\\.md" . poly-markdown-mode))
      (define-key polymode-mode-map (kbd "M-n r") 'my/ess-render-rmarkdown))
  (error (message "** could not activate polymode")))

Activating polymode defines M-n as a prefix for polymode key bindings.

Here's a function for rendering the current buffer, copied (and modified a bit) from a comment left by @kwstat on an issue in the polymode github repository.

(defun my/ess-render-rmarkdown ()
  "Compile R markdown (.Rmd). Should work for any output type."
  (interactive)
  ;; Check if attached R-session
  (condition-case nil
      (ess-get-process)
    (error
     (ess-switch-process)))
  (let* ((rmd-buf (current-buffer)))
    (save-excursion
      (let* ((sprocess (ess-get-process ess-current-process-name))
             (sbuffer (process-buffer sprocess))
             (buf-coding (symbol-name buffer-file-coding-system))
             (buffer-file-name-html
              (concat (file-name-sans-extension buffer-file-name) ".html"))
             (R-cmd
              (format
               "library(rmarkdown); rmarkdown::render(\"%s\")" buffer-file-name)))
        (save-buffer)
        (message "Running rmarkdown on %s" buffer-file-name)
        (ess-execute R-cmd 'buffer nil nil)
        (switch-to-buffer rmd-buf)
        (ess-show-buffer (buffer-name sbuffer) nil)))))

29 org-mode

org-mode hooks

(add-hook 'org-mode-hook
          '(lambda ()
             (message "Loading org-mode hooks")
             ;; (font-lock-mode)
             (setq org-confirm-babel-evaluate nil)
             (setq org-src-fontify-natively t)
             (setq org-edit-src-content-indentation 0)
             (define-key org-mode-map (kbd "M-<right>") 'forward-word)
             (define-key org-mode-map (kbd "M-<left>") 'backward-word)
             ;; provides key mapping for the above; replaces default
             ;; key bindings for org-promote/demote-subtree
             (define-key org-mode-map (kbd "M-S-<right>") 'org-do-demote)
             (define-key org-mode-map (kbd "M-S-<left>") 'org-do-promote)
             (define-key org-mode-map (kbd "C-c n")  'hydra-org-navigation/body)
             (visual-line-mode)
             ;; org-babel

             ;; enable a subset of languages for evaluation in code blocks
             (setq my/org-babel-load-languages
                   '((R . t)
                     (latex . t)
                     (python . t)
                     (sql . t)
                     (sqlite . t)
                     (emacs-lisp . t)
                     (dot . t)))

             ;; use "shell" for org-mode versions 9 and above
             (add-to-list 'my/org-babel-load-languages
                          (if (>= (string-to-number (substring (org-version) 0 1)) 9)
                              '(shell . t) '(sh . t)))

             (org-babel-do-load-languages
              'org-babel-load-languages my/org-babel-load-languages)

             (require 'ox-minutes nil t)

             ;; (defun org-with-silent-modifications(&rest args)
             ;;   "Replaces function causing error on org-export"
             ;;   (message "Using fake 'org-with-silent-modifications'"))
             (defadvice org-todo-list (after org-todo-list-bottom ())
               "Move to bottom of page after entering org-todo-list"
               (progn (end-of-buffer) (recenter-top-bottom)))
             (ad-activate 'org-todo-list)
             ))

(setq org-agenda-files (list "~/Dropbox/notes/index.org"))
(push '("\\.org\\'" . org-mode) auto-mode-alist)
(push '("\\.org\\.txt\\'" . org-mode) auto-mode-alist)

Custom key bindings

29.1 navigation

I can't seem to remember the default bindings for navigation in org-mode, so I made this hydra for movement between headings and code blocks.

(if (require 'hydra nil 'noerror)
    (progn
      (defhydra hydra-org-navigation
        (:exit nil :foreign-keys warn :columns 4 :post (redraw-display))
        "hydra-org-navigation"
        ("RET" nil "<quit>")
        ("i" org-previous-item "org-previous-item")
        ("k" org-next-item "org-next-item")
        ("<right>" org-next-block "org-next-block")
        ("<left>" org-previous-block "org-previous-block")
        ("<down>" outline-next-visible-heading "outline-next-visible-heading")
        ("<up>" outline-previous-visible-heading "outline-previous-visible-heading")
        ("S-<down>" org-forward-paragraph "org-forward-paragraph")
        ("S-<up>" org-backward-paragraph "org-backward-paragraph")
        ("s" (org-insert-structure-template "src") "add src block" :color blue)
        ("w" my/org-element-as-docx "my/org-element-as-docx" :color blue)
        ("q" nil "<quit>")))
  (message "** hydra is not installed"))
;; org-mode-map binds "C-c n" in org-mode-map

29.2 org-re-reveal

Fork of org-reveal for generating presentations with reveal.js

Example document header:

#+OPTIONS: num:nil toc:nil
#+REVEAL_TRANS: None
#+REVEAL_THEME: Moon
#+REVEAL_ROOT: ./reveal.js
#+Title: LM510: Informatics Managenent
#+Author: Noah Hoffman
#+Email: ngh2@uw.edu

This assumes that you have cloned https://github.com/hakimel/reveal.js in the same directory as the .org file.

(if (require 'org-re-reveal nil 'noerror)
    (use-package org-re-reveal)
  (message "** org-re-reveal is not installed"))

29.3 org-mode utilities

(defun insert-date ()
  ;; Insert today's timestamp in format "<%Y-%m-%d %a>"
  (interactive)
  (insert (format-time-string "<%Y-%m-%d %a>")))
(make-alias 'insert-date)
(defun org-add-entry (filename time-format)
  ;; Add an entry to an org-file with today's timestamp.
  (interactive "FFile: ")
  (find-file filename)
  (end-of-buffer)
  (delete-blank-lines)
  (insert (format-time-string time-format)))

Add a new entry to main notes file.

(defvar my/org-index "~/Dropbox/notes/index.org")

(defun my/org-index-add-entry ()
  (interactive)
  (org-add-entry my/org-index "\n* <%Y-%m-%d %a> "))

(defun my/find-org-index ()
  (interactive)
  (find-file my/org-index))

Add a new entry to my journal.

(global-set-key
 (kbd "C-x C-j") (lambda () (interactive)
                   (org-add-entry "~/Dropbox/journal/journal.org"
                                  "\n* %A, %B %d, %Y")))

Export the contents of the element at point and convert to .docx using pandoc.

(defun safename (str)
  "Remove non-alphanum characters and downcase"
  (let ((exprs '(("^\\W+" "") ("\\W+$" "") ("\\W+" "-"))))
    (dolist (e exprs)
      (setq str (replace-regexp-in-string (nth 0 e) (nth 1 e) str)))
    (downcase str)))

(defun my/org-element-as-docx ()
  "Export the contents of the element at point to a file and
convert to .docx with pandoc"
  (interactive)
  (let* ((sec (car (cdr (org-element-at-point))))
         (header (plist-get sec ':title))
         (fname (safename header))
         (basedir
          (shell-quote-argument (read-directory-name
           "Output directory: " (expand-file-name "~/Downloads"))))
         (orgfile (make-temp-file fname nil ".org"))
         (docx (concat (file-name-as-directory basedir) fname ".docx")))
    (write-region
     (plist-get sec ':begin) (plist-get sec ':end) orgfile)
    (call-process-shell-command (format "pandoc %s -o %s" orgfile docx))
    (if (y-or-n-p "open file?")
        (shell-command (format "open %s" docx)))
    (message "wrote %s" docx)
    ))

30 chrome "edit with emacs"

'edit-server' is initialized by ELPA, but we need to start the server.

(condition-case nil
    (edit-server-start)
  (error (message "** could not start edit-server (chrome edit with emacs)")))

31 Python

elpy (installed from ELPA above) provides the primary basis for my python programming environment. After a bit of trial and error, the combination of elpy and flycheck (as opposed to flymake, see below) supports fairly seamless switching between python2 and python3 programming environments.

An important note on using virtualenvs: when emacs is first launched, no venv is activated, and elpy uses system defaults for the python interpreter and any supporting python packages that may be installed. This is probably not what you want. Use the functions via the hydra defined below to activate appropriate virtualenv for your project. The table below shows my key bindings:

C-\ p 2 activate-venv-default-py2
C-\ p 3 activate-venv-default-py3
C-\ p v activate-venv-current-project

See below for the definition of these functions.

The default virtualenvs may be created using bin/venv.sh, which looks like this:

#!/bin/bash

# set up virtualenvs for python2 and python3

set -e

VENV2=~/.emacs.d/python2-env
virtualenv $VENV2
$VENV2/bin/pip install -U pip
$VENV2/bin/pip install -U -r requirements.txt

VENV3=~/.emacs.d/python3-env
python3 -m venv $VENV3
$VENV3/bin/pip install -U pip
$VENV3/bin/pip install -U -r requirements.txt

31.1 python-mode hooks

(add-hook 'python-mode-hook
          '(lambda ()
             (setq indent-tabs-mode nil)
             (setq tab-width 4)
             (setq py-indent-offset tab-width)
             (setq py-smart-indentation t)
             (define-key python-mode-map "\C-m" 'newline-and-indent)
             (elpy-mode)))

File name mappings

(push '("SConstruct" . python-mode) auto-mode-alist)
(push '("SConscript" . python-mode) auto-mode-alist)
(push '("*.cgi" . python-mode) auto-mode-alist)

Default 'untabify converts a tab to equivalent number of spaces before deleting a single character.

(setq backward-delete-char-untabify-method "all")

31.2 flycheck

Flycheck implements syntax checking for many languages, but for now, I'm just using it in the context of elpy. It's a tossup whether to configure flycheck separately from elpy, but I did so below.

(if (require 'flycheck nil 'noerror)
    (use-package flycheck
      :init
      (setq flycheck-flake8rc "~/.emacs.d/flake8.conf")
      (setq flycheck-check-syntax-automatically '(mode-enabled save)))
  (message "** flycheck is not installed"))

31.3 elpy

See https://github.com/jorgenschaefer/elpy/wiki/Installation

Configure elpy. Note that I use C- + arrows to move between windows, and M + arrows to move by word. These are in muscle memory at this point, and elpy can't have them (these bindings are removed below).

(if (require 'elpy nil 'noerror)
    (use-package elpy
      :bind (:map elpy-mode-map
                  ("C-<right>" . nil)
                  ("C-<left>" . nil)
                  ("M-<right>" . nil)
                  ("M-<left>" . nil)
                  ("M-<right>" . nil)
                  ("M-C-]" . elpy-nav-move-iblock-right)
                  ("M-C-[" . elpy-nav-move-iblock-left))
      :init
      (when (require 'flycheck nil t)
        (add-hook 'elpy-mode-hook 'flycheck-mode))
      :config
      (setq elpy-modules (delq 'elpy-module-django elpy-modules))
      (setq elpy-modules (delq 'elpy-module-flymake elpy-modules))
      (setq elpy-rpc-backend "jedi")
      (add-to-list 'elpy-project-ignored-directories "src")
      (add-to-list 'elpy-project-ignored-directories "*-env"))
  (message "** elpy is not installed"))

Features such as syntax checking, linting, and code completion rely on external python packages. Here's a script that creates a default virtualenv for each of python2 and python3, and installs all of the external packages that I use there:

cd ~/.emacs.d
bin/venv.sh

On (re)activation of a virtualenv, it seems to be necessary to restart the elpy RPC process and reload elpy-mode to reset syntax checking. Otherwise, elpy does a nice job of switching virtualenvs.

(defvar venv-default-py2 "~/.emacs.d/python2-env")
(defvar venv-default-py3 "~/.emacs.d/python3-env")
(defvar venv-default venv-default-py2)

(defun activate-venv-and-reload (venv)
  (pyvenv-activate venv)
  (elpy-rpc-restart)
  (elpy-mode))

(defun activate-venv-default-py2 ()
  (interactive)
  (setq elpy-rpc-python-command "python2")
  (activate-venv-and-reload venv-default-py2))

(defun activate-venv-default-py3 ()
  (interactive)
  (setq elpy-rpc-python-command "python3")
  (activate-venv-and-reload venv-default-py3))

This function installs python dependencies to the current virtualenv from within Emacs.

(defun elpy-install-requirements ()
  "Install python requirements to the current virtualenv."
  (interactive)
  (unless pyvenv-virtual-env
    (error "Error: no virtualenv is active"))
  (let ((dest "*elpy-install-requirements-output*")
        (install-cmd (format "%s/bin/pip install -U --force '%%s'" pyvenv-virtual-env))
        (deps '("elpy" "jedi" "pyflakes" "pep8" "flake8" "importmagic" "yapf")))
    (generate-new-buffer dest)
    (mapcar
     #'(lambda (pkg)
         (message (format install-cmd pkg))
         (call-process-shell-command (format install-cmd pkg) nil dest)) deps)
    (call-process-shell-command
     (format "%s/bin/pip freeze" pyvenv-virtual-env) nil dest)
    (switch-to-buffer dest))
  (elpy-rpc-restart))
(make-alias 'elpy-install-requirements)

Define some utilities for activating a virtualenv in the current project. Especially during the transition to python3, there may be more than one around, and it's useful to be able to switch between virtualenvs for the same project. Turns out it's really easy to use helm to present a menu of options.

(defun list-venvs (basedir)
  "Return a list of paths to virtualenvs in 'basedir' or nil if
 none can be found"
  (interactive)
  (let ((fstr "find %s -path '*bin/activate' -maxdepth 4")
        (pth (replace-regexp-in-string "/$" "" basedir)))

    (mapcar (lambda (string)
              (replace-regexp-in-string "/bin/activate$" "" string))
            (cl-remove-if
             (lambda (string) (= (length string) 0))
             (split-string (shell-command-to-string (format fstr pth)) "\n")))
    ))

(defun list-venvs-current-project ()
  (if (elpy-project-root)
      (list-venvs elpy-project-root)
    (error "error: there is no project here")))

(defun helm-choose-venv-current-project ()
  (interactive)
  (helm
   :sources (helm-build-sync-source "choose a virtualenv"
              :candidates 'list-venvs-current-project)
   :buffer "*helm choose virtualenv*"))

(defun activate-venv-current-project ()
  "Activate a virtualenv if one can be found in the current
project; otherwise activate the virtualenv defined in
`venv-default'. Also restarts the elpy rpc process."
  (interactive)
  (let ((venv (helm-choose-venv-current-project)))
    (if venv
        (if (y-or-n-p (format "Activate %s?" venv))
            (progn
              (activate-venv-and-reload venv)
              (message "Using %s" pyvenv-virtual-env)))
      (message "could not find a virtualenv here"))))
(make-alias 'activate-venv-current-project)

31.4 autopep8

Apply autopep8 (https://github.com/hhatto/autopep8) to the current buffer. Reference: Mastering Emacs: http://www.masteringemacs.org/articles/2011/10/19/executing-shell-commands-emacs/

(defun p8 ()
  "Apply autopep8 to the current region or buffer"
  (interactive)
  (unless (region-active-p)
    (mark-whole-buffer))
  (shell-command-on-region
   (region-beginning) (region-end)      ;; beginning and end of region or buffer
   "autopep8 -"                         ;; command and parameters
   (current-buffer)                     ;; output buffer
   t                                    ;; replace?
   "*autopep8 errors*"                  ;; name of the error buffer
   t)                                   ;; show error buffer?
  (goto-char (region-end))              ;; ... and delete trailing newlines
  (re-search-backward "\n+" nil t)
  (replace-match "" nil t))

Instead of simply replacing the current buffer, use ediff to compare it to the output of autopep8.

(defun p8-and-ediff ()
  "Compare the current buffer to the output of autopep8 using ediff"
  (interactive)
  (let ((p8-output
         (get-buffer-create (format "* %s autopep8 *" (buffer-name)))))
    (shell-command-on-region
     (point-min) (point-max)    ;; beginning and end of buffer
     "autopep8 -"               ;; command and parameters
     p8-output                  ;; output buffer
     nil                        ;; replace?
     "*autopep8 errors*"        ;; name of the error buffer
     t)                         ;; show error buffer?
    (ediff-buffers (current-buffer) p8-output)))

31.5 a hydra for python-related actions

Let's expose some of the frequently-used actions using a hyrda presented as a submenu of my main hydra (ie, C-\ p).

(if (require 'hydra nil 'noerror)
    (progn
      (defhydra hydra-python (:color blue :columns 4 :post (redraw-display))
        "hydra-python"
        ("RET" redraw-display "<quit>")
        ("2" activate-venv-default-py2 "activate-venv-default-py2")
        ("3" activate-venv-default-py3 "activate-venv-default-py3")
        ("c" flycheck-list-errors "flycheck-list-errors")
        ("e" elpy-config "elpy-config")
        ("f" flycheck-verify-setup "flycheck-verify-setup")
        ("g" elpy-goto-definition-other-window "elpy-goto-definition-other-window")
        ("i" elpy-install-requirements "elpy-install-requirements")
        ("v" activate-venv-current-project "activate-venv-current-project")
        ("y" elpy-yapf-fix-code "elpy-yapf-fix-code")))
  (message "** hydra is not installed"))

32 Javascript/json

Indent json and javascript files using 2 spaces (default is 4).

(add-hook 'js-mode-hook
          (lambda ()
            (make-local-variable 'js-indent-level)
            (setq js-indent-level 2)))

(add-hook 'json-mode-hook
          (lambda ()
            (make-local-variable 'js-indent-level)
            (setq js-indent-level 2)))

33 Groovy mode

Useful for nextflow

(use-package groovy-mode
             :ensure t
             :mode ("\\.nf" . groovy-mode))

34 shell

Recognize zsh and bash scripts by file suffix.

(add-to-list 'auto-mode-alist '("\\.zsh\\'" . sh-mode))
(add-to-list 'auto-mode-alist '("\\.bash\\'" . sh-mode))

35 scons

I should really start using a snippet package, but for now:

(defun scons-insert-command ()
  (interactive)
  (insert "output, = env.Command(
    target=,
    source=,
    action=('')
)"))
(make-alias 'scons-insert-command)

36 text-mode

(add-hook 'text-mode-hook
          '(lambda ()
             ;; (longlines-mode)
             (if enable-flyspell-p (flyspell-mode))))

37 rst-mode

(add-hook 'rst-mode-hook
          '(lambda ()
             (message "Loading rst-mode hooks")
             (if enable-flyspell-p (flyspell-mode))
             (define-key rst-mode-map (kbd "C-c C-a") 'rst-adjust)))

38 moinmoin-mode

A mode for editing moinmoin wiki text. This mode is installed via elpa, but must be initialized.

;; (condition-case nil
;;     (require 'moinmoin-mode)
;;   (error (message "** could not load moinmoin-mode")))

39 web-mode

A major-mode for editing web templates http://web-mode.org

Also: A Hydra for web-mode commands; activate via hydra-launcher using C-\ w

(if (require 'web-mode nil 'noerror)
    (use-package web-mode
      :mode (("\\.html" . web-mode))
      :bind ("C-c w" . hydra-web-mode/body)
      :init
      (setq web-mode-enable-current-element-highlight t)
      (setq web-mode-engines-alist
            '(("django" . "\\.html")))
      (setq indent-tabs-mode nil)
      (defhydra hydra-web-mode (:color blue :columns 4 :post (redraw-display))
        "hydra-web-mode"
        ("RET" redraw-display "<quit>")
        ("b" web-mode-element-beginning "element-beginning")
        ("e" web-mode-element-beginning "element-end")
        ("/" web-mode-element-close "element-close")))
  (message "** web-mode is not installed"))

40 tramp

(condition-case nil
    (require 'tramp)
  (setq tramp-default-method "scp")
  (error (message "** could not load tramp")))

41 git/magit

(require 'vc-git)

Magit is installed via ELPA.

(global-set-key (kbd "C-c m") 'magit-status)

42 sql support

Use sqlite3

(setq sql-sqlite-program "sqlite3")

Preset connections

(setq sql-connection-alist
      '((some-server
         (sql-product 'mysql)
         (sql-server "1.2.3.4")
         (sql-user "me")
         (sql-password "mypassword")
         (sql-database "thedb")
         (sql-port 3307))))

(defun sql-connect-preset (name)
  "Connect to a predefined SQL connection listed in `sql-connection-alist'"
  (eval `(let ,(cdr (assoc name sql-connection-alist))
    (flet ((sql-get-login (&rest what)))
      (sql-product-interactive sql-product)))))

(defun sql-mastermu ()
  (interactive)
  (sql-connect-preset 'mastermu))

;; buffer naming
(defun sql-make-smart-buffer-name ()
  "Return a string that can be used to rename a SQLi buffer.
This is used to set `sql-alternate-buffer-name' within
`sql-interactive-mode'."
  (or (and (boundp 'sql-name) sql-name)
      (concat (if (not(string= "" sql-server))
                  (concat
                   (or (and (string-match "[0-9.]+" sql-server) sql-server)
                       (car (split-string sql-server "\\.")))
                   "/"))
              sql-database)))

(add-hook 'sql-interactive-mode-hook
          (lambda ()
            (setq sql-alternate-buffer-name (sql-make-smart-buffer-name))
            (sql-rename-buffer)))

43 gpg

(require 'epa-file)
(setenv "GPG_AGENT_INFO" nil) ;; suppress graphical passphrase prompt

44 Outline minor mode

The default key bindings for outline-minor-mode start with 'C-c @ C-', which is… awkward. Use alternative bindings courtesy of Sue D. Nymme via emacswiki (http://emacswiki.org/emacs/OutlineMinorMode).

;; Outline-minor-mode key map
(define-prefix-command 'cm-map nil "Outline-")
;; HIDE
(define-key cm-map "q" 'hide-sublevels)    ; Hide everything but the top-level headings
(define-key cm-map "t" 'hide-body)         ; Hide everything but headings (all body lines)
(define-key cm-map "o" 'hide-other)        ; Hide other branches
(define-key cm-map "c" 'hide-entry)        ; Hide this entry's body
(define-key cm-map "l" 'hide-leaves)       ; Hide body lines in this entry and sub-entries
(define-key cm-map "d" 'hide-subtree)      ; Hide everything in this entry and sub-entries
;; SHOW
(define-key cm-map "a" 'show-all)          ; Show (expand) everything
(define-key cm-map "e" 'show-entry)        ; Show this heading's body
(define-key cm-map "i" 'show-children)     ; Show this heading's immediate child sub-headings
(define-key cm-map "k" 'show-branches)     ; Show all sub-headings under this heading
(define-key cm-map "s" 'show-subtree)      ; Show (expand) everything in this heading & below
;; MOVE
(define-key cm-map "u" 'outline-up-heading)                ; Up
(define-key cm-map "n" 'outline-next-visible-heading)      ; Next
(define-key cm-map "p" 'outline-previous-visible-heading)  ; Previous
(define-key cm-map "f" 'outline-forward-same-level)        ; Forward - same level
(define-key cm-map "b" 'outline-backward-same-level)       ; Backward - same level
;; commands are prefixed with C-c o
(global-set-key (kbd "C-c o") cm-map)

45 Search and replace

Here are a few packages that make search and replace more fun. I'll define a keymap using a hydra in the final section below.

45.1 occur-dwim

Copied from a post on "(or emacs"

Note that plain-old occur can be executed using "M-s o" by default (which I can never remember), of via hyrda-search defined below.

(defun occur-dwim ()
  "Call `occur' with the current region (if active) or word."
  (interactive)
  (push (if (region-active-p)
            (buffer-substring-no-properties
             (region-beginning)
             (region-end))
          (let ((sym (thing-at-point 'symbol)))
            (when (stringp sym)
              (regexp-quote sym))))
        regexp-history)
  (call-interactively 'occur))

45.2 visual-regexp

Two packages by the same author add visual cues to searching and/or replacing with regular expressions:

visual-regexp
https://github.com/benma/visual-regexp.el
visual-regexp-steroids
https://github.com/benma/visual-regexp-steroids.el/

Installed using ELPA.

(if (package-installed-p 'visual-regexp-steroids)
    (require 'visual-regexp-steroids))

45.3 helm-swoop

Show matches to a search string in a another window.

A couple of nice touches:

  • Use M-i to activate from within isearch
  • While searching, C-c C-e activates edit-mode, in which you can edit the buffer from within the helm-swoop buffer
(condition-case nil
    (require 'helm-swoop)
  (error (message "** could not activate helm-swoop")))

45.4 Hydra for searching

(if (require 'hydra nil 'noerror)
    (progn
      (defhydra hydra-search (:color blue :columns 4)
        "hydra-search"
        ("RET" helm-swoop "helm-swoop")
        ("b" helm-swoop-back-to-last-point "helm-swoop-back-to-last-point")
        ("f" file-file-in-project "find-file-in-project")
        ("m" helm-multi-swoop "helm-multi-swoop")
        ("M" helm-multi-swoop-all "helm-multi-swoop-all")
        ("o" occur "occur-dwim")
        ("O" occur-dwim "occur")
        ("r" vr/isearch-backward "vr/isearch-backward")
        ("s" vr/isearch-forward "vr/isearch-forward"))
      (global-set-key (kbd "C-c s") 'hydra-search/body))
  (message "** hydra is not installed"))

45.5 Hydra for string replacement

(if (require 'hydra nil 'noerror)
    (progn (defhydra hydra-replace (:color blue)
             "hydra-replace"
             ("RET" replace-string "replace-string")
             ("r" vr/replace "vr/replace")
             ("q" query-replace "query-replace")
             ("Q" vr/query-replace "vr/query-replace"))

           (global-set-key (kbd "C-c r") 'hydra-replace/body))
  (message "** hydra is not installed"))

46 Misc utilities

46.1 copy-buffer-file-name

(defun copy-buffer-file-name ()
  "Add `buffer-file-name' to `kill-ring' and echo the value to
the minibuffer"
  (interactive)
  (if buffer-file-name
      (progn
      (kill-new buffer-file-name t)
      (message buffer-file-name))
    (message "no file associated with this buffer")))
(make-alias 'copy-buffer-file-name)

46.2 copy-and-comment

(defun copy-and-comment ()
  "Comment active region and paste uncommented text on the
following line."
  (interactive)
  (kill-new
   (buffer-substring
    (region-beginning)
    (region-end)))
  (comment-region (region-beginning)
                  (region-end))
  (goto-char (region-end))
  (delete-blank-lines)
  (newline 2)
  (yank))

(global-set-key (kbd "M-C-;") 'copy-and-comment)

46.3 unfill-paragraph

from http://defindit.com/readme_files/emacs_hints_tricks.html

(defun unfill-paragraph ()
  (interactive)
  (let ((fill-column (point-max)))
  (fill-paragraph nil)))
(global-set-key (kbd "M-C-q") 'unfill-paragraph)
(make-alias 'unfill-paragraph)

46.4 Copy region to other window

Adapted from http://emacs.stackexchange.com/questions/3743/how-to-move-region-to-other-window

(defun copy-region-or-line-other-window ()
  "Copy selected text or current line to other window"
  (interactive)
  (progn (save-excursion
           (if (region-active-p)
               (copy-region-as-kill
                (region-beginning) (region-end))
             (copy-region-as-kill
              (line-beginning-position) (+ (line-end-position) 1)))
           (other-window 1)
           (yank))
         (other-window -1)))

(make-alias 'copy-region-or-line-other-window)

47 elisp-format

Written by Andy Stewart and available on emacswiki: http://www.emacswiki.org/emacs/elisp-format.el

(condition-case nil
    (require 'elisp-format)
  (error (message "** could not load elisp-format")))

48 emacsclient

Buffers opened from command line don't create new frame

(setq ns-pop-up-frames nil)

49 discover.el

Introduced in this blog post by Mickey Petersen, discover.el provides Magit-style contextual menus for dired (activate using ?), register keys in C-x r, and the Isearch keys in M-s. I do in fact discover something every time I use it!

(condition-case nil
    (progn
      (require 'discover)
      (global-discover-mode 1))
      (error (message "** could not activate discover")))

50 dired-x

Toggle dired-omit-mode with C-x M-o. Hide uninteresting files by default. By default, . and .. are hidden in dired-omit-mode; redefine dired-omit-files to show these.

(use-package dired-x
  :config
  (progn
    (setq dired-omit-verbose nil)
    (add-hook 'dired-mode-hook #'dired-omit-mode)
    (setq dired-omit-files "^\\.?#")))

51 enable "advanced" commands

Not sure why these are disabled by default.

(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)

52 custom-set-variables

Emacs modifies this statement if you use the interactive "customize" function, so don't do that.

(custom-set-variables
  '(safe-local-variable-values (quote ((toggle-read-only . t)))))

53 License

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

Author: Noah Hoffman

Created: 2019-11-19 Tue 21:39

Validate