About Articles Projects Links Apps Feed

Emacs Pro-tips

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.

Also see the follow-up article.

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

  • Minimize Emacs startup time.
  • Keep it simple.
  • Don’t use configuration frameworks.

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:

  • just in time when it is needed thanks to with-eval-after-load,
  • and only once thanks to the require function.

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 require’s 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:

  • Set a buffer-local variable. (Those variables whose documentation shows “Automatically becomes buffer-local when set.”, like indent-tabs-mode). If not added to a hook, the change would apply to the current buffer only. Global variables can be permanently made buffer-local with the make-variable-buffer-local command.
  • Set a variable to be buffer-local for this mode only and set its value. compile-command is global by default: making it buffer-local in the mode hook allows for setting different compile commands for the various buffers in this mode while other modes will keep dealing with a global compile command.
  • Make a buffer-local change to a hook thanks to the LOCAL parameter of the add-hook function. Adding this hook change to the mode hook will effectively apply the hook change to all buffers in this mode while leaving it untouched for other modes.

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 interactively.

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 its 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 startup:

(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 (e.g. git grep) 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 ag 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)))

(global-set-key (kbd "C-x G") #'helm-grep-git-or-ag)

Other features of Helm:

  • Lookup global variables and functions in current buffer with helm-semantic-or-imenu, or for all buffers with helm-imenu-in-all-buffers.
  • To enable proper fuzzy finding when finding files recursively (helm-find), set helm-findutils-search-full-path to non-nil.
  • Lookup files in the Git project with third-party helm-ls-git.
    • Call yank to lookup last region.
    • Use the universal argument to include more to your lookup (e.g. subfolders).
  • Use C-c C-f to activate follow mode and navigate through the results to display a complete context.
  • Save some helm sessions with C-x C-s for later re-use. Edit grep buffers with wgrep and apply the changes all at once.
  • Or resume last helm session with C-x c b.
  • I like to replace M-s o with helm-occur, C-x C-x with helm-all-mark-rings, M-y with helm-show-kill-ring, etc.
  • Lookup completion suggestions with helm-company.
  • Browse Man page sections with helm-imenu.

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.

(setq user-emacs-directory "~/.cache/emacs/")
(if (not (file-directory-p user-cache-directory))
    (make-directory user-cache-directory t))

;; Some files need to be forced to the cache folder.
(setq geiser-repl-history-filename (expand-file-name "geiser_history" user-emacs-directory))
(setq elfeed-db-directory (expand-file-name "elfeed" user-emacs-directory))

;; Place backup files in specific directory.
(setq backup-directory-alist
      `(("." . ,(expand-file-name "backups" user-emacs-directory))))

If you use Semantic, make sure it is started after changing the cache folder since its database is stored there.

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 cases of C and sh are special for historical reasons. Other modes indentation 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:

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

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

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:

  • (compile COMMAND &optional COMINT) prompts for the command to run when called interactively. A user-defined command can do that with (call-interactively 'compile). Make compile-command local to the function scope if you want to run a temporary command.
  • (recompile &optional EDIT-COMMAND) is handy to recall last command without prompting the user. It has some shortcomings when using a buffer-local compile-command:
    • compile-history remains untouched unless we do some manual bookkeeping.
    • It uses a global compilation-directory, thus calling recompile in another buffer will fail if the target file is in a different folder. We can make that variable buffer-local, but that would only work if we never use compile. In such a scenario, compile-history is unused.

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 to 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 ((formatbuf (get-buffer-create "*C format buffer*"))
	status start end)
    (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 before-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>"))
  (global-set-key (kbd "C-<mouse-1>") #'mc/add-cursor-on-click)
  (global-set-key (kbd "C-x M-r") #'mc/edit-lines)
  (global-set-key (kbd "C-x M-m") #'mc/mark-more-like-this-extended)
  (global-set-key (kbd "C-x M-l") #'mc/mark-all-like-this-dwim)
  ;; mc-compatible with search.
  (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

Comments

Date: 2016-09-28 (Last update: 2018-09-03)

Made with Emacs 27.0.50 (Org mode 9.1.9)

Creative Commons License