I’m a Vim-in-terminal user that has converted to a terminal-in-Emacs user since 2021. These days, I’m running a pretty typical Evil setup that embraces Emacs’ power and amazing tools, but uses Vim-like modal editing.
My Emacs configuration is extracted from this document, so both should remain in sync. Unless I’m experimenting with something locally that I haven’t pushed yet, this document describes the configuration I’m currently using in my editor.
Configuration
This configuration uses Emacs version 30.2, from the latest stable branch of nixpkgs. I’m inclined to use a stable version of Emacs instead of building from Git, as I’m not specifically looking to be on the bleeding edge.
Emacs-overlay
The configuration is then bundled in through emacs-overlay, which reads the configuration file and automatically installs packages for each use-package declaration that has its :ensure keyword set.
The packages are installed from Elpa, Melpa or NonGNU, and the overlay updates these repositories daily, but this configuration only updates sporadically, when there’s reason to do so, to keep everything as stable as possible.
{pkgs, ...}:
pkgs.emacsWithPackagesFromUsePackage {
config = ./default.el;
defaultInitFile = true;
package = pkgs.emacs-pgtk;
}
Literal configuration
The configuration file in default.el is generated from the emacs.org source file, an Org document with a literal configuration, from which the configuration file is extracted.
Emacs-overlay can read Org files with Emacs Lisp code blocks directly, which would remove the need to keep a separate default.el file in the repository.
This configuration chooses to do so anyway because of three reasons:
- Emacs-overlay parses code blocks in the Org file, so it doesn’t understand any blocks that have noweb tags (like
<<ef-themes-custom>>), which are extensively used. - Keeping the
default.elfile in the repository keeps that as the single point of truth, meaning the literal configuration is just a means to generate the configuration. That means the configuration’s file history remains intact, and the way of generating it could be changed or removed later without affecting the configuration. - When debugging issues, it’s helpful to see what’s actually read by Emacs, and it’s easier to quickly test some changes in the Emacs Lisp configuration file.
Use-package
Aside from using Emacs’ use-package as a way to install and configure external packages, this configuration also uses it to configure Emacs itself.
An example of where this happens is enabling Emacs’ built-in savehist feature by calling the savehist-mode function.
(savehist-mode 1)
The example above is used in this document, since it’s is sufficient to get it to work in a configuration. However, in the exported configuration file, it’s wrapped in a use-package block to distinguish it from other configuration options.
(use-package savehist :config (savehist-mode 1))
The whole use-package declaration is included in this document when it’s required for the code sample to work, like when depending on an external package.
(use-package no-littering :ensure t :config (no-littering-theme-backups))
General
No littering
The no-littering plugin helps keep Emacs’ configuration directory clean by setting up configuration and data directories, and having Emacs and its packages place their files in there.
Calling the no-littering-theme-backups function also sets the location for auto save and backup files, so they’re also stored in Emacs’ configuration directory.
The plugin is loaded as early as possible, before anything else changes any path variables.
(use-package no-littering :ensure t :config (no-littering-theme-backups))
Ad-hoc package installs
Packages are installed with emacs-overlay, which installs packages listed in the configuration file.
To install packages temporarily to test them out or to do a one-off task, use Emacs’ package-install.
To make more packages available, add Melpa to the list of package archives.
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
Disable unused user interface elements
Turn off scroll-bar-mode, tool-bar-mode, and menu-bar-mode to disable the scroll bar, tool bar, and menu bar.
(scroll-bar-mode -1)
(tool-bar-mode -1)
(menu-bar-mode -1)
Inhibit the startup screen
Don’t snow the startup screen when opening a new Emacs frame, but open an empty scratch buffer instead.
(setq inhibit-startup-screen t)
Disable sounds
Flash the screen instead of sounding a bell for illegal input.
(setq visible-bell t)
Use short answers
Instead of having to type “yes” or “no” to answer questions in the minibuffer, always use the shorthands “y” and “n”.
(setq use-short-answers t)
History
Save the minibuffer history to a file to be reused in future Emacs sessions.
(savehist-mode 1)
Appearance
Fonts
Use Iosevka (with its “extended” width) as the default and fixed pitch font faces, and “Iosevka Aile” as the variable pitch face. Since the other faces inherit from the default face, set the height and width on the default only.
(set-face-attribute 'default nil :family "Iosevka" :height 140 :width 'expanded) (set-face-attribute 'fixed-pitch nil :family "Iosevka") (set-face-attribute 'variable-pitch nil :family "Iosevka Aile")
Mixed pitch
The mixed-pitch package swaps the default font to the variable pitch one, and uses the default font only for faces in mixed-pitch-fixed-pitch-faces.
This results in more variable-width text, but not for tables, terminals, and most code.
(use-package mixed-pitch :ensure t :hook text-mode)
Themes
The ef-themes is a set of themes for emacs, now based on the modus-themes. This configuration switches between the themes in the list from time to time, taking a light and dark pair every time.
Enable mixed fonts to add variable pitch text, and italic and bold constructs to allow the use of italic and bold text variants. Finally, use variable pitch text for UI elements, like the mode line.
(setq modus-themes-mixed-fonts t) (setq modus-themes-italic-constructs t) (setq modus-themes-bold-constructs t) (setq modus-themes-variable-pitch-ui t)
Increase the font size of level 1 and 2 headings to match default HTML <h2> and <h3> tags in text documents.
Headings are printed in bold type in a variable pitch font without any extra configuration needed.
(setq modus-themes-headings (quote ((1 1.5) (2 1.17))))
To toggle the Ef themes using the modus-themes-toggle function, the Modus themes need to be configured to consider its derivatives through modus-them
es-include-derivatives-mode.
(modus-themes-include-derivatives-mode 1)
Then, set which themes to toggle between by configuring modus-themes-to-toggle.
(setq modus-themes-to-toggle '(ef-winter ef-summer))
After the configuration is set, enable one of the themes through modus-themes-load-theme.
(modus-themes-load-theme 'ef-winter)
Finally, add the configuration above to a use-package block which ensures the package is available, configures and enables it.
(use-package ef-themes :ensure t :custom (modus-themes-mixed-fonts t) (modus-themes-italic-constructs t) (modus-themes-bold-constructs t) (modus-themes-variable-pitch-ui t) (modus-themes-headings (quote ((1 1.5) (2 1.17)))) (modus-themes-to-toggle '(ef-winter ef-summer)) :config (modus-themes-include-derivatives-mode 1) (modus-themes-load-theme 'ef-winter))
Window padding and subtitle mode lines
The spacious-padding package adds padding around Emacs windows when enabled.
The spacious-padding-subtile-mode-line variable is set to switch the default mode line to a more subtile version with a single line and no background color.
Enabling both gives Emacs a slightly more modern feel.
(use-package spacious-padding :ensure t :config (spacious-padding-mode 1) :custom spacious-padding-subtle-mode-line t)
Hide minor modes from mode lines
By default, the mode line displays the current major mode, and minor modes have the option to show a minor mode symbol. That’s useful in theory, but packages like mixed-pitch-mode and evil-commentary-mode also have these symbols set, which I never found useful.
Use-package’s :diminish keyword is a way to hide selected minor modes from the mode line, but it needs to be applied to every package that sets a minor mode symbol.
Instead, this configuration uses the minions package, which replaces all minor mode symbols with a single-character menu that only lists all minor modes when clicked.
(use-package minions :ensure t :config (minions-mode 1))
Resize frames per pixel
By default, Emacs resizes the window per line and column, so the height of the frame is always a multiple of the line height.
The frame-resize-pixelwise configuration option lifts that limitation, and lets frames be resized to pixel-precise widths and heights.
(setq frame-resize-pixelwise t)
Modal editing
Evil
It might not be the best option for modal editing in Emacs, but Evil is definitely the best Vim mode.
Instead of enabling Evil’s global evil-mode hook, turn it on per buffer.
By hooking into prog-mode, text-mode, and conf-mode Evil mode is only enabled for programming, text, and config editing buffers, not everywhere else.
By relying on Emacs’ own key bindings outside of text editing, the evil-collection package is no longer needed.
An example of an essential difference between Emacs and Vim is how they handle the location of the cursor (named “point” in Emacs).
In Vim, the cursor is on a character, while Emacs’ point is before it.
In Evil mode, the cursor changes between a box in “normal mode” to a bar in “insert mode".
Because Emacs is always in a kind of insert mode, make the default-cursor a bar.
By default, Evil binds the TAB key to jump forward, which breaks opening and closing headlines in Org documents.
The evil-want-C-i-jump option is set to nil to prevent this, keeping the TAB key for inserting tab characters and cycling Org headlines.
(use-package evil :ensure t :config (setq-default cursor-type 'bar) :custom (evil-want-C-i-jump nil) :hook (conf-mode . turn-on-evil-mode) (prog-mode . turn-on-evil-mode) (text-mode . turn-on-evil-mode))
Quick comments
Evil-commentary is an Evil port of vim-commentary which adds key bindings to call Emacs’ built in comment-or-uncomment-region function with gcc to comment out the current line, or gc to comment out the current selection.
(use-package evil-commentary :ensure t :after evil :config (evil-commentary-mode 1))
Working with “surroundings”
Evil-surround is an Evil port of vim-surround which adds key bindings for dealing with “surroundings”, like replacing the quotes in a double-quoted string with single quotes by pressing cs"'.
(use-package evil-surround :ensure t :after evil :config (global-evil-surround-mode 1))
Completion
Vertico
Vertico is a vertical completion package that extends the built-in completion system, by replacing the UI for anything that uses completing-read.
(use-package vertico :ensure t :config (vertico-mode 1))
Orderless
Orderless is a completion style to allow for fuzzy searching in completion buffers.
It’s enabled by setting completion-styles to include orderless.
(use-package orderless :ensure t :custom completion-styles '(orderless basic))
Marginalia
Marginalia adds annotations to completion buffers to add context about the selectable options.
(use-package marginalia :ensure t :config (marginalia-mode 1))
Consult
The Consult package defines a list of enhancements for built-in navigation commands.
consult-bufferreplacesswitch-to-bufferconsult-project-bufferreplacesproject-switch-to-bufferconsult-goto-linereplacesgoto-lineconsult-ripgrepreplacesproject-find-regexp
Finally, add consult-ripgrep to project-switch-commands to how a Ripgrep option after invoking project-switch-project.
(use-package consult :ensure t :bind ("C-x b" . consult-buffer) ("C-x p b" . consult-project-buffer) ("M-g g" . consult-goto-line) ("M-g M-g" . consult-goto-line) ("C-x p g" . consult-ripgrep) :config (add-to-list 'project-switch-commands '(consult-ripgrep "Ripgrep")))
Corfu
Corfu is an in-buffer completion pop-up.
Install it and enable global-corfu-mode.
(use-package corfu :ensure t :init (global-corfu-mode 1))
Eshell
Aliases
Terminal emulation
Eat (short for “Emulate a Terminal”), is a terminal emulator for Emacs.
Outside of running the emulator by evaluating M-x eat, Eat can run inside Eshell by enabling eat-eshell-mode, which is bound to eshell-mode.
Also enable eat-eshell-visual-command-mode to use Eat instead of Term for visual commands inside Eshell.
Then, because visual commands can now run directly in Eshell, unset eshell-visual-commands.
To replace Emacs’ shell with Eat, bind it to C-x p s, and add it to project-switch-commands.
(use-package eat :ensure t :hook (eshell-load . eat-eshell-mode) (eshell-load . eat-eshell-visual-command-mode) :custom eshell-visual-commands nil :config (add-to-list 'project-switch-commands '(eat-project "Eat")) :bind ("C-x p s" . eat-project))
Setting $PATH
(use-package exec-path-from-shell :ensure t :config (exec-path-from-shell-initialize))
Command history
(use-package eshell-atuin :ensure t :after em-hist :config (eshell-atuin-mode) (keymap-set eshell-hist-mode-map "<up>" 'eshell-atuin-history) (keymap-unset eshell-hist-mode-map "<down>") (vertico-multiform-mode 1) (setq vertico-multiform-commands '((eshell-atuin-history reverse (vertico-sort-function . identity)))) :custom eshell-atuin-search-options nil)
Autoload dependencies
(use-package direnv :ensure t :config (direnv-mode 1))
Org
Note taking
Use Org-roam for note-taking, and place all notes in the ~/notes directory.
Then, enable org-roam-db-autosync-mode to keep the database in sync whenever a node is added or edited.
(use-package org-roam :ensure t :custom org-roam-directory "~/notes" :config (org-roam-db-autosync-mode 1))
Org-roam-ui is a frontend for org-roam, used to explore connections between notes.
(use-package org-roam-ui :ensure t :after org-roam)
Consult-org-roam is a collection of functions to search for notes using Consult.
Enable consult-org-roam-mode to use its functions to overwrite org-roam-node-read and org-roam-ref-read, producing previews when searching for notes with org-roam-node-find, for example.
(use-package consult-org-roam :ensure t :after org-roam :config (consult-org-roam-mode 1))
Exporting
The ox-gfm package adds a new Org exporter based on the built-in Markdown one which exports to “GitHub Flavored” Markdown, with features like “fenced” code blocks.
(use-package ox-gfm :ensure t)
The ox-md-title package adds document titles to Markdown exports, which are omitted by default.
Enable it globally by setting org-md-title and adding it by calling org-md-title-add.
(use-package ox-md-title :vc (:url "https://github.com/jeffkreeftmeijer/ox-md-title.el.git") :custom (org-md-title t) :config (org-md-title-add))
Htmlize is Org’s standard syntax highlighter, which uses Emacs’ own highlighting capabilities to output to HTML.
(use-package htmlize :ensure t)
Embedded code blocks
Org-babel executes and extracts code blocks from Org documents.
By default, Emacs only executes Emacs Lisp code blocks.
Add shell and ruby to also evaluate shell and Ruby scripts.
(setq org-babel-load-languages '((emacs-lisp . t) (shell . t) (ruby . t)))
Tools
Magit
Magit is the best user interface for working with git.
(use-package magit :ensure t)
Aside from magit itself, also load magit-extras, which adds magit to project-switch-commands.
By doing that, magit can be loaded in a project with C-x p m, or for a project with C-x p p m.
(use-package magit-extras)
Forge
(use-package forge :ensure t :after magit)
Pass
(use-package pass :ensure t)
Qutebrowser
(use-package exwm :ensure t) (use-package qutebrowser.el :vc (:url "https://github.com/lrustand/qutebrowser.el.git"))
Proced
Proced is Emacs’ process monitor user interface, used as a replacement for top and its derivatives.
Enable proced-enable-color-flag to make the process list easier to read.
(setq proced-enable-color-flag t)
Flyspell
Emacs’ flyspell mode checks spelling while writing, highlights possible errors, and suggests corrections.
Enable it in text-mode for writing, and in prog-mode for checking strings and comments.
(add-hook 'text-mode-hook 'flyspell-mode) (add-hook 'prog-mode-hook 'flyspell-prog-mode)
(use-package dirvish :ensure t :config (dirvish-override-dired-mode)) (use-package colorful-mode :config (global-colorful-mode 1))
Major modes
Beancount
Add beancount-mode and add beancount-language-server to the list of Eglot server programs.
(use-package beancount :ensure t)
(add-to-list 'eglot-server-programs
'(beancount-mode . ("beancount-language-server")))
CSV
(use-package csv-mode :ensure t)
Dockerfile
(use-package dockerfile-mode :ensure t)
Elixir
(use-package elixir-mode :ensure t)
Git attributes, config and ignore files
(use-package git-modes :ensure t)
JSON
(use-package json-mode :ensure t)
Markdown
Since there’s no separate major mode for MDX files, add the .mdx extension to markdown-mode.
(use-package markdown-mode :ensure t :mode "\\.mdx\\'")
Nix
(use-package nix-mode :ensure t)
Rust
(use-package rust-mode :ensure t)
vimrc
(use-package vimrc-mode :ensure t)
YAML
(use-package yaml-mode :ensure t)
Language servers & code formatting
Hook eglot-ensure into prog-mode, to try to connect to any running language server.
(use-package eglot :hook (prog-mode . eglot-ensure))
(use-package consult-eglot :ensure t)
(use-package format-all :ensure t :commands format-all-mode :hook (prog-mode . format-all-mode) (format-all-mode . format-all-ensure-formatter))