Emacs Pro-tips

Updated 2017-09-15

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

It is also wise to reduce the loading time to a minimum, should you start some daemon-independent Emacs instance (e.g. for development purposes).

There is not one and only way to architecture Emacs initialization files, but surely so there are some good practices. My personal 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.

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:

(with-eval-after-load 'cc-mode (require 'init-cc))

The first time a C buffer is created, the c-mode autoloaded function requires the cc-mode.el. When this one is loaded, the with-eval-after-load kicks in and requires our additional init-cc. The form of with-eval-after-load is evaluated everytime a C buffer is loaded, thus it is important to rely on require instead of load so that we load our configuration only once.

The init-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 "<f6>") (recompile))
...

; Need to end with `provide' so that `require' does not load the file twice.
(provide 'init-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.

(defun go-setup ()
  (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))
(add-hook go-mode-hook go-setup)

This last example shows three types of relevant hook use:

Last but not least, refrain from using lambdas in hooks: it makes the documentation and the intention harder to understand, while making it much trickier to use the remove-hook function, should you need to alter the hook at runtime.

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. The procedure is the same:

(with-eval-after-load 'lua-mode (require 'init-lua))

If you want to make a mode immediately available on start-up:

(when (require 'helm-config nil t) (require 'init-helm))

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)

Other features of Helm:

Update to latest Emacs version

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.

I would not advise sticking to an outdated versions: too many essential features and packages rely on recent Emacs.

Thankfully it’s easy enough to compile the latest Emacs thanks to its extreme portability and can be installed within the user home folder.

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 (expand-file-name "saveplace" emacs-cache-folder))
(setq url-cookie-file (expand-file-name "url.cookies" emacs-cache-folder))
(setq bookmark-default-file (expand-file-name "emacs.bmk" emacs-cache-folder))
(setq recentf-save-file (expand-file-name "recentf" emacs-cache-folder))
(setq backup-directory-alist `((".*" . ,(expand-file-name "backups/" emacs-cache-folder))))
(setq eshell-directory-name (expand-file-name "eshell" emacs-cache-folder))

The cache folder must exist for desktop-mode:

(setq desktop-dirname (expand-file-name "desktop" emacs-cache-folder))
(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 (expand-file-name "semanticdb" emacs-cache-folder))
(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 variables 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:

(defun compile-last-command () (interactive) (compile compile-command))
(global-set-key (kbd "C-<f6>") 'compile)
(global-set-key (kbd "<f6>") 'compile-last-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 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 commandline is
provided."
  (interactive "P")
  (hack-local-variables)
  ;; Alternatively, if a Makefile is found, we could change default directory
  ;; and leave the compile command to "make".  Changing `default-directory'
  ;; could have side effects though.
  (let ((makefile-dir (locate-dominating-file "." "Makefile")))
    (if (and makefile-dir (not nomakefile))
        (setq compile-command (concat "make -k -C " (shell-quote-argument (file-name-directory makefile-dir))))
      (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
restored."
  (interactive)
  (let (compile-command
        (makefile-dir (locate-dominating-file "." "Makefile")))
    (when makefile-dir
      (compile (format "make -k -C %s clean" (shell-quote-argument makefile-dir))))))

(dolist (map (list c-mode-map c++-mode-map))
  (define-key map "<f5>" 'cc-clean))

(dolist (hook '(c-mode-hook c++-mode-hook))
  (add-hook hook 'cc-set-compiler))

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))

If you are an Evil user, multiple-cursors will not work. Use the dedicated evil-mc instead.

Org Mode

Last but not least, the famous Org Mode. It offers some impressive features, 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.

References

Aggregator wikis:

User configurations:

Blogs and other resources: