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.
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.
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.
Every major-mode related configuration can be moved to its own configuration file which can be loaded:
with-eval-after-load,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
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:
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 at
runtime.
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 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:
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.
Use 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.
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.
(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)
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 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))))
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 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))
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 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.
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.
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.
Aggregator wikis:
User configurations:
Blogs and other resources: