Emacs: Pro-tips

Updated 2017-06-04

Emacs is a wild beast that takes a while to tame. Read upon the official documentation, learn Elisp, check out for the best third-party packages and learn from other users.

Here follows a selection of subtle best practices on how to set up Emacs as well as a selection of my favorite Emacs features. See my dotfiles for my complete configuration.

Emacsclient and Emacs server (daemon)

With time the configuration may grow and Emacs can take a while to load, especially when using heavy packages.

Running the daemon will make Emacs load the startup files only once. This allows Emacs instances to be fired up instantly, with the added benefit of a shared state between instances.

The simplest way to start the daemon (if not already started) and the instances is to set your editor as this script:

emacsclient -c -a "" "$@"

The -a "" option lets Emacs start a daemon if none was already started. The command will spawn a new Emacs frame and return immediately, which is usually what we want; this has some shortcomings however. While forking Emacs in the background is generally a good idea, there are times when it should wait (writing e-mails with Mutt, git commit message, web browser fields), others when it should fork. We can create links to it and parameterize the waiting behaviour depending on the name of the script. Which leads us to the following script: em forks and does not return while the emw and emc links (windowed and console version respectively) return to their caller.

if [ "${0##*/}" = "emc" ]; then
	## Force terminal mode
	param="-t"
else
	## If Emacs cannot start in graphical mode, -c will act just like -t.
	param="-c"
	if [ "${0##*/}" != "emw" ] && [ -n "$DISPLAY" ] && [ "$(emacs --batch -Q --eval='(message (if (fboundp '"'"'tool-bar-mode) "X" "TTY"))' 2>&1)" = X ]; then
		## Don't wait if not called with "emw" and if Emacs can start in graphical mode.
		## The Emacs batch test checks whether it was compiled with GUI suppport.
		param="$param -n"
	fi
fi

emacsclient "$param" -a "" "$@"

The windowed version of Emacs is not restricted by any terminal limitation; nonetheless the console version has the advantage of not spawning a new window when already running in console.

You can then set your environment to your liking, e.g. export EDITOR=em and VISUAL=emw: some programs use VISUAL and wait for it to return, while others use EDITOR and need not waiting. This is barely a convention though and some applications will require local adjustments.

If you want to pass additional parameters to Emacs, create an emacs wrapper and place it first in your PATH. For instance, say you want to use --no-site-file to enforce a vanilla setting on invasive distributions, you can write the following wrapper:

#!/bin/sh
exec /usr/bin/emacs --no-site-file "$@"

This will apply to both the daemon and stand-alone instances.

Initialization file architecture

With Emacs daemon we can factor the loading time of Emacs to a single instance, but we can still do better for that very instance. It is also wise to reduce the loading to a minimum, should you start some daemon-independent Emacs instance.

There is not one and only way to architecture Emacs initialization files, but surely so there are good practices. My design tenets:

Some like to rely on third-party packages to handle the configuration for them. I think it adds a layer of complexity (together with its inevitable bugs) and reduces flexibility.

For my configuration, here are the helper functions I use:

(defun add-hook-and-eval (hook function)
  "Add FUNCTION to HOOK and evaluate it."
  (add-hook hook function)
  (funcall function))

That’s it. Minimal, simple.

To minimize the startup time, we need to lazy load the configuration depending on the running modes. Everything that is not part of the global configuration can be conditionally loaded.

Major-modes configuration package

Every major-mode related configuration can be moved to its own configuration file which can be loaded:

In practice, it boils down to a simple line in init.el, e.g. for the C mode:

(add-hook 'c-mode-hook (lambda () (require 'mode-cc)))

The first time a C buffer is created, the hook contains only the require function that loads up the rest of the configuration from the mode-cc.el file. This file is loaded only once.

The mode-cc.el file should contain a C-specific global configuration: variables, function definitions, skeletons, etc.

(setq semanticdb-default-save-directory (concat emacs-cache-folder "semanticdb"))
(semantic-mode 1)
(local-set-key (kbd "<f10>") (lambda () (compile compile-command)))
...

; Need to end with `provide' so that `require' does not load the file twice.
(provide 'mode-cc)

Note that local-set-key generally sets the mode map globally and is not buffer-local. If it is, it means that the mode is not using the standard mode API or it hasn’t called use-local-map. You should probably report the issue upstream.

Some of your configuration might need to be buffer-local, in which case you must add it to the mode hook. Cluttering hooks will slow down buffer creation and can become a source of confusion, so it is advised to stick to only what requires a hook.

The first time a buffer is created in this mode it will run the mode hook only containing the require function. At that time any addition done to the hook will not be applied to the currently loading buffer (regardless of whether the additions are prepended or appended). This will make the current buffer partly configured, but we can easily fix that with our custom add-hook-and-eval:

(add-hook-and-eval
  c-mmode-hook
  (lambda ()
    (setq indent-tabs-mode t)
    (set (make-local-variable 'compile-command) (concat "go run " (shell-quote-argument buffer-file-name)))
    (add-hook 'before-save-hook #'gofmt-before-save nil t)
    ; ...

This last example shows three types of relevant hook use:

Package management

Third party packages, major modes or not, can be loaded similarly depending on their availability: if the package is not installed, there is no need to parse it’s configuration. Thus we move the package configurations to a separate file and load it with:

(when (require 'PACKAGE nil t)
  (add-hook 'PACKAGE-HOOK (lambda () (require 'PACKAGE-CONFIG-FILE)))

if a major mode (or if initialization does not need to occur on startup), or

(when (require 'PACKAGE nil t)
  (require 'PACKAGE-CONFIG-FILE))

otherwise.

The NOERROR parameter tells require not to signal an error when the package is not found. require returns nil when the load fails, so we make sure to load the configuration only when the package is found.

If the package’s initialization can be delayed, we lazy-load the configuration like before thanks to a hook. If the package needs to be initialized on startup, we load the configuration immediately.

Helm

Helm is a UI revolution: It will add incremental narrowing (fuzzy) search to… everything!

The concept: instead of listing and selecting, it will list and narrow down as you type, while sorting by the most relevant results first. Beside, the search can be fuzzy, which makes it practical to find things when you do not know the exact name.

You can lookup buffers, commands, documentation, files, and more: pretty much anything that requires a lookup. See this article for a more exhaustive presentation.

The one killer feature is the ability to search text in your whole project or file tree. Helm comes with a few greppers: grep itself, but it also supports the current version control grepper and other tools such as ag and pt.

The VCS grepper is usually faster than grep. I have set the bindings to use the VCS grepper first and to fallback to pt when no file in the current folder is versioned:

(defun call-process-to-string (program &rest args)
  "Call PROGRAM with ARGS and return output."
  (with-output-to-string
    (with-current-buffer
        standard-output
      (apply 'call-process program nil t nil args))))

(defun helm-grep-git-or-ag (arg)
  "Run `helm-grep-do-git-grep' if possible; fallback to `helm-do-grep-ag' otherwise."
  (interactive "P")
  (require 'vc)
  (if (and (vc-find-root default-directory ".git")
           (or arg (split-string (call-process-to-string "git" "ls-files" "-z") "\0" t)))
      (helm-grep-do-git-grep arg)
    (helm-do-grep-ag arg)))

(define-key mickey-minor-mode-map (kbd "C-x G") 'helm-grep-git-or-ag)

I prefer using pt before ag:

(setq helm-grep-ag-command "pt -S --hidden --color --nogroup %s %s %s")

Other features of Helm:

Coping with outdated systems

You might like Emacs enough you want it everywhere. And yet sometimes you are forced to use an outdated, crappy system on which you have no administrative priviledge.

One way to go would be to compile the latest version of Emacs. Depending on the host system, that could be a daunting task. (Dirty patches, outdated dependencies…)

In case I have to stick to an outdated Emacs, I have the following hook at the very end of my versioned configuration:

(load "local" t t)

The local.el file, in turn, remains unversioned since it will be system specific.

Example hook:

; This was not on by default on old Emacsen.
(transient-mark-mode 1)

; The host system only has evince:
(setq pdf-viewer "evince")
(setq pdf-viewer-args nil)

; Old Emacs does not know about 'prog-mode-hook.
(mapcar
 (lambda (mode-hook)
   (add-hook mode-hook (lambda () (run-hooks 'prog-mode-hook))))
 '(asm-mode-hook awk-mode-hook c++-mode-hook c-mode-hook
                 emacs-lisp-mode-hook lisp-mode-hook lua-mode-hook
                 makefile-mode-hook octave-mode-hook perl-mode-hook
                 python-mode-hook scheme-mode-hook sh-mode-hook))

Cache folder

(Or how to keep your configuration folder clean.)

Many modes store their cache files in ~/.emacs.d. I prefer to keep those ephemeral files in ~/.cache/emacs.

(defvar emacs-cache-folder "~/.cache/emacs/"
  "Cache folder is everything we do not want to track along with
  the configuration files.")
(if (not (file-directory-p emacs-cache-folder))
    (make-directory emacs-cache-folder t))

Some common settings:

(setq save-place-file (concat emacs-cache-folder "saveplace"))
(setq url-cookie-file (concat emacs-cache-folder "url.cookies"))
(setq bookmark-default-file (concat emacs-cache-folder "emacs.bmk"))
(setq recentf-save-file (concat emacs-cache-folder "recentf"))
(setq backup-directory-alist `((".*" . ,(concat emacs-cache-folder "backups/"))))
(setq eshell-directory-name (concat emacs-cache-folder "eshell"))

The cache folder must exist for desktop-mode:

(setq desktop-dirname (concat emacs-cache-folder "desktop"))
(unless (file-directory-p desktop-dirname)
  (make-directory desktop-dirname t))
(setq desktop-path `(,desktop-dirname))

Semantic must be started after setting the db folder:

(setq semanticdb-default-save-directory (concat emacs-cache-folder "semanticdb"))
(semantic-mode 1)

Streamline indentation

I think Emacs has too many options for indentation. Since I have a strong opinion on always using tabs to indent (except for Lisp), I “redirect” with defvaralias the mode-specific indentation levels to only one variable, namely tab-width.

(defvaralias 'standard-indent 'tab-width)
(setq-default indent-tabs-mode t)

;; Lisp should not use tabs.
(mapcar
 (lambda (hook)
   (add-hook
    hook
    (lambda ()
      (setq indent-tabs-mode nil))))
 '(lisp-mode-hook emacs-lisp-mode-hook))

;; This needs to be set globally since they are defined as local variable and
;; Emacs does not know how to set an alias on a local variable.
(defvaralias 'c-basic-offset 'tab-width)
(defvaralias 'sh-basic-offset 'tab-width)

Add the following to sh-mode-hook:

(defvaralias 'sh-indentation 'sh-basic-offset)

The case of C and sh are special for historical reasons. Other modes can be corrected as follows:

(defvaralias 'js-indent-level 'tab-width)
(defvaralias 'lua-indent-level 'tab-width)
(defvaralias 'perl-indent-level 'tab-width)

Elisp ‘go to definition’

Elisp has the find-variable-at-point and the find-function-at-point functions, yet it does not have a proper go to definition command. Not for long:

(define-key lisp-mode-shared-map "\M-." 'find-symbol-at-point)

(defun find-symbol-at-point ()
  "Find directly the symbol at point, i.e. go to definition."
  (interactive)
  (let ((sym (symbol-at-point)))
    (if (boundp sym)
        (find-variable sym)
      (find-function sym))))

Smart compilation

Emacs has a compilation mode that comes in very handy to run arbitrary commands over your buffer and navigate the errors back to the source code.

It is not only useful for compilers but also for, say, browsing your own programs’ debug messages, a linter, etc.

Emacs standard behaviour is to store the last used compile command in the global variable compile-command. Similarly, compile-history remembers all compile commands used globally. This is useful if you jump from buffer to buffer and want to run the same compile command for your project without having to switch back to a specific buffer.

Another approach is to make compile-command buffer-local. You’ll have to be in a specific buffer to run the desired command. In practice, I find myself having to run several buffer-specific commands per project (documentation, linting, library builds, executable builds, etc.).

To use the buffer-local approach, add this to your init file before configuring the modes:

(eval-after-load 'compile (make-variable-buffer-local 'compile-command))

The compile command can be modified per buffer upon request. If you use the desktop mode to save your session, each buffer’s command can be restored as well.

(add-to-list 'desktop-locals-to-save 'compile-command)

Emacs provides two compilation commands:

In short: when using a buffer-local compile-command, we are better off sticking to compile and leaving recompile aside.

Let’s add some bindings for convenience:

(global-set-key (kbd "C-<f10>") 'compile)
(global-set-key (kbd "<f10>") (lambda () (interactive) (compile compile-command)))

Here follows a complete example for C: it will look for the closest Makefile in the parent folders and set the command to make -C /path/to/makefile or else fallback on some dynamically set values depending on the language (C or C++) and the environment (GCC, Clang, etc.). The linker flags are configurable on a per-buffer basis thanks to the buffer-local cc-ldlibs and cc-ldflags variables.

(defvar-local cc-ldlibs "-lm -pthread"
  "Custom linker flags for C/C++ linkage.")

(defvar-local cc-ldflags ""
  "Custom linker libs for C/C++ linkage.")

(defun get-closest-pathname (&optional file)
  "Get pathname of the first instance of FILE towards root.
If FILE is unspecified, look for 'Makefile'. If it does not find
FILE, return nil. This may not do the correct thing in presence
of links."
  (let* ((pwd default-directory) (file (or file "Makefile")) (target (expand-file-name file pwd)))
    (while
        (unless (or (file-exists-p target) (equal pwd "/"))
          (setq pwd (expand-file-name ".." pwd))
          (setq target (expand-file-name file pwd))))
    (if (file-exists-p target) target nil)))

(defun cc-set-compiler (&optional nomakefile)
  "Set compile command to be nearest Makefile or a generic command.
The Makefile is looked up in parent folders. If no Makefile is
found (or if NOMAKEFILE is non-nil or if function was called with
universal argument), then a configurable command line is
provided.\n Requires `get-closest-pathname'."
  (interactive "P")
  (require 'functions)
  (hack-local-variables)
  (let ((makefile (get-closest-pathname)))
    (if (and makefile (not nomakefile))
        (setq compile-command (concat "make -k -C " (shell-quote-argument (file-name-directory makefile))))
      (setq compile-command
           (let
               ((c++-p (eq major-mode 'c++-mode))
                (file (file-name-nondirectory buffer-file-name)))
             (format "%s %s -o '%s' %s %s %s"
                     (if c++-p
                         (or (getenv "CXX") "g++")
                       (or (getenv "CC") "gcc"))
                     (shell-quote-argument file)
                     (shell-quote-argument (file-name-sans-extension file))
                     (if c++-p
                         (or (getenv "CXXFLAGS") "-Wall -Wextra -Wshadow -DDEBUG=9 -g3 -O0")
                       (or (getenv "CFLAGS") "-ansi -pedantic -std=c11 -Wall -Wextra -Wshadow -DDEBUG=9 -g3 -O0"))
                     (or (getenv "LDFLAGS") cc-ldflags)
                     (or (getenv "LDLIBS") cc-ldlibs)))))))

(defun cc-clean ()
  "Find Makefile and call the `clean' rule. If no Makefile is
found, no action is taken. The previous `compile' command is then
restored."
  (interactive)
  (let (compile-command (makefile (get-closest-pathname)))
    (when makefile
      (compile (format "make -k -f '%s' clean" makefile)))))

(mapc
 (lambda (mode-hook)
   (add-hook-and-eval
    mode-hook
    (lambda ()
      (cc-set-compiler)
      (local-set-key (kbd "<f9>") 'cc-clean)
 '(c-mode-hook c++-mode-hook))

C pretty format

I use uncrustify to format my C code automatically. See my indentation rationale.

I can call it from Emacs with the following function:

(defun cc-fmt ()
  "Run uncrustify(1) on current buffer or region."
  (interactive)
  (let  (status
         start end
         (formatbuf (get-buffer-create "*C format buffer*")))
    (if (use-region-p)
        (setq start (region-beginning) end (region-end))
      (setq start (point-min) end (point-max)))
    (setq status
          (call-process-region start end "uncrustify" nil formatbuf nil "-lc" "-q" "-c" (concat (getenv "HOME") "/.uncrustify.cfg")))
    (if (/= status 0)
        (error "error running uncrustify")
      (delete-region start end)
      (insert-buffer formatbuf)
      (kill-buffer formatbuf))))

We could add this to the save hook to auto-format my code at all times, but that would be bad practice when working with source code using different formatting rules.

Magit

Magit makes Git management a bliss. The most evident feature would be the easy hunk selection when staging code. This simple feature together with a few others will make a drastic change to your workflow.

Multiple cursors

See this video for a short introduction of this very powerful editing framework.

As of Septermber 2016, multiple cursors does not support searching, so I use phi-search that automatically adds support to it.

(when (require 'multiple-cursors nil t)
  (setq mc/list-file (concat emacs-cache-folder "mc-lists.el"))
  ;; Load the file at the new location.
  (load mc/list-file t)
  (global-unset-key (kbd "C-<down-mouse-1>"))
  (define-key mickey-minor-mode-map (kbd "C-<mouse-1>") 'mc/add-cursor-on-click)
  (define-key mickey-minor-mode-map (kbd "C-x M-r") 'mc/edit-lines)
  (define-key mickey-minor-mode-map (kbd "C-x M-m") 'mc/mark-more-like-this-extended)
  (define-key mickey-minor-mode-map (kbd "C-x M-l") 'mc/mark-all-like-this-dwim)
  ;; Search compatible with mc.
  (require 'phi-search nil t))

Org Mode

Last but not least, the famous Org Mode. I do not really endorse the bloated swiss-army-knife design, but Org Mode offers some impressive capabilities. Such as seamless table manipulation (swap columns with a keystroke…) and formula computation. From the manual:

Finally, just to whet your appetite for what can be done with the
fantastic `calc.el' package, here is a table that computes the Taylor
series of degree `n' at location `x' for a couple of functions.

 |---+-------------+---+-----+--------------------------------------|
 |   | Func        | n | x   | Result                               |
 |---+-------------+---+-----+--------------------------------------|
 | # | exp(x)      | 1 | x   | 1 + x                                |
 | # | exp(x)      | 2 | x   | 1 + x + x^2 / 2                      |
 | # | exp(x)      | 3 | x   | 1 + x + x^2 / 2 + x^3 / 6            |
 | # | x^2+sqrt(x) | 2 | x=0 | x*(0.5 / 0) + x^2 (2 - 0.25 / 0) / 2 |
 | # | x^2+sqrt(x) | 2 | x=1 | 2 + 2.5 x - 2.5 + 0.875 (x - 1)^2    |
 | * | tan(x)      | 3 | x   | 0.0175 x + 1.77e-6 x^3               |
 |---+-------------+---+-----+--------------------------------------|
 #+TBLFM: $5=taylor($2,$4,$3);n3

Note that the last column is computed automatically! Formulae can be computed using the Calc mode, Elisp, or even external programs such as R or PARI/GP. Possibilities are endless.

Finally, you can export the end result to LaTeX, HTML, etc.