Emacs: Pro-tips

Updated 2016-12-30

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 my favorite Emacs features. See my dotfiles for my complete configuration.

Daemon and lazy configuration

With time the configuration may grow and Emacs can take a while to load, especially when using heavy plugins. Running the daemon can alleviate this issue (with the added benefit of a shared state between instances).

I use the following em script to start Emacs. If the server is already started, then it connects to it in a new frame.

if [ "${0##*/}" = "emc" ] || [ -z "$DISPLAY" ] || \
		 [ "$(emacs --batch -Q --eval='(if (fboundp '"'"'tool-bar-mode) (message "X") (message "TTY"))' 2>&1)" = TTY ]; then
	param="-t"
else
	if [ "${0##*/}" = "emw" ]; then
		param="-c"
	else
		param="-nc"
	fi
fi

[ ! -e "/tmp/emacs$(id -u)/server" ] && EMACS_SERVER=t emacs --daemon --no-site-file
emacsclient $param "$@"

em forks and does not return. The emw and emc links to this script (windowed and console version respectively) return to their caller. This can be useful in some contexts: Mutt, ranger bulkrename, git commit message and web browser field editing. The windowed version of Emacs is not restricted by any terminal limitation; but the console version has the advantage of not spawning a new window when already running in console. I use the --no-site-file parameter to force starting from a vanilla configuration: some distributions tweak Emacs in ways I dislike.

It is possible to do better: lazy load your configuration. Every mode related configuration can be moved to its own configuration file which can be loaded:

Example: In init.el:

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

And in mode-cc.el:

(semantic-mode 1)
; ...
(provide 'mode-cc)

Some code needs to be buffer local, in which case you must add it to the mode hook. A word of caution: the less code in the hook the faster. Back to mode-cc.el:

(add-hook-and-eval
  c-mmode-hook
  (lambda ()
    (local-set-key (kbd "M-.") 'semantic-ia-fast-jump)
    ; ...

Note the use of our custom add-hook-and-eval:

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

The first C buffer will trigger the load of mode-cc, at which point the c-mode-hook only contains (require 'mode-cc). Had we used a simple add-hook, the hook code would only apply to subsequent C buffers and not to the first one.

Emacs provide other functions to help lazy-loading:

Package management

If you do not want Emacs to fail on startup because of missing third-party packages (e.g. some packages are not crucial to your configuration), simply load them with

(require 'PACKAGE nil t)

If you want to make the configuration dependent on the presence of the package, it is just as simple:

(when (require 'PACKAGE nil t)
  ; configure here

If the package configuration is consequent, consider moving it to its own file and hooking its configuration to spare some initialization time and keep your configuration clear:

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

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 there 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 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:

(add-hook
 'emacs-lisp-mode-hook
 (lambda ()
   (local-set-key (kbd "M-.") 'find-symbol-at-point)))

(defun find-symbol-at-point ()
  "Find directly the symbol at point in the other window."
  (interactive)
  (let ((sym (symbol-at-point)))
    (if (boundp sym)
        (find-variable-at-point)
      (find-function-at-point))))

C smart compilation

I use Emacs’ recompile command to compile in C. With some tweaking it is possible to automatically look for the closest Makefile in the parent folders or to fallback to some sane defaults:

(defcustom cc-ldlibs "-lm -pthread"
  "Custom linker flags for C/C++ linkage."
  :safe 'stringp)
(make-variable-buffer-local 'cc-ldlibs)

(defcustom cc-ldflags ""
  "Custom linker libs for C/C++ linkage."
  :safe 'stringp)
(make-variable-buffer-local 'cc-ldflags)

(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))
        (set (make-local-variable 'compile-command) (concat "make -k -C " (shell-quote-argument (file-name-directory makefile))))
      (set (make-local-variable '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 "CPPFLAGS") "-Wall -Wextra -Wshadow -DDEBUG=9 -g3 -O0")
                       (or (getenv "CFLAGS") "-ansi -pedantic -std=c99 -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)))))

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

The compile command can be modified per buffer upon request and it will be saved for further use. If you use the desktop mode to save your session, the command will be saved as well.

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)
  (unless mark-active
    (mark-whole-buffer))
  (when (> (point) (mark))
    (exchange-point-and-mark))
  (let ((status)
        (formatbuf (get-buffer-create "*C format buffer*")))
    (setq status
          (call-process-region (point) (mark) "uncrustify" nil formatbuf nil "-lc" "-q"))
    (if (/= status 0)
        (error "Bad formatted C file")
      (delete-region (point) (mark))
      (insert-buffer formatbuf)
      (kill-buffer formatbuf))))

I could add this to the save hook to auto-format my code at all times, but that would break 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.