Protesilaos Stavrou
Philosopher. Polymath.

Emacs initialisation file (dotemacs)

An integral part of my custom desktop session on GNU/Linux

Created: 2019-08-15
Updated: 2020-01-21, 10:47 EET.
See this file's upstream git history. Everything is part of my dotfiles' repository.

Table of Contents

1 Overview

1.1 Canonical links to this document

1.2 What is this

The present document, referred to in the source code version as emacs-init.org, contains the bulk of my configurations for GNU Emacs. It is designed using principles of "literate programming": a combination of ordinary language and inline code blocks. Emacs knows how to parse this file properly so as to evaluate only the elisp ("Emacs Lisp") included herein. The rest is for humans to make sense of my additions and their underlying rationale.

Literate programming allows us to be more expressive and deliberate. Not only we can use typography to its maximum potential, but can also employ techniques such as internal links between sections. This makes the end product much better for end users, than a terse script.

In more practical terms, this document is written using org-mode. It contains all package configurations for my Emacs setup. To actually work, it needs to be initialised from another file, that only covers the absolute essentials.

1.2.1 Contents of my init.el

The emacs-init.org is actually loaded from an other file, named init.el as per the Emacs convention. Mine is designed to add the community-driven MELPA archive to the list of package repositories, configure use-package and then load the file with my configurations (i.e. the present document).

For reference, these were the contents of my init.el prior to Emacs 27.1.

(require 'package)
(setq package-enable-at-startup nil)
(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/"))
(unless package--initialized (package-initialize))

(require 'org)
(org-babel-load-file (expand-file-name "~/.emacs.d/emacs-init.org"))

Whereas in Emacs 27.1 and onward, they are modified thus:

(require 'package)

(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/"))

;; Initialise the packages, avoiding a re-initialisation.
(unless (bound-and-true-p package--initialized)
  (setq package-enable-at-startup nil)
  (package-initialize))

;; Make sure `use-package' is available.
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

;; Configure `use-package' prior to loading it.
(eval-and-compile
  (setq use-package-always-ensure nil)
  (setq use-package-always-defer nil)
  (setq use-package-always-demand nil)
  (setq use-package-expand-minimally nil)
  (setq use-package-enable-imenu-support t))

(eval-when-compile
  (require 'use-package))

(require 'org)
(org-babel-load-file (expand-file-name "~/.emacs.d/emacs-init.org"))

Starting with Emacs 27.1, an early-init.el is now required to control things with greater precision. Its code is as follows:

;; Do not initialise the package manager.  This is done in `init.el'.
(setq package-enable-at-startup nil)

;; Do not resize the frame at this early stage.
(setq frame-inhibit-implied-resize t)

These adjustments are of paramount importance due to changes in the way Emacs initialises the package manager. Prior to Emacs 27.1, the init.el was supposed to handle that task by means of calling package-initialize. Whereas for Emacs 27.1, the default behaviour is to start the package manager before loading the user's init file. This can create unexpected results with regard to how existing configuration files are parsed—or at least that was my experience with certain settings not being parsed consistently (was not able to reproduce it reliably). I prefer the old behaviour so I simply tell the early-init.el to defer the process of initialising the package manager to when init.el is evaluated.

1.2.2 About `use-package'

This is a tool that streamlines the configuration of packages. It handles everything from assigning key bindings, setting the value of customisation options, writing hooks, declaring a package as a dependency for another, and so on.

Though it might not be readily apparent, a "package" in Emacs parlance is any elisp file that is evaluated by Emacs. This includes libraries that are shipped with the upstream distribution as well as code that comes from other sources.

Unlike a typical extensible program, there is no real distinction between native Emacs code and the one that comes from third parties. There is no externally-facing limited set of features that other tools can plug into. Emacs is an interpreter of lisp (Emacs Lisp), meaning that any elisp is evaluated in real time, making Emacs behave in accordance with it.

I have an hour long presentation about switching to Emacs, where this and other topics are discussed in greater detail. It is good to understand the context in order to appreciate the differences between the various use-package declarations documented herein.

The two main functions of use-package:

  1. To install and set up external packages. Those are denoted by the inclusion of :ensure t.
  2. To configure default packages. No :ensure t for them.

With use-package we can improve the start-up performance of Emacs in a few fairly simple ways. Whenever a command is bound to a key it is configured to be loaded only once invoked. Otherwise we can specify which functions should be autoloaded by means of the :commands keyword.

Furthermore, and if absolutely necessary, I define all variables that are supposed to be immutable with the :custom keyword. This writes them to the custom.el that I specify further below. Consider that to be the exception, as all minor modes, custom functions, or other configurations are enabled under the :config keyword. The activation of a mode should always be the very last thing, once all variables have been set.

The following snippet of elisp sets up and configures use-package to my liking. It is already referenced in the previous section concerning the contents of my init.el. This is due to changes in how Emacs 27.1 starts up. Whereas before I used to configure use-package from inside this document.

;; Setup `use-package'
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

;; Should set before loading `use-package'
(eval-and-compile
  (setq use-package-always-ensure nil)
  (setq use-package-always-defer nil)
  (setq use-package-always-demand nil)
  (setq use-package-expand-minimally nil)
  (setq use-package-enable-imenu-support t))

(eval-when-compile
  (require 'use-package))

Settings that do not have a corresponding package are declared using the special use-package emacs notation.

1.2.3 About the source code version of this document

In the org-mode version of this document, I make sure that the above-referenced code blocks are not declared as an emacs-lisp source but rather as mere examples, so they are not accidentally parsed by the actual setup.

Actual code blocks are wrapped between #+begin_src and #+end_src tags (not visible in the website version of this page). For Emacs 27.1, such templates can be quickly inserted with C-c C-, (this works both for empty blocks and active regions). For more on the matter, refer to Org's section further below.

As for the various settings included herein, you can learn even more about them by using Emacs' built-in documentation (great for discovering new features and pieces of functionality).

1.3 Where I run Emacs

My OS is mostly Debian 10 'buster' (the current stable), though I also work on a Void Linux machine. My Emacs is built from source, using the emacs-27 branch. Normally I would be using the packages provided by my GNU/Linux distribution but am opting for the manual method instead in order to make sure my Modus themes are compatible with changes upstream.

I do not optimise for portability across different versions or operating systems. I do, nonetheless, provide inline comments when a certain option or configuration is specific to a yet-to-be-released version of Emacs.

1.4 Note about my methodology

I choose external packages only after I try the defaults. The idea is to take things slowly and learn along the way, while consulting the official manual and relevant documentation. This is necessary to make an informed decision about what is actually missing and what could be improved further.

External packages that I do use are either a clear upgrade over the defaults or otherwise extend the functionality of what is already available. For example, magit is the superior choice for working with git, while Ivy offers a richer experience than that of either ido-mode or icomplete.

Though a former Vim user, I decided not to use evil-mode or any kind of Vi emulation. I wanted to do things differently in order to ultimately set on the best approach going forward. I have eventually settled on a system that builds on top of the "Emacs way" to key bindings. I believe that a mnemonics-based set of keys is easier to memorise and to expand considerably (also see my use of the Super key).

1.5 Note about the use of the Super key

Some sections of this document assign functions to s-KEY. The lower case s represents the "Super" key, also known as the "Windows key" on some commercial keyboards. In most cases, those key bindings are alternative ways of invoking common commands that are by default bound to otherwise awkward key chords. The original keys will continue to function as intended (for example, C-x o is also s-o).

Note that your choice of desktop environment might render some of these useless. The DE will simply intercept the key chord before it is ever sent to Emacs. For example, GNOME has a hidden key mapping to s-p, which does something with monitors (last time I checked on GNOME 3.30). Such bindings are scattered throughout the config database that is normally accessed with gsettings on the command line or the graphical dconf-editor.

Similarly, a tiling window manager that binds practically all of its motions to Super, will cause you trouble. Personally, I have enabled the Hyper key and am now using it as an extra modifier for controlling my bespoke BSPWM setup (comprehensive documentation in this commit).

1.6 Note about the source file

If you are reading the source code for this file (available in my dotfiles repo on Gitlab), you will notice some metadata tags specific to org-mode below each heading. These are generated by the functions that are defined in the package declaration for org-id. The idea is to keep anchor tags consistent when generating a new HTML version of this document.

This metadata also makes it possible to create immutable internal links, whenever a reference is needed. To create such links, you can use C-c l to capture the unique ID of the current section and then C-c C-l to create a link (the former is defined in the Org package declaration—this is an internal link in action).

1.7 COPYING

Copyright (c) 2019-2020 Protesilaos Stavrou <info@protesilaos.com>

This file is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this file. If not, see http://www.gnu.org/licenses/.

2 Base settings

This section contains the relatively few configurations that are needed prior to the setup of everything else.

2.1 Disable GUI components

Overview of these settings:

  • I normally use the GTK (GUI) variant of Emacs. I prefer not to have any of the elements that come with it. This keeps the window clean. The only "interface" component that remains in place is the mode line, which is not part of the GUI toolkit anyway…
  • The start-up screen that offers an overview of GNU Emacs is also disabled. It is useful for beginners, but is rendered obsolete once you familiarise yourself with the essentials.
  • The pair of key bindings that involve z minimise the Emacs frame. This is technically an interface action, in that it assumes my window manager has a minimise function or that I want to have such a motion inside of Emacs. Disable them.
  • Also disable the "hello file" function, because it crashes Emacs. I assume this has to do with font rendering and missing font files, as I experienced similar issues on various terminal emulators.
(use-package emacs
  :config
  (setq use-file-dialog nil)
  (setq use-dialog-box nil)
  (setq inhibit-splash-screen t)
  (global-unset-key (kbd "C-z"))
  (global-unset-key (kbd "C-x C-z"))
  (global-unset-key (kbd "C-h h"))
  :hook (after-init . (lambda ()
                        (menu-bar-mode -1)
                        (tool-bar-mode -1)
                        (scroll-bar-mode -1))))

2.2 Edit modeline "lighters"

In Emacs speak, the name of a mode present at the modeline is called a "lighter". For example, Flyspell's lighter is "Fly".

With this package we can edit or rename lighters, or altogether hide them (the information is still available when running C-h m). Furthermore, the functionality can be integrated in every package declaration of use-package: you will see a :delight tag.

(use-package delight
  :ensure t
  :after use-package)

2.3 Custom.el

When you install a package or use the various customisation interfaces to tweak things to your liking, Emacs will append a piece of elisp to your init file. I prefer to have that stored in a separate file.

(use-package cus-edit
  :config
  (setq custom-file "~/.emacs.d/custom.el")

  (unless (file-exists-p custom-file)
    (write-region "" nil custom-file))

  (load custom-file))

2.4 Base typeface configurations

While we can always specify a font family for each Emacs "face" (i.e. customisable styles of code/interface constructs), it still is a good idea to establish a baseline for what typeface settings should be applicable by default. In general, I like constistent typography and would only mix styles when it is absolutely necessary to convey a particular meaning.

2.4.1 Primary font settings

I am using a variant of the Iosevka typeface, ever since the release of version 3.0.0-alpha.2, which addressed some the issues I had with previous iterations.

Iosevka has true italics and supports both scripts I am interested in: Latin and Greek. It also offers a comprehensive list of stylistic alternatives as well as a broad range of weight sizes.

On Debian and Void Linux (and probably all other distros) per-user fonts are read from ~/.local/share/fonts/.

Each font declaration can accept several fontconfig parameters, as shown in prot/fixed-pitch-params. Read the relevant spec for further details. These can be exceptions to environment-wide rules that you specify for fontconfig, at the user or system level. Refer to my dotfiles for the relevant fontconfig settings, as Iosevka is only meant to be used inside of Emacs, while I keep Hack as the generic monospaced font.

(use-package emacs
  :config
  (setq x-underline-at-descent-line t)
  (setq underline-minimum-offset 1)

  (defconst prot/fixed-pitch-font "Iosevka Slab Extended"
    "The default fixed-pitch typeface.")

  (defconst prot/fixed-pitch-font-alt "Iosevka Slab"
    "The default fixed-pitch typeface (alt).")

  (defconst prot/fixed-pitch-params ":hintstyle=hintfull"
    "Fontconfig parameters for the fixed-pitch typeface.")

  (defun prot/font-family-size (family size)
    "Set frame font to FAMILY at SIZE."
    (set-frame-font
     (concat family "-" (number-to-string size) prot/fixed-pitch-params) t t))

  (defun prot/laptop-fonts ()
    "Pass desired argument to `prot/font-sizes' for use on my
small laptop monitor."
    (interactive)
    (when window-system
	  (prot/font-family-size prot/fixed-pitch-font-alt 10.75)))

  (defun prot/desktop-fonts ()
    "Pass desired argument to `prot/font-sizes' for use on my
larger desktop monitor."
    (interactive)
    (when window-system
	  (prot/font-family-size prot/fixed-pitch-font 10.75)))

  (defun prot/fonts-per-monitor ()
    "Choose between `prot/laptop-fonts' and `prot/desktop-fonts'
based on the width of the monitor.  The calculation is based on
the maximum width of my laptop's screen."
    (interactive)
    (when window-system
	  (if (<= (display-pixel-width) 1366)
	      (prot/laptop-fonts)
	    (prot/desktop-fonts))))

  :hook (after-init . prot/fonts-per-monitor))
2.4.1.1 Typeface suitability test

Here is a simple test I have come up with to make an initial assessment of the overall quality of the font: can you discern the character at a quick glance? If yes, your choice of font is good prima facie, else search for something else.

Note that this test is not perfect, since many typefaces fall short in less obvious ways, such as the space between the characters. Also note that the website version of this document may not accurately represent the typeface I am using.

()[]{}<>«»‹›
6bB8&0ODdo
1tiIlL|
!ij
5$Ss
7Zz
gqp
nmMN
uvvwWuuw
x×X
.,·°;:¡!¿?
:;
`'
‘’
''"
'
"
“”
-~≈=_.…

Sample character set
Check for monospacing and Greek glyphs

ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
1234567890#%^*
ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
αβγδεζηθικλμνξοπρστυφχψω

3 Selection candidates and search methods

3.1 Completion framework and extras

As discussed in my video about Emacs' buffer and window management, the optimal way of using Emacs is through searching and narrowing selection candidates. Spend less time worrying about where things are on the screen and more on how fast you can bring them into focus.

3.1.1 Ivy/Counsel/Swiper plus filtering and scoring

This is a suite of tools that enhance several aspects of the Emacs experience. Basically we have:

  • ivy is the mechanism that handles all selection lists, narrowing them down using a variety of possible builders (regular expressions of flexible matching). It also provides a base interface for any function that needs to receive input based on a list of candidates.
  • counsel provides a superset of functions for navigating the file system, switching buffers, etc. that expand on the basic features supported by Ivy. For instance, switching buffers with Counsel offers a preview of their contents in the window, whereas regular Ivy does not.
  • swiper is a tool for performing searches, powered by Ivy, all while presenting a preview of the results.
3.1.1.1 Configurations for Ivy

A few highlights of my configurations in the subsequent code block:

  • ivy-height-alist governs the maximum width of the Ivy window to 1/4 of the viewport. I prefer this over an absolute number as I work on monitors with varying dimensions (though note that ivy-posframe will override it, if enabled).
  • ivy-virtual-buffers populates buffer-switching lists with items from the recentf utility. In practice, a recently killed buffer can still be accessed from {ivy,counsel}-switch-buffer as if the kill had never occurred.
  • ivy-re-builders-list allows us to specify the algorithm for matching candidates. Unless declared otherwise, I am using regexp matching by default.
  • ivy-initial-inputs-alist adds some initial input to the commands we specify. I define it to prepend a caret sign (^) by default, unless explicitly stated not to include anything.
  • ivy-use-selectable-prompt solves the problem of trying to create a new file system path that shares a common name with an existing item. Press C-p and proceed without further conflicts.
  • The various ivy-set-occur are meant to specify a function for producing an appropriate buffer when running ivy-occur (see table right below).

And here are some 'hidden' key bindings for making the most out of Ivy (find more in the official manual).

Key Function Description
M-o ivy-dispatching-done Show actions for current match.
C-c C-o ivy-occur Place the list in a standalone buffer.
C-M-m ivy-call Run command, keep minibuffer open.
M-i ivy-insert-current Insert match in the prompt.
M-j ivy-yank-word Put word at point in the minibuffer prompt.
S-SPC ivy-restrict-to-matches Restrict list to prompt (and search anew).
C-SPC ivy-restrict-to-matches My alias for the above.

Adding to the table above, you can always use the universal M-n and M-p to cycle through the history of entries.

With those granted, make sure to inspect the entirety of my dotemacs' section on Selection candidates and search methods, as the following package declaration is but a piece of a greater whole.

(use-package ivy
  :ensure t
  :delight
  :config
  (setq ivy-count-format "(%d/%d) ")
  (setq ivy-height-alist '((t lambda (_caller) (/ (window-height) 4))))
  (setq ivy-use-virtual-buffers t)
  (setq ivy-wrap nil)
  (setq ivy-re-builders-alist
        '((counsel-M-x . ivy--regex-fuzzy)
          (ivy-switch-buffer . ivy--regex-fuzzy)
          (ivy-switch-buffer-other-window . ivy--regex-fuzzy)
          (counsel-rg . ivy--regex-or-literal)
          (t . ivy--regex-plus)))
  (setq ivy-display-style 'fancy)
  (setq ivy-use-selectable-prompt t)
  (setq ivy-fixed-height-minibuffer nil)
  (setq ivy-initial-inputs-alist
        '((counsel-M-x . "^")
          (ivy-switch-buffer . "^")
          (ivy-switch-buffer-other-window . "^")
          (counsel-describe-function . "^")
          (counsel-describe-variable . "^")
          (t . "")))

  (ivy-set-occur 'counsel-fzf 'counsel-fzf-occur)
  (ivy-set-occur 'counsel-rg 'counsel-ag-occur)
  (ivy-set-occur 'ivy-switch-buffer 'ivy-switch-buffer-occur)
  (ivy-set-occur 'swiper 'swiper-occur)
  (ivy-set-occur 'swiper-isearch 'swiper-occur)
  (ivy-set-occur 'swiper-multi 'counsel-ag-occur)
  :hook ((after-init . ivy-mode)
         (ivy-occur-mode . hl-line-mode))
  :bind (("<s-up>" . ivy-push-view)
		 ("<s-down>" . ivy-switch-view)
         ("C-S-r" . ivy-resume)
         :map ivy-occur-mode-map
         ("f" . forward-char)
         ("b" . backward-char)
         ("n" . ivy-occur-next-line)
         ("p" . ivy-occur-previous-line)
         ("<C-return>" . ivy-occur-press)))
3.1.1.2 DEPRECATED AMX (M-x history and matches)

UPDATE 2019-12-06: I am deprecating amx in favour of prescient. See Prescient's package declaration. This section will be removed in the near future.

The amx package enhances the minibuffer experience, by tracking the history of commands and ranking them automagically. I once thought it was only meant to be used with Ido, but upon closer inspection I realised otherwise. Great! Counsel leverages AMX's features without any further intervention.

(use-package amx
  :ensure t
  :disabled
  :after ivy
  :config
  (setq amx-backend 'auto)
  (setq amx-save-file "~/.emacs.d/amx-items")
  (setq amx-history-length 50)
  (setq amx-show-key-bindings nil)
  :hook (after-init . amx-mode))
3.1.1.3 Prescient (sort, filter results)

This tool provides a filtering and scoring system that can interface with Ivy. It is a replacement for amx.

Filtering concerns the way matches are determined. It is possible to select candidates by applying the search terms in a number of ways, such as a literal interpretation of the character string, a regular expression, a set of ordered wildcards (fuzzy match), or an initialism. The filters can be applied on a per function basis, though I that breaks the way Ivy highlights things.

The scoring system is based on the frequency and recency of commands. This is extremely valuable, as it will always surface to the top the commands you most likely need. Running M-x is now akin to starting a key chord chain (for example, M-x b will give me bongo as my first match, which is exactly what I need). It eliminates the need for increasingly arcane key bindings—use keys only for the most frequently-used commands.

The value of ivy-prescient-sort-commands defines functions that are exempt from all the sorting operations performed by this tool. I think that, at the very least, swiper should always be excluded from any kind of sorting mechanism, because it already orders its results based on the line number. We should not break that expectation. All other commands are just a matter of preference, where I simply find the generic sorting to offer a better first impression (does not really matter, since I will type something anyhow).

(use-package prescient
  :ensure t
  :config
  (setq prescient-history-length 200)
  (setq prescient-save-file "~/.emacs.d/prescient-items")
  (setq prescient-filter-method '(literal regexp))
  (prescient-persist-mode 1))

(use-package ivy-prescient
  :ensure t
  :after (prescient ivy)
  :config
  (setq ivy-prescient-sort-commands
        '(:not counsel-grep
               counsel-rg
               counsel-switch-buffer
               ivy-switch-buffer
               swiper
               swiper-multi))
  (setq ivy-prescient-retain-classic-highlighting t)
  (setq ivy-prescient-enable-filtering nil)
  (setq ivy-prescient-enable-sorting t)
  (ivy-prescient-mode 1))
3.1.1.4 Counsel configurations and key bindings

A few things to consider about the settings in this sub-section:

  • With regard to key bindings, notice that Counsel's implementation for switching buffers will preview the currently matched item. This is particularly distracting when running it for the current window. For that case I use the generic Ivy method. I am okay with Counsel's approach when operating on the other window.
  • As for counsel-yank-pop-separator, its value is a series of em dashes with a newline character at either end. This creates a nice separator line when browsing the kill ring (counsel-yank-pop).
  • The function counsel-rg provides an interface to an external program called ripgrep. This is a great alternative to grep. For me the main selling point is its improved speed. The key chord for it is similar to the built-in occur.
  • The key chord for counsel-git-grep is also inspired by occur. This one will perform a search in the current git repository.
  • I have a few custom functions for finding files using a fluid workflow from fzf to ripgrep (and vice versa). Better check my video on Fuzzy search with “Ivy actions” for FZF and RIPGREP (2019-12-15).
  • For the prot/counsel-fzf-ace-window function, I recommend you refer to my package declaration for ace-window, in order to see what commands I want to use.

Also make sure to study all the other package declarations in the Selection candidates and search methods section, to appreciate their interplay and the full extent of my customisations.

(use-package counsel
  :ensure t
  :after ivy
  :config
  (setq counsel-yank-pop-preselect-last t)
  (setq counsel-yank-pop-separator "\n—————————\n")
  (setq counsel-rg-base-command
        "rg -SHn --no-heading --color never --no-follow --hidden %s")
  (setq counsel-find-file-occur-cmd; TODO Simplify this
        "ls -a | grep -i -E '%s' | tr '\\n' '\\0' | xargs -0 ls -d --group-directories-first")

  (defun prot/counsel-fzf-rg-files (&optional input dir)
    "Run `fzf' in tandem with `ripgrep' to find files in the
present directory.  If invoked from inside a version-controlled
repository, then the corresponding root is used instead."
    (interactive)
    (let* ((process-environment
            (cons (concat "FZF_DEFAULT_COMMAND=rg -Sn --color never --files --no-follow --hidden")
                  process-environment))
           (vc (vc-root-dir)))
      (if dir
          (counsel-fzf input dir)
        (if (eq vc nil)
            (counsel-fzf input default-directory)
          (counsel-fzf input vc)))))

  (defun prot/counsel-fzf-dir (arg)
    "Specify root directory for `counsel-fzf'."
    (prot/counsel-fzf-rg-files ivy-text
                               (read-directory-name
                                (concat (car (split-string counsel-fzf-cmd))
                                        " in directory: "))))

  (defun prot/counsel-rg-dir (arg)
    "Specify root directory for `counsel-rg'."
    (let ((current-prefix-arg '(4)))
      (counsel-rg ivy-text nil "")))

  ;; TODO generalise for all relevant file/buffer counsel-*?
  (defun prot/counsel-fzf-ace-window (arg)
    "Use `ace-window' on `prot/counsel-fzf-rg-files' candidate."
    (ace-window t)
    (let ((default-directory (if (eq (vc-root-dir) nil)
                                 counsel--fzf-dir
                               (vc-root-dir))))
      (if (> (length (aw-window-list)) 1)
          (find-file arg)
        (find-file-other-window arg))
      (balance-windows (current-buffer))))

  ;; Pass functions as appropriate Ivy actions (accessed via M-o)
  (ivy-add-actions
   'counsel-fzf
   '(("r" prot/counsel-fzf-dir "change root directory")
     ("g" prot/counsel-rg-dir "use ripgrep in root directory")
     ("a" prot/counsel-fzf-ace-window "ace-window switch")))

  (ivy-add-actions
   'counsel-rg
   '(("r" prot/counsel-rg-dir "change root directory")
     ("z" prot/counsel-fzf-dir "find file with fzf in root directory")))

  (ivy-add-actions
   'counsel-find-file
   '(("g" prot/counsel-rg-dir "use ripgrep in root directory")
     ("z" prot/counsel-fzf-dir "find file with fzf in root directory")))

  ;; Remove commands that only work with key bindings
  (put 'counsel-find-symbol 'no-counsel-M-x t)
  :bind (("M-x" . counsel-M-x)
         ("C-x C-f" . counsel-find-file)
         ("s-f" . counsel-find-file)
         ("s-F" . find-file-other-window)
         ("C-x b" . ivy-switch-buffer)
         ("s-b" . ivy-switch-buffer)
         ("C-x B" . counsel-switch-buffer-other-window)
         ("s-B" . counsel-switch-buffer-other-window)
         ("C-x d" . counsel-dired)
         ("s-d" . counsel-dired)
         ("s-D" . dired-other-window)
         ("C-x C-r" . counsel-recentf)
         ("s-r" . counsel-recentf)
         ("s-y" . counsel-yank-pop)
         ("C-h f" . counsel-describe-function)
         ("C-h v" . counsel-describe-variable)
         ("M-s r" . counsel-rg)
         ("M-s g" . counsel-git-grep)
         ("M-s l" . counsel-find-library)
         ("M-s z" . prot/counsel-fzf-rg-files)
         :map ivy-minibuffer-map
         ("C-r" . counsel-minibuffer-history)
         ("s-y" . ivy-next-line)        ; Avoid 2× `counsel-yank-pop'
         ("C-SPC" . ivy-restrict-to-matches)))
3.1.1.4.1 Counsel and projectile

These are a set of commands for interacting with projects, by leveraging the projectile library. A "project" is a version-controlled directory or one that is manually declared as such by an empty .projectile file at its root. Either way, projects are placed inside a filesystem path, which I declare in projectile-project-search-path (this could actually be a list).

The disabled :delight statement customises the minor mode's presentation to only show the project's name. For non-project buffers, a dash appears instead. However, it is disabled in favour of not showing anything at all in the minor mode list. A dedicated indicator for the built-in version control status is used in my custom mode line instead. This is enough for my use-case.

I only really need a handful of commands from this setup, namely the ability to switch between buffers or directories in a project and to switch between projects. For the rest, I am already covered by what I have defined in the Counsel section above.

In case you want to use counsel-projectile to its full extent, then enable and configure :bind-keymap. That key binding is just the common prefix to a list of key chords. You can learn about them with M-s p C-h (so append C-h). Note though that the keys I define below do not require such a prefix. They call the commands I need directly.

The counsel-projectile-switch-project is configured to run the fourth action of those available, which configures it to switch to a project and open it inside of dired, rather than ask for a file to open.

While the ivy-initial-inputs-alist extends the variable that is specified in the Ivy section.

(use-package projectile
  :ensure t
  ;; :delight '(:eval (concat " " (projectile-project-name)))
  :delight
  :config
  (setq projectile-project-search-path '("~/Git/Projects/"))
  (setq projectile-indexing-method 'alien)
  (setq projectile-enable-caching t)
  (setq projectile-completion-system 'ivy))

(use-package counsel-projectile
  :ensure t
  :config
  (add-to-list 'ivy-initial-inputs-alist '(counsel-projectile-switch-project . ""))
  :hook (after-init . counsel-projectile-mode)
  ;; :bind-keymap ("M-s p" . projectile-command-map)
  :bind (("M-s b" . counsel-projectile-switch-to-buffer)
         ("M-s d" . counsel-projectile-find-dir)
         ("M-s p" . (lambda ()
                      (interactive)
                      (counsel-projectile-switch-project 4)))))
3.1.1.5 Swiper commands and settings

This is the search tool that is powered by Ivy. I use it to get an overview of the matching candidates when performing a more complex search. It is not intended as a drop-in replacement for isearch (see section on Isearch), especially since the latter is better for recording macros and jumping to visible sections in the buffer.

Given that Swiper is related to Ivy, do not forget to review the entire section on Selection candidates and search methods.

(use-package swiper
  :ensure t
  :after ivy
  :config
  (setq swiper-action-recenter t)
  (setq swiper-goto-start-of-match t)
  (setq swiper-include-line-number-in-search t)
  :bind (("C-S-s" . swiper)
         ("M-s s" . swiper-multi)
         ("M-s w" . swiper-thing-at-point)))

3.1.2 Ivy extensions

These tools build on the foundation of Ivy and friends.

3.1.2.1 Ivy rich

With this package we can make good use of the plenty of empty space left by Ivy's default presentation of its items. It enhances several commands, providing each of them with additional information that is pertinent to the task at hand. For example, M-x contains function descriptions, while the buffer list includes information about the major mode and file system path of the items.

The ivy-rich-path-style offers an abbreviation of the file system path that an item is referring to. So far, the only noticeable difference over an absolute value is the use of the tilde (~) instead of /home/USER/ in the buffer list. Whereas recentf continues to display absolute paths. Will need to test this further…

(use-package ivy-rich
  :ensure t
  :config
  (setq ivy-rich-path-style 'abbreviate)

  (setcdr (assq t ivy-format-functions-alist)
          #'ivy-format-function-line)
  :hook (after-init . ivy-rich-mode))
3.1.2.2 Ivy posframe

This package allows us to reposition Ivy's window anywhere inside of the Emacs frame. Furthermore, it is possible to use this feature on a per-command basis, all while assigning a different height to each list.

The ivy-posframe-parameters can be found in the source code of the main library. Do M-x find-library RET posframe RET where RET means to hit the Return key in order to proceed. In one of the parameters I define a font that is different from what I normally use, just as a proof of concept (see section on fonts).

(use-package ivy-posframe
  :ensure t
  :delight
  :config
  (setq ivy-posframe-parameters
        '((left-fringe . 2)
          (right-fringe . 2)
          (internal-border-width . 2)
          (font . "Iosevka-10.75:hintstyle=hintfull")))
  (setq ivy-posframe-height-alist
        '((swiper . 15)
          (swiper-isearch . 15)
          (t . 10)))
  (setq ivy-posframe-display-functions-alist
        '((complete-symbol . ivy-posframe-display-at-point)
          (swiper . nil)
          (swiper-isearch . nil)
          (t . ivy-posframe-display-at-frame-center)))
  :hook (after-init . ivy-posframe-mode))

3.2 Configurations or extensions for built-in commands

These are meant to enhance the functionality of tools that are already shipped with Emacs.

3.2.1 Isearch enhancements

I use isearch all the time for quick navigation, either to a visible part of the buffer or to some specific string I am aware of. It also is essential when used in the context of a keyboard macro. For me Swiper, which I also declare in this document, can never be optimal for those uses.

Run C-h k C-s to get an awesome help menu with all the extra keys you can use with isearch. These are the ones I use the most:

Key chord Description
C-s C-w Search char or word at point
M-s . Similar, but broader match
M-s o Run `occur' on regexp
M-s h r Highlight regexp
M-s h u Undo the highlight
C-s M-r Toggle regexp search
M-% Run `query-replace'
C-M-% `query-replace-regexp'

Many commands can be invoked while running isearch to operate on the current match. For example, C-s SEARCH M-s o will produce an "Occur" buffer with the contents of the search terms. Absolutely great!

With regard to the replace commands, note that you can use them on the active region. Furthermore, you do not need to confirm each action, but can instead type ! to answer "yes" to all possible replacement. Better only use this while having already limited the results to the active region, to some specialised editable buffer like the one of occur, or by using Emacs' narrowing techniques, such as narrow-to-region.

In the package declaration below, the combined effect of the variables for whitespace is a valuable hack: typing a space is the same as inserting a wildcard, which is much more useful as far as I am concerned. It basically behaves the same way as Swiper: a single space represents a wildcard that matches items in a non-greedy fashion. This concerns regular searches (the standard C-s and C-r). The regexp functions C-M-s and C-M-r remain in tact. You can toggle whitespace matching behaviour while performing a search, with M-s SPC (revert back to just literal spaces).

Now on to some custom functions. Here is an overview of what goes into this package declaration.

Mark isearch match
Replaces the default mark command following a successful search. I prefer to mark the match. This can be then used to insert multiple cursors (if you are using it), kill the region, etc. Besides, it is always possible to mark a region from point to search string by running C-x C-x following a successful search.
Move to opposite end
Isearch places the point at either the beginning or the end of the match, depending on the direction it is moving in. For single words or balanced expressions this is not an issue because you can always confirm a search by using a motion key (so, for example, move to the end of the matching word with M-f). There are, however, matches that are not limited to such boundaries. For those cases moving to the opposite end might require multiple key presses, which is bad when trying to record an efficient keyboard macro. prot/isearch-other-end addresses the issue. It is bound to C-RET while running a successful search. The direct inspiration is this forum answer. Note though that you can achieve the same result by changing the direction the search is moving towards with C-s or C-r (though I still prefer my minor addition).
Delete non-match
The built-in method to remove the entirety of a mismatched input is to hit C-g following a failed search. This keeps the valid part and allows you to continue searching. However, I find that the choice of key binding can prove problematic, since C-g also exits a standard/successful search. As such, the simple function prot/isearch-abort is designed to remove the entirety of a mismatch, just by hitting backspace (aka DEL). For valid searches, backspace functions exactly as expected, deleting one character at a time. Note, though, that it is no longer possible to delete part of a failed search, just by hitting backspace: you can still rely on C-M-d for that (or edit the input with M-e).
Replace symbol at point
Combine the built-in functions of isearch-forward-symbol-at-point and isearch-query-replace-regexp into a single command that is bound to the key chord M-s %. Simple and super effective (pro tip: hit ! to answer "yes" to all possible matches, which is possible in all cases where Emacs asks you for multiple confirmations).

The variables about the lazy count that are commented as "Emacs 27.1" effectively supersede the functionality of anzu, a package I once used.

(use-package isearch
  :config
  (setq search-whitespace-regexp ".*?")
  (setq search-highlight t)
  (setq isearch-lax-whitespace t)
  (setq isearch-regexp-lax-whitespace nil)
  (setq isearch-lazy-highlight t)
  ;; All of the following variables were introduced in Emacs 27.1.
  (setq isearch-lazy-count t)
  (setq lazy-count-prefix-format "(%s/%s) ")
  (setq lazy-count-suffix-format nil)
  (setq isearch-yank-on-move 'shift)
  (setq isearch-allow-scroll 'unlimited)

  (defun prot/isearch-mark-and-exit ()
    "Mark the current search string and exit the search."
    (interactive)
    (push-mark isearch-other-end t 'activate)
    (setq deactivate-mark nil)
    (isearch-done))

  (defun prot/isearch-other-end ()
    "End current search in the opposite side of the match.
Particularly useful when the match does not fall within the
confines of word boundaries (e.g. multiple words)."
    (interactive)
    (isearch-done)
    (when isearch-other-end
      (goto-char isearch-other-end)))

  (defun prot/isearch-abort ()
    "Remove non-matching `isearch' input, reverting to previous
successful search and continuing with the search.

This is a modified variant of the original `isearch-abort',
mapped to C-g which will remove the failed match if any and only
afterwards exit the search altogether."
    (interactive)
    (discard-input)
    (while (or (not isearch-success) isearch-error)
      (isearch-pop-state))
    (isearch-update))

  (defun prot/isearch-query-replace-symbol-at-point ()
    "Run `query-replace-regexp' for the symbol at point."
    (interactive)
    (isearch-forward-symbol-at-point)
    (isearch-query-replace-regexp))

  :bind (("M-s M-o" . multi-occur)
         ("M-s %" . prot/isearch-query-replace-symbol-at-point)
         :map minibuffer-local-isearch-map
         ("M-/" . isearch-complete-edit)
         :map isearch-mode-map
         ("M-/" . isearch-complete)
         ("C-SPC" . prot/isearch-mark-and-exit)
         ("DEL" . prot/isearch-abort)
         ("<C-return>" . prot/isearch-other-end)))

3.2.2 wgrep (writable grep)

With wgrep we can directly edit the results of a grep and save the changes to all affected buffers. In principle, this is the same as what the built-in occur offers. We can use it to operate on a list of matches by leveraging the full power of Emacs' editing capabilities (e.g. keyboard macros, multiple cursors…).

(use-package wgrep
  :ensure t
  :config
  (setq wgrep-auto-save-buffer t)
  (setq wgrep-change-readonly-file t))

4 Directory, buffer, window management

4.1 Dired (directory editor, file manager)

dired is a built-in tool that performs file management operations. It is simply superb. I use it daily. Check my video on my Dired tweaks and refinements.

4.1.1 Base settings

The options here are meant to do the following:

  • Copy and delete recursively. Do not ask about it.
  • Search only file names while point is there, else the rest (useful when using the detailed view).
  • Deletion sends items to the system's Trash, making it safer than the standard rm.
  • Prettify output. Sort directories first. Show dotfiles first. Omit implicit directories (the single and double dots). Use human-readable size units. There are also options for tweaking the behaviour of find-name-dired, in the same spirit. To learn everything about these switches, you need to read the manpage of ls. You can do it with M-x man RET ls.
  • Hide all the verbose details by default (permissions, size, etc.). These can easily be toggled on using the left parenthesis ( inside a dired buffer. Also enable highlighting of the current line, which makes it even easier to spot the current item (I do not enable this globally, because I only want it for per-line interfaces, such as Dired's, but not for per-character ones, such as text editing).
  • While having two dired buffers side-by-side, the rename and copy operations of one are easily propagated to the other. Dired is smart about your intentions and uses the adjacent Dired buffer's path as a prefix when performing such actions.
  • For Emacs 27.1, Dired can automatically create destination directories for its copy and remove operations. So you can, for example, rename file to /non-existent-path/file and you will get what you want right away.
  • For Emacs 27.1, renaming a file of a version-controlled repository (git) will be done using the appropriate VC mechanism.
  • Let the relevant find commands use case-insensitive names.
  • Enable asynchronous mode. This is subject to change, as I need to test it a bit more.

Note that dired-listing-switches and find-ls-option are configured to show hidden directories and files before their non-hidden counterparts. If you want to reverse this order, you must append the -X option (such as -AFXhlv --group-directories-first).

(use-package dired
  :config
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq delete-by-moving-to-trash t)
  (setq dired-listing-switches "-AFhlv --group-directories-first")
  (setq dired-dwim-target t)
  :hook ((dired-mode . dired-hide-details-mode)
         (dired-mode . hl-line-mode)))

(use-package dired-aux
  :config
  (setq dired-isearch-filenames 'dwim)
  ;; The following were introduced in Emacs 27.1
  (setq dired-create-destination-dirs 'always)
  (setq dired-vc-rename-file t))

(use-package find-dired
  :after dired
  :config
  (setq find-ls-option ;; applies to `find-name-dired'
   '("-ls" . "-AFhlv --group-directories-first"))
  (setq find-name-arg "-iname"))

(use-package async
  :ensure t)

(use-package dired-async
  :after (dired async)
  :hook (dired-mode . dired-async-mode))

Pro tip while renaming or copying a file, M-n will return its original name, thus allowing you to easily {pre,ap}pend to it.

4.1.2 Narrowed dired

I have a video demo on techniques to narrow a Dired buffer.

The easiest way to produce a Dired buffer with only a handful of files is to mark them, either manually or with % m, then toggle the mark with t, and then remove (just from the view) everything with k. This will leave you with only the files you need to focus on.

For dynamic filtering, use this package. Exit the narrowed view with g (which is generally used to regenerate the listing).

The keys for this are meant to resemble other common search patterns such as occur. Other useful interactive functions I considered, but opted against them in the interest of simplicity:

  • dired-narrow-regexp
  • dired-narrow-fuzzy
(use-package dired-narrow
  :ensure t
  :after dired
  :config
  (setq dired-narrow-exit-when-one-left t)
  (setq dired-narrow-enable-blinking t)
  (setq dired-narrow-blink-time 0.3)
  :bind (:map dired-mode-map
         ("M-s n" . dired-narrow)))

4.1.3 wdired (writable dired)

This is the editable state of a dired buffer. You can access it with C-x C-q. Write changes to files or directories, as if it were a regular buffer, then confirm them with C-c C-c.

  • While in writable state, allow the changing of permissions.
  • While renaming a file, any forward slash is treated like a directory and is created directly upon successful exit.
(use-package wdired
  :after dired
  :commands (wdired-mode
             wdired-change-to-wdired-mode)
  :config
  (setq wdired-allow-to-change-permissions t)
  (setq wdired-create-parent-directories t))

4.1.4 peep-dired (file previews including images)

By default, dired does not show previews of files, while image-dired is intended for a different purpose. We just want to toggle the behaviour while inside a regular dired buffer.

(use-package peep-dired
  :ensure t
  :after dired
  :bind (:map dired-mode-map
              ("P" . peep-dired))
  :config
  (setq peep-dired-cleanup-on-disable t)
  (setq peep-dired-ignored-extensions
        '("mkv" "webm" "mp4" "mp3" "ogg" "iso")))

;; ;; use this for peep always on
;; (setq peep-dired-enable-on-directories t)

4.1.5 image-dired (image thumbnails and previews)

This tool offers facilities for generating thumbnails out of a selection of images and displaying them in a separate buffer. An external program is needed for converting the images into thumbnails: imagemagick. Other useful external packages are optipng and sxiv. The former is for operating on PNG files, while the latter is a lightweight image viewer.

I feel this process is a bit cumbersome and can be very slow if you try to generate lots of images at once. The culprit is the image converter.

(use-package image-dired
  :config
  (setq image-dired-external-viewer "xdg-open")
  (setq image-dired-thumb-size 80)
  (setq image-dired-thumb-margin 2)
  (setq image-dired-thumb-relief 0)
  (setq image-dired-thumbs-per-row 4)
  :bind (:map image-dired-thumbnail-mode-map
              ("<return>" . image-dired-thumbnail-display-external)))

4.1.6 dired-subtree (tree-style view/navigation)

This is great. Tree-style navigation across the filesystem.

  • The tab key will expand or contract the subdirectory at point.
  • C-TAB will behave just like org-mode handles its headings: hit it once to expand a subdir at point, twice to do it recursively, thrice to contract the tree.
  • I also have Shift-TAB for contracting the subtree when the point is inside of it.

At any rate, this does not override the action of inserting a subdirectory listing in the current dired buffer (with i over the target dir).

(use-package dired-subtree
  :ensure t
  :after dired
  :bind (:map dired-mode-map
              ("<tab>" . dired-subtree-toggle)
              ("<C-tab>" . dired-subtree-cycle)
              ("<S-iso-lefttab>" . dired-subtree-remove)))

4.1.7 dired-x (extra Dired functions)

Some additional features that are shipped with Emacs. The one I need is dired-jump and its "other window" variant. These are among my favourite commands. They will always take you to the directory that contains the current buffer. 'Jumping' works even when you are inside buffers that do not visit files, such as Magit. Edit a file then proceed to do some file management, then invoke previous-buffer or winnder-undo to go back to where you were (I have a key bindings for those in the Window configuration section). Everything happens naturally. Emacs' interconnectedness at its best!

(use-package dired-x
  :after dired
  :bind (("C-x C-j" . dired-jump)
         ("s-j" . dired-jump)
         ("C-x 4 C-j" . dired-jump-other-window)
         ("s-J" . dired-jump-other-window))
  :hook
  (dired-mode . (lambda ()
                  (setq dired-clean-confirm-killing-deleted-buffers t))))

4.1.8 dired-rsync

The rsync utility is great for performing file transfers between different systems (such as via SSH). I have been using the standard CLI tool for quite some time now. This package offers integration with Dired (do M-x man rsync RET and read this package's README for more information on the technicalities).

(use-package dired-rsync
  :ensure t
  :bind (:map dired-mode-map
              ("r" . dired-rsync)))

4.1.9 diredfl (more dired colours)

This package defines a few more colours for Dired, especially while in the detailed view. My themes support it (see the section on my Modus themes).

(use-package diredfl
  :ensure t
  :hook (dired-mode . diredfl-mode))

4.1.10 Git overview in Dired

I generally do not need to have the git related information readily available. I use a dedicated package for version control. Still, there are cases where just toggling on an overview is all you ever need.

The commit message format is configured to show an abbreviated hash of the commit, the commit subject, and the relative date. The \t represents the tab character and is there to ensure alignment.

(use-package dired-git-info
  :ensure t
  :after dired
  :config
  (setq dgi-commit-message-format "%h\t%s\t%cr")
  :bind (:map dired-mode-map
              (")" . dired-git-info-mode)))

4.2 Git front-end (Magit) and relevant configurations

I was already well-versed in the CLI commands for git, but I feel that magit offers an intuitive interface that speeds up most common tasks. The real reason I use it though, is because it makes it easier to perform git operations while inside a directory/file. No need to switch to a terminal emulator.

4.2.1 Base Magit settings

Magit has good defaults. I only found a few things that I would like to customise, which I do in the following package declarations.

(use-package magit
  :ensure t
  :bind (("C-c g" . magit-status)
         ("s-g" . magit-status)))

4.2.2 Git commits

The following package is configured in accordance with the guidelines provided by this article on writing a Git commit message. The gist is to write commits that are clean and easy to read. The fill-column is set elsewhere in this document to 72 characters long.

(use-package git-commit
  :after magit
  :config
  (setq git-commit-summary-max-length 50)
  (setq git-commit-known-pseudo-headers
        '("Signed-off-by"
          "Acked-by"
          "Modified-by"
          "Cc"
          "Suggested-by"
          "Reported-by"
          "Tested-by"
          "Reviewed-by"))
  (setq git-commit-style-convention-checks
        '(non-empty-second-line
          overlong-summary-line)))

4.2.3 Git diffs

The settings below are for the diff screens that Magit produces. I just want to highlight changes within a line, not just the line itself. I enable it only for the focused hunk (there is an option for 'all).

My Modus themes are configured to style these appropriately.

(use-package magit-diff
  :after magit
  :config
  (setq magit-diff-refine-hunk t))

And this covers the built-in diff mode, which I seldom use. Both of these variables were introduced in Emacs 27.1.

(use-package diff
  :config
  (setq diff-font-lock-prettify nil)
  (setq diff-font-lock-syntax nil))

4.2.4 Magit repo list

When maintaining a number of projects, it sometimes is necessary to produce a full list of them with their corresponding Magit status. That way you can determine very quickly which repositories need to be examined further.

(use-package magit-repos
  :after magit
  :commands magit-list-repositories
  :config
  (setq magit-repository-directories
        '(("~/Git/Projects" . 1))))

4.2.5 Git time machine

One of the major upsides of keeping files under version control is the ability to revert to a previous state in the history of changes. By using git-timemachine we can flip through a file's full list of permutations. Reverting to a given phase is as easy as writing the buffer to a file, with C-x C-w. In general, it might be better to save a version as a different file and then run M-x diff between the two. Or maybe you want to do so with three files, in which case M-x ediff3 is your friend.

Once inside the time machine, hit ? to get a help interface with the available key bindings. These extend magit functionality.

(use-package git-timemachine
  :ensure t
  :commands git-timemachine)

4.3 Working with buffers

4.3.1 Unique names for buffers

These settings make it easier to work with multiple buffers. When two buffers have the same name, Emacs will try to disambiguate them by displaying their unique path inside angled brackets. With the addition of uniquify-strip-common-suffix it will also remove the part of the file system path they have in common.

All such operations are reversed once an offending buffer is removed from the list, allowing Emacs to revert to the standard of displaying only the buffer's name.

(use-package uniquify
  :config
  (setq uniquify-buffer-name-style 'post-forward-angle-brackets)
  (setq uniquify-strip-common-suffix t)
  (setq uniquify-after-kill-buffer-p t))

4.3.2 Configure ibuffer

ibuffer is a built-in replacement for buffer-list that allows for fine-grained control over the buffer list (both work similar to dired).

Some tweaks to the default behaviour and presentation:

  • Prompt for confirmation only when deleting a modified buffer.
  • Hide the summary.
  • Do not open on the other window (not focused window).
  • Do not show empty filter groups.
  • Do not cycle movements. So do not go to the top when moving downward at the last item on the list.
  • Use colours for common actions more consistently. Inherit styles from Dired (see my Modus themes).
  • Remap default key to launch ibuffer instead of list-buffers.
(use-package ibuffer
  :config
  (setq ibuffer-expert t)
  (setq ibuffer-display-summary nil)
  (setq ibuffer-use-other-window nil)
  (setq ibuffer-show-empty-filter-groups nil)
  (setq ibuffer-movement-cycle nil)
  (setq ibuffer-default-sorting-mode 'filename/process)
  ;;;; NOTE built into the Modus themes
  ;; (setq ibuffer-deletion-face 'dired-flagged)
  ;; (setq ibuffer-marked-face 'dired-marked)
  (setq ibuffer-title-face 'font-lock-doc-face)
  (setq ibuffer-use-header-line t)
  (setq ibuffer-default-shrink-to-minimum-size nil)
  (setq ibuffer-saved-filter-groups
        '(("Main"
           ("Directories" (mode . dired-mode))
           ("Org" (mode . org-mode))
           ("Programming" (or
                           (mode . c-mode)
                           (mode . conf-mode)
                           (mode . css-mode)
                           (mode . emacs-lisp-mode)
                           (mode . html-mode)
                           (mode . mhtml-mode)
                           (mode . python-mode)
                           (mode . ruby-mode)
                           (mode . scss-mode)
                           (mode . shell-script-mode)
                           (mode . yaml-mode)))
           ("Markdown" (mode . markdown-mode))
           ("Magit" (or
                     (mode . magit-blame-mode)
                     (mode . magit-cherry-mode)
                     (mode . magit-diff-mode)
                     (mode . magit-log-mode)
                     (mode . magit-process-mode)
                     (mode . magit-status-mode)))
           ("Apps" (or
                    (mode . bongo-playlist-mode)
                    (mode . mu4e-compose-mode)
                    (mode . mu4e-headers-mode)
                    (mode . mu4e-main-mode)
                    (mode . elfeed-search-mode)
                    (mode . elfeed-show-mode)
                    (mode . mu4e-view-mode)))
           ("Emacs" (or
                     (name . "^\\*Help\\*$")
                     (name . "^\\*Custom.*")
                     (name . "^\\*Org Agenda\\*$")
                     (name . "^\\*info\\*$")
                     (name . "^\\*scratch\\*$")
                     (name . "^\\*Backtrace\\*$")
                     (name . "^\\*Messages\\*$"))))))
  :hook
  (ibuffer-mode . hl-line-mode)
  (ibuffer-mode . (lambda ()
                    (ibuffer-switch-to-saved-filter-groups "Main")))
  :bind (("C-x C-b" . ibuffer)))

4.4 Window configuration

I believe that Emacs' true power lies in its buffer management rather than its multiplexing. The latter becomes inefficient at scale, since it tries to emulate the limitations of the real world, namely, the placement of things on a desk.

By leveraging the power of the computer, we can use search methods to easily reach any item. There is no need to remain confined to the idea of a finite space (screen real estate) that needs to be carefully managed.

That granted, Emacs' multiplexing can be turned into a powerhouse as well, covering everything from window placement rules, to the recording of history and layouts, as well as directional or direct window navigation.

4.4.1 Window rules and basic tweaks

The display-buffer-alist and all other functions grouped together with prot/window-dired-vc-root-left are considered experimental and subject to review. The former is intended as a rule-set for controlling the display of windows. While the latter serves as a series of tangible examples of passing certain rules programmatically, in combination with a few relevant extras. The objective is to create a more intuitive workflow where targeted buffer groups or types are always shown in a given location, on the premise that predictability improves usability.

For each buffer action in display-buffer-alist we can define several functions for selecting the appropriate window. These are executed in sequence, but my usage thus far suggests that a simpler method is just as effective for my case.

Everything pertaining to buffer actions is documented at length in the GNU Emacs Lisp Reference Manual, currently corresponding to version 26.3. Information can also be found via C-h f display-buffer and, for my settings, C-h f display-buffer-in-side-window.

With regard to the contents of the :bind keyword of the window library, most key combinations are complementary to the standard ones, such as C-x 1 becoming s-1, C-x o turning into s-o and the like. They do not replace the defaults: they just provide more convenient access to their corresponding functions. They all involve the Super key, following the norms described in the introductory note on the matter.

For a demo of the display-buffer-alist and the functions that accompany it, watch my video on rules for buffer placement (2020-01-07).

(use-package window
  :init
  ;; TODO do not repeat common parts, abstract them somehow
  (setq display-buffer-alist
        '(;; top side window
          ("\\*\\(Flycheck\\|Package-Lint\\).*"
           (display-buffer-in-side-window)
           (window-height . 0.15)
           (side . top)
           (slot . 0)
           (window-parameters . ((no-other-window . t))))
          ;; bottom side window
          ("\\*e?shell.*"
           (display-buffer-in-side-window)
           (window-height . 0.25)
           (side . bottom)
           (slot . 0))
          ("\\*\\(Backtrace\\|Warnings\\|Compile-Log\\|[Hh]elp\\|Messages\\)\\*"
           (display-buffer-in-side-window)
           (window-height . 0.25)
           (side . bottom)
           (slot . 1))
          ;; right side window
          ("\\*Faces\\*"
           (display-buffer-in-side-window)
           (window-width . 0.333)
           (side . right)
           (slot . 0)
           (window-parameters . ((no-other-window . t)
                                 (mode-line-format . (" "
                                                      mode-line-buffer-identification)))))
          ("\\*Custom.*"
           (display-buffer-in-side-window)
           (window-width . 0.333)
           (side . right)
           (slot . 1))))
  :bind (("s-n" . next-buffer)
         ("s-p" . previous-buffer)
         ("s-o" . other-window)
         ("s-2" . split-window-below)
         ("s-3" . split-window-right)
         ("s-0" . delete-window)
         ("s-1" . delete-other-windows)
         ("s-5" . delete-frame)
         ("<f8>" . window-toggle-side-windows)))

;; These are all experimental.  Just showcasing the power of passing
;; parameters to windows or frames.
(use-package emacs
  :config
  (defun prot/window-dired-vc-root-left ()
    "Open root directory of current version-controlled repository
or the present working directory with `dired' and bespoke window
parameters.  This is meant as a proof-of-concept function,
illustrating how to leverage window rules to display a buffer,
plus a few concomitant extras."
    (interactive)
    (let ((dir (if (eq (vc-root-dir) nil)
                   (dired-noselect default-directory)
                 (dired-noselect (vc-root-dir)))))
      (display-buffer-in-side-window
       dir `((side . left)
             (slot . 0)
             (window-width . 0.16)
             (window-parameters . ((no-other-window . t)
                                   (no-delete-other-windows . t)
                                   (mode-line-format . (" "
                                                        mode-line-buffer-identification))))))
      (with-current-buffer dir
        (rename-buffer "*Dired-Side*")
        (setq-local window-size-fixed 'width)))
    (with-eval-after-load 'ace-window
      (when (boundp 'aw-ignored-buffers)
        (add-to-list 'aw-ignored-buffers "*Dired-Side*"))))

  (defun prot/make-frame-floating-with-current-buffer ()
    "Display the current buffer in a new floating frame.

This passes certain parameters to the newly created frame:

- use a different name than the default;
- use a graphical frame;
- do not display the minibuffer.

The name is meant to be used by the external rules of my tiling
window manager (BSPWM) to present the frame in a floating state."
    (interactive)
    (make-frame '((name . "my_float_window")
                  (window-system . x)
                  (minibuffer . nil))))

  (defun prot/display-buffer-at-bottom ()
    "Move the current buffer to the bottom of the frame.  This is
useful to take a buffer out of a side window.

The window parameters of this function are provided mostly for
didactic purposes."
    (interactive)
    (let ((buffer (current-buffer)))
      (with-current-buffer buffer
        (delete-window)
        (display-buffer-at-bottom
         buffer `((window-parameters . ((mode-line-format . (" "
                                                             mode-line-buffer-identification)))))))))
  :bind (("C-c d" . prot/window-dired-vc-root-left)
         ("C-c f" . prot/make-frame-floating-with-current-buffer)
         ("C-c b" . prot/display-buffer-at-bottom)))

4.4.2 Window history and directional motions (winner-mode and windmove)

Winner is a built-in tool that keeps a record of buffer and window layout changes. It then allows us to move back and forth in the history of said changes. I have it enabled by default, while I assign its two main functions to Super and the right/left arrow keys.

(use-package winner
  :hook (after-init . winner-mode)
  :bind (("<s-right>" . winner-redo)
         ("<s-left>" . winner-undo)))

Windmove is also built into Emacs. It provides functions for selecting a window in any of the cardinal directions. I use the Vim keys while holding down Super and Meta because other mnemonics-based actions involving just Super or Meta are already occupied.

The windmove-create-window specifies what should happen when trying to move past the edge of the frame. The idea with this is to allow it to create a new window. I do not want that.

(use-package windmove
  :config
  (setq windmove-create-window nil)     ; Emacs 27.1
  :bind (("M-s-h" . windmove-left))
         ("M-s-j" . windmove-down)
         ("M-s-k" . windmove-up)
         ("M-s-l" . windmove-right))

4.4.3 ace-window (more flexible window motions)

The default Emacs distribution is very weak on window management. While its notion of the "other window" is appropriate for two-window layouts, it becomes a constraint whenever more windows need to be displayed on the frame.

With ace-window (from the developer of Ivy/Counsel/Swiper…) we can both enhance the movement between windows, but also perform additional actions to them. These are listed in aw-dispatch-list. Note that the keys in that list must not conflict with those in aw-keys.

Also bear in mind that I define several other window motions. Refer to Basic tweaks for windows and Window history and directional motions.

(use-package ace-window
  :ensure t
  :config
  (setq aw-keys '(?h ?j ?k ?l ?y ?u ?i ?o ?p))
  (setq aw-scope 'frame)
  (setq aw-dispatch-always t)
  (setq aw-dispatch-alist
        '((?s aw-swap-window "Swap Windows")
          (?2 aw-split-window-vert "Split Window Vertically")
          (?3 aw-split-window-horz "Split Window Horizontally")
          (?? aw-show-dispatch-help)))
  (setq aw-minibuffer-flag t)
  (setq aw-ignore-current nil)
  (setq aw-display-mode-overlay t)
  (setq aw-background t)

  (ace-window-display-mode -1)
  :bind (("s-a" . ace-window)))

5 Applications and utilities

This section includes configurations for programs like email clients, news reader, music players… Anything you would normally see in a GUI. It also configures the various "killer apps" of the Emacs ecosystem (those that did not fit in any of the other sections). The end goal is to eventually integrate everything inside of Emacs.

5.1 Configure Org-mode

WORK IN PROGRESS. Documentation will be expanded once I am done.

Overview of the following settings:

Agenda and task list
Define where to store captured items and how to display the information.
Style code blocks
I want .org files to use the native settings for styling code blocks. The first variable concerns C-c ' (run it inside of a code block). That opens a buffer with just the contents of the code block, with the major mode configured appropriately.
Export settings
Just provide a table of contents, cover 8 levels of depth and offer support for the back-ends I specify. For the time being, I mostly use HTML to publish this document on my website…
General settings
Allow C-a and C-e to move to the logical beginning or end of the Org element upon second invocation (first one behaves normally). Define a few templates for inserting source blocks. Prior to Org version 9.2 you could insert a template by using <, KEY, and then TAB, where KEY is the one that corresponds to the given template. Now you invoke the menu with C-c C-, and then press the relevant key.

The contrib/org-agenda-refresh is taken from the README of org-recur.

(use-package org
  :config
  ;; agenda
  (setq org-directory "~/Org")
  (setq org-agenda-files
        '("~/Org/tasks.org" "~/Org/notes.org"))
  (setq org-default-notes-file "~/Org/notes.org")
  (setq org-agenda-window-setup 'current-window)
  (setq org-deadline-warning-days 7)
  (setq org-agenda-span 'month)
  (setq org-agenda-skip-scheduled-if-deadline-is-shown t)
  (setq org-agenda-sorting-strategy
        '((agenda deadline-up priority-down)
          (todo priority-down category-keep)
          (tags priority-down category-keep)
          ((search category-keep))))
  ;; capture, refile, todo
  (setq org-refile-targets
        '((org-agenda-files . (:maxlevel . 3))))
  (setq org-reverse-note-order nil)
  (setq org-refile-use-outline-path t)
  (setq org-refile-allow-creating-parent-nodes 'confirm)
  (setq org-refile-use-cache t)
  (setq org-todo-keywords
        '((sequence "TODO(t)" "|" "DONE(d)")
          (sequence "STUDY(s)" "WRITE(w)" "|" "POSTED(p)")
          (sequence "NOTE(n)" "|" "ARCHIVED(r)")))
  (setq org-highest-priority ?A)
  (setq org-lowest-priority ?C)
  (setq org-default-priority ?A)
  (setq org-capture-templates
        '(("t" "Task for the day" entry
           (setq file+headline "~/Org/tasks.org" "Tasks with a deadline")
           "* TODO [#A] %?\nDEADLINE: %t\n")
          ("l" "Link to Emacs buffer or file" entry
           (setq file+headline "~/Org/tasks.org" "Links to buffers/files")
           "* STUDY [#B] %?\nSCHEDULED: %t\n%a")
          ("L" "Link to full file system path" entry
           (setq file+headline "~/Org/tasks.org" "Links to buffers/files")
           "* STUDY [#B] %?\nSCHEDULED: %t\n%F")
          ("n" "Note" entry
           (setq file+headline "~/Org/tasks.org" "Notes with or without context")
           "* NOTE [#C] %?\nSCHEDULED: %t\n%i")))
  ;; code blocks
  (setq org-src-window-setup 'current-window)
  (setq org-src-fontify-natively t)
  (setq org-src-preserve-indentation t)
  (setq org-src-tab-acts-natively t)
  (setq org-confirm-babel-evaluate nil)
  (setq org-edit-src-content-indentation 0)
  ;; export
  (setq org-export-with-toc t)
  (setq org-export-headline-levels 8)
  (setq org-export-backends
        '(ascii html latex md))
  ;; log
  (setq org-log-done 'time)
  (setq org-log-redeadline nil)
  (setq org-log-reschedule nil)
  (setq org-read-date-prefer-future 'time)
  ;; general
  (setq org-special-ctrl-a/e 'reversed)
  (setq org-hide-emphasis-markers t)
  (setq org-structure-template-alist
        '(("s" . "src")
          ("E" . "src emacs-lisp")
          ("e" . "example")
          ("q" . "quote")
          ("v" . "verse")
          ("V" . "verbatim")
          ("c" . "center")
          ("C" . "comment"))) ; CHANGED in Org 9.2 (Emacs 27.1)
  (setq org-catch-invisible-edits 'show)
  (setq org-return-follows-link t)

  ;; Refresh org-agenda after rescheduling a task.
  (defun contrib/org-agenda-refresh ()
    "Refresh all `org-agenda' buffers."
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when (derived-mode-p 'org-agenda-mode)
          (org-agenda-maybe-redo)))))

  (defadvice org-schedule (after refresh-agenda activate)
    "Refresh org-agenda."
    (contrib/org-agenda-refresh))

  (defun prot/org-insert-elisp-template ()
    "Just insert an Emacs Lisp source block."
    (interactive)
    (org-insert-structure-template "src emacs-lisp"))

  ;; disable keys I rely on for other tasks
  (define-key org-mode-map (kbd "<C-return>") nil)
  (define-key org-mode-map (kbd "<C-S-return>") nil)
  :bind (("C-c a" . org-agenda)
         ("C-c c" . org-capture)
         ("C-c l" . org-store-link)
         ("C-c e" . prot/org-insert-elisp-template)))

5.1.1 Consistent heading IDs (and anchor tags)

Everything in this section is copied directly from this detailed tutorial on Org header IDs. Basically, the problem is that exported HTML does not have reliable anchor tags for the various sections of the document. This fixes the issue (read the article for more).

(use-package org-id
  :after org
  :commands (contrib/org-get-id
             contrib/org-id-headlines)
  :config
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)

  (defun contrib/org-get-id (&optional pom create prefix)
    "Get the CUSTOM_ID property of the entry at point-or-marker POM.
   If POM is nil, refer to the entry at point. If the entry does
   not have an CUSTOM_ID, the function returns nil. However, when
   CREATE is non nil, create a CUSTOM_ID if none is present
   already. PREFIX will be passed through to `org-id-new'. In any
   case, the CUSTOM_ID of the entry is returned."
    (interactive)
    (org-with-point-at pom
      (let ((id (org-entry-get nil "CUSTOM_ID")))
        (cond
         ((and id (stringp id) (string-match "\\S-" id))
          id)
         (create
          (setq id (org-id-new (concat prefix "h")))
          (org-entry-put pom "CUSTOM_ID" id)
          (org-id-add-location id (buffer-file-name (buffer-base-buffer)))
          id)))))

  (defun contrib/org-id-headlines ()
    "Add CUSTOM_ID properties to all headlines in the
   current file which do not already have one."
    (interactive)
    (org-map-entries (lambda ()
                       (contrib/org-get-id (point) 'create)))))

5.1.2 Simple presentations inside of Emacs (org-tree-slide)

I like the idea of easily converting an .org file into a set of pseudo slides. It is simple and has no external dependencies.

My needs are pretty simple and straightforward: just show some text. The other packages in this section are only meant to be used for presentations.

For the font specified herein, make sure to understand the overall configurations by reading the section on primary font settings. I opted not to use text-scale-adjust or some variant thereof, because that only operates on the text of the focused window, whereas I want all interfaces to adapt to the new size (so that I can, for example, show the minibuffer while doing a presentation).

NOTE: I am keeping these disabled and will only be activating them when I need to do a presentation. For example, I did so in my ~1 hour-long video blog on the topic of switching to Emacs (2019-12-20).

(use-package darkroom
  :ensure t
  :disabled
  :commands (darkroom-mode
             darkroom-tentative-mode)
  :config
  (setq darkroom-text-scale-increase 0))

(use-package org-bullets
  :ensure t
  :disabled
  :commands org-bullets-mode
  :after org)

(use-package org-tree-slide
  :ensure t
  :disabled
  :after (org darkroom)
  :config
  (setq org-tree-slide-breadcrumbs nil)
  (setq org-tree-slide-header nil)
  (setq org-tree-slide-slide-in-effect nil)
  (setq org-tree-slide-heading-emphasis nil)
  (setq org-tree-slide-cursor-init t)
  (setq org-tree-slide-modeline-display nil)
  (setq org-tree-slide-skip-done nil)
  (setq org-tree-slide-skip-comments t)
  (setq org-tree-slide-fold-subtrees-skipped t)
  (setq org-tree-slide-skip-outline-level 8)
  (setq org-tree-slide-never-touch-face t)

  (defun prot/org-presentation ()
    "Specifies conditions that should apply locally upon
activation of `org-tree-slide-mode'."
    (if (eq darkroom-tentative-mode nil)
        (progn
          (darkroom-tentative-mode 1)
          (org-bullets-mode 1)
          (org-indent-mode 1)
          (set-frame-font (concat
                           prot/fixed-pitch-font "-" (number-to-string 14)
                           prot/fixed-pitch-params)
                          t t)
          (setq cursor-type '(bar . 1)))
      (darkroom-tentative-mode -1)
      (org-bullets-mode -1)
      (org-indent-mode -1)
      (prot/fonts-per-monitor)
      (setq cursor-type 'box)))
  :bind (("<f9>" . org-tree-slide-mode)
         :map org-tree-slide-mode-map
         ("<C-right>" . org-tree-slide-move-next-tree)
         ("<C-left>" . org-tree-slide-move-previous-tree))
  :hook (org-tree-slide-mode . prot/org-presentation))

5.2 Shells and terminal emulators

5.2.1 Shell (M-x shell)

This is a shell (Bash, in my case) that runs inside of Emacs. Unlike term (see next section), this one can use standard Emacs keys and behaves like an ordinary buffer. The one area where it differs substantially from ordinary buffers is with regard to the command prompt: you can re-run a command on the scroll-back buffer by just hitting RET while point is on its line (no need to go back to the end and cycle the command history with M-p or M-n).

Note that I am well aware of eshell. I read the manual and checked lots of configurations about it. I still do not see a major improvement over Bash, notwithstanding the significant downside of having to learn the idiosyncrasies of yet another tool. Besides, many of the features of eshell are already available inside of Emacs and are accessible via more appropriate interfaces (e.g. find-file or dired powered by a completion framework like ido-mode or ivy). The shell has to behave consistently whether I run it inside of Emacs, in Xterm, or just a TTY. Everything else introduces friction.

Run C-h m inside of a shell buffer to learn about all the key bindings and corresponding functions.

(use-package shell
  :commands shell-command
  :config
  (setq ansi-color-for-comint-mode t)
  (setq shell-command-prompt-show-cwd t) ; Emacs 27.1

  (defun prot/shell-multi ()
    "Spawn a new instance of `shell' and give it a unique name
based on the directory of the current buffer."
    (interactive)
    (let* ((parent (if (buffer-file-name)
                       (file-name-directory (buffer-file-name))
                     default-directory))
           (name (car (last (split-string parent "/" t)))))
      (with-current-buffer (shell)
        (rename-buffer
         (generate-new-buffer-name (concat "*shell: " name "*"))))))
  :bind (("<s-return>" . shell)
         ("<s-S-return>" . prot/shell-multi)))

5.2.2 Terminal emulators

term and ansi-term are terminal emulators like Xterm. Not to be confused with command line shells such as Bash. They run inside of Emacs but are basically alien to the rest of the Emacs milieu: they do not reuse standard key bindings like C-y.

Only use those if you absolutely need one AND you have no access to a standalone, fully fledged terminal emulator.

As far as I can tell, based on reading the comments in term.el and elsewhere in the docs, the major difference between term and ansi-term is the ability of the latter to run one or multiple buffers simultaneously. Better check the documentation for this point. It does not seem to be a strong point, since this is also possible with the other options in the Emacs space (e.g. with C-u M-x shell).

(use-package term
  :commands term
  :config
  (setq term-buffer-maximum-size 9999)
  (setq term-completion-autolist t)
  (setq term-completion-recexact t)
  (setq term-scroll-to-bottom-on-output t))

5.3 Proced (process monitor, similar to `top')

This is a built-in tool that allows you to monitor running processes and act on them accordingly. These are the basic settings I have right now. Would need to experiment with it a bit more. It works fine though.

(use-package proced
  :commands proced
  :config
  (setq proced-auto-update-flag t)
  (setq proced-auto-update-interval 1)
  (setq proced-descend t)
  (setq proced-filter 'user))

5.4 Pass interface (password-store)

The external pass program, aka "password-store", is a password manager that uses GPG and standard UNIX tools to handle passwords. Encrypted files are stored in a plain directory structure. Very simple, very nice: now all data is available with a variety of interfaces, such as standard CLI, a dmenu interface, a graphical front-end like qtpass, etc.

The package below provides an Emacs interface to some of the most common actions, in the form of a list of candidates that can be narrowed down (such as with Ivy, Ido). I use it to quickly store a password to the kill ring.

(use-package password-store
  :ensure t
  :commands (password-store-copy
             password-store-edit
             password-store-insert)
  :config
  (setq password-store-time-before-clipboard-restore 30))

And this one adds a major mode for browsing the pass keychain. Call it with M-x pass. There is a helpful section at the top with key bindings and their functions.

(use-package pass
  :ensure t
  :commands pass)

5.5 Elfeed (feed reader for RSS/Atom)

Settings for the feed reader package. I mostly care about the unique buffers tweak. It allows me to open a feed entry and keep it around while I go on browsing the feed list.

Here is the source code on Gitlab for ambrevar/elfeed-play-with-mpv, with minor tweaks by me.

(use-package elfeed
  :ensure t
  :commands elfeed
  :config
  (setq elfeed-use-curl t)
  (setq elfeed-curl-max-connections 10)
  (setq elfeed-db-directory "~/.emacs.d/elfeed")
  (setq elfeed-enclosure-default-dir "~/Downloads")
  (setq elfeed-search-clipboard-type 'CLIPBOARD)
  (setq elfeed-search-title-max-width (current-fill-column))
  (setq elfeed-search-title-max-width 100)
  (setq elfeed-search-title-min-width 30)
  (setq elfeed-search-trailing-width 16)
  (setq elfeed-show-truncate-long-urls t)
  (setq elfeed-show-unique-buffers t)

  (defun prot/feeds ()
    "Loads a file with RSS/Atom feeds.  This file contains valid
syntax for use by the `elfeed' package."
    (let ((feeds "~/.emacs.d/feeds.el.gpg"))
      (when (file-exists-p feeds)
        (load-file feeds))))

  (defun ambrevar/elfeed-play-with-mpv ()
    "Play entry link with mpv."
    (interactive)
    (let ((entry (if (eq major-mode 'elfeed-show-mode)
                     elfeed-show-entry (elfeed-search-selected :single)))
          (quality-arg "")
          (quality-val (completing-read "Resolution: "
                                        '("480" "720" "1080")
                                        nil nil)))
      (setq quality-val (string-to-number quality-val))
      (message "Opening %s with height≤%s..."
               (elfeed-entry-link entry) quality-val)
      (when (< 0 quality-val)
        (setq quality-arg
              (format "--ytdl-format=[height<=?%s]" quality-val)))
      (start-process "elfeed-mpv" nil "mpv"
                     quality-arg (elfeed-entry-link entry))))
  :hook (after-init . prot/feeds)
  :bind (:map elfeed-search-mode-map
              ("v" . (lambda ()
                       (interactive)
                       (ambrevar/elfeed-play-with-mpv)
                       (elfeed-search-untag-all-unread)))
              ("w" . elfeed-search-yank)
              ("g" . elfeed-update)
              ("G" . elfeed-search-update--force)
              :map elfeed-show-mode-map
              ("v" . ambrevar/elfeed-play-with-mpv)
              ("w" . elfeed-show-yank)))

5.6 Emacs web browser and HTML parser

As far as I can tell, the following shr-* variables concern an HTML parser that is used by a variety of tools, including Elfeed (defined right above). I guess we could scope them by using hooks, but I see no need for different settings.

What these do:

  • Open links in a new Emacs window, instead of the system's browser. This Emacs web browser is called eww.
  • Use monospaced fonts, since that is what I want to have everywhere in Emacs.
  • Do not preserve colours from websites, as they may be inaccessible (see my Modus theme).
  • Keep images to 20% of the window. This number is arbitrary. It just feels like a good upper limit (not a fan of decorative images inside of blog posts).
  • Line length at same number of characters as fill-column (defined elsewhere in this doc at 72).
(use-package shr
  :commands (eww
             eww-browse-url)
  :config
  (setq browse-url-browser-function 'eww-browse-url)
  (setq shr-use-fonts nil)
  (setq shr-use-colors nil)
  (setq shr-max-image-proportion 0.2)
  (setq shr-width (current-fill-column)))

Support the HTML pre tag with proper syntax highlighting. Got this snippet directly from its GitHub project page.

(use-package shr-tag-pre-highlight
  :ensure t
  :after shr
  :config
  (add-to-list 'shr-external-rendering-functions
               '(pre . shr-tag-pre-highlight))
  (when (version< emacs-version "26")
    (with-eval-after-load 'eww
      (advice-add 'eww-display-html :around
                  'eww-display-html--override-shr-external-rendering-functions))))

5.7 Bongo (Music player)

I already tried EMMS and various other options but did not stick with them. I felt I was missing something or maybe I just tested them too early into my Emacs journey. Now using Bongo and am quite happy with it.

Concerning the customisations below, these can be summarised thus:

  • Hide icons.
  • No mode line indicators.
  • Do not ask for directory tree insertion.
  • With Dired, the "Music" directory doubles as a Bongo library (see prot/bongo-dired-library and the relevant hook).
  • Because of the above, prefer playlist buffers (pro tip: you can use dired-jump inside of a playlist buffer to switch to that directory—see my Dired section for the relevant configs).
  • While contrib/bongo-add-dired-file integrates Dired mark command with Bongo. The function is provided in this Emacs Wiki entry (minor tweaks by me).

The way I play music is very simple. I load up a directory tree with a bunch of audio files. Then I C-u C-c C-r from inside a Bongo buffer to play the tracks in random order. Done! I rarely switch tracks manually and change playlists in regular intervals (a directory tree typically contains hundreds of music files).

This sequence is conveniently mapped to C-RET inside of the Bongo Library buffer (so the Dired buffer of ~/Music and its sub-directories). The command will operate on the directory at point or on the marked items, if they exist. Note that I used to bind that action to just SPC but I realised it would interfere with tasks in wdired (and probably elsewhere).

I have a couple of videos about my workflow with Bongo and Dired:

(use-package bongo
  :ensure t
  :commands bongo
  :config
  (setq bongo-default-directory "~/Music")
  (setq bongo-prefer-library-buffers nil)
  (setq bongo-insert-whole-directory-trees t)
  (setq bongo-logo nil)
  (setq bongo-action-track-icon nil)
  (setq bongo-display-track-icons nil)
  (setq bongo-display-track-lengths nil)
  (setq bongo-display-header-icons nil)
  (setq bongo-display-playback-mode-indicator t)
  (setq bongo-display-inline-playback-progress nil)
  (setq bongo-mark-played-tracks nil)
  (setq bongo-header-line-mode nil)
  (setq bongo-header-line-function nil)
  (setq bongo-mode-line-indicator-mode nil)
  (setq bongo-vlc-program-name "cvlc")

  (defun contrib/bongo-add-dired-files ()
    "Add marked files inside of a Dired buffer to the Bongo library"
    (interactive)
    (let (file-point file (files nil))
      (dired-map-over-marks
       (setq file-point (dired-move-to-filename)
             file (dired-get-filename)
             files (append files (list file)))
       nil t)
      (save-excursion
        (set-buffer bongo-default-playlist-buffer-name)
        (mapc 'bongo-insert-file files))))

  (defun prot/bongo-dired-library ()
    "Activate `bongo-dired-library-mode' when accessing the
~/Music directory.  This is meant to be hooked into `dired-mode'.

Upon activation, the directory and all its sub-directories become
a valid library buffer for Bongo, from where we can, among
others, add tracks to playlists.  The added benefit is that Dired
will continue to behave as normal, making this a superior
alternative to a purpose-specific library buffer."
    (when (string-match-p "\\`~/Music/" default-directory)
      (set (make-local-variable 'bongo-dired-library-mode) 't)))

  (defun prot/bongo-clear-playlist-and-stop ()
    "Stop playback and clear the entire `bongo' playlist buffer.
Contrary to the standard `bongo-erase-buffer', this also removes
the currently-playing track."
    (interactive)
    (bongo-stop)
    (bongo-erase-buffer))

  (defun prot/bongo-library-insert-and-play-random ()
    "Enqueue directory tree at point, or marked items, to the
`bongo' playlist.  This is meant to work while inside a `dired'
buffer that doubles as a library buffer (see
`prot/bongo-dired-library')."
    (interactive)
    (contrib/bongo-add-dired-files)
    (bongo-play-random)
    (bongo-random-playback-mode 1))
  :hook
  (dired-mode . prot/bongo-dired-library)
  (bongo-playlist-mode . hl-line-mode)
  :bind (("<C-XF86AudioPlay>" . bongo-pause/resume)
         ("<C-XF86AudioNext>" . bongo-next)
         ("<C-XF86AudioPrev>" . bongo-previous)
         ("<M-XF86AudioPlay>" . bongo-show)
         :map bongo-playlist-mode-map
         ("C-d" . prot/bongo-clear-playlist-and-stop)
         :map bongo-dired-library-mode-map
         ("<C-return>" . prot/bongo-library-insert-and-play-random)))

5.7.1 TODO read metadata

5.7.2 TODO browse/search by metadata

5.8 Email settings

Setting up Email is quite the challenge, especially because we have been used to the likes of Thunderbird, where everything "just works". The toolchain for my current setup consists of the following (nothing is from MELPA):

  • The built-in SMTP capabilities to send email.
  • The mu4e front-end to the mu mail indexer. This is for finding and reading email. Both of those tools are available on available through distro-specific packages (tested on Debian and Void Linux).
  • The offlineimap utility (also from distro packages) is used to store a local copy of my emails. This is necessary for mu to actually do its work.

5.8.1 Sending email (SMTP)

These are the base configs for the SMTP server. Passwords for each email account are stored in ~/.authinfo.gpg.

Pro tip: While using Dired, type : e to encrypt file at point. Emacs can decrypt those automatically.

(use-package smtpmail
  :config
  (setq smtpmail-default-smtp-server "mail.gandi.net")
  (setq smtpmail-smtp-server "mail.gandi.net")
  (setq smtpmail-stream-type 'ssl)
  (setq smtpmail-smtp-service 465)
  (setq user-full-name "Protesilaos Stavrou")
  (setq auth-sources '("~/.authinfo.gpg" "~/.authinfo"))
  (setq epa-file-cache-passphrase-for-symmetric-encryption t))

(use-package smtpmail-async
  :after smtpmail
  :config
  (setq send-mail-function 'async-smtpmail-send-it)
  (setq message-send-mail-function 'async-smtpmail-send-it))

5.8.2 Reading email (MUA)

The following configure mu4e, the Mail User Agent. An overview:

  • Include mu4e in the load path. This is necessary because we are using the Void Linux package.
  • Use mu4e as the default MUA in Emacs. This concerns actions such as C-x m (compose-mail).
  • I prefer to run the "get mail" command manually rather than rely on a timer. The idea is that when I have time to check my email, I can also refresh its index.
  • Do not provide verbose output about indexing operations.
  • Update manually, because I anyway interact with email only when I have time to check it.
  • Use my selected completion framework (Ido, Ivy…) where relevant.
  • Define my signature and include it in new messages.
  • Specify the directory where mail is stored. This is where offlineimap is configured to place its findings. Each email account has its own subdirectory therein.
  • Careful with this: Store sent messages in their appropriate place (defined in the "contexts" file—see further below). The docs suggest that IMAP accounts should opt for either moving messages to the trash directory or outright deleting them. The idea is that IMAP is supposed to handle this stuff automatically—my initial tests with my configs do not confirm this, which is why I just tell it to place them in the "sent" directory.
  • Do not kill message buffer upon exit from it. It can always be useful to quickly check something.
  • Always show email addresses (the default is to display just the name).
  • The variables that concern mu4e contexts are relevant because of prot/mu4e-contexts. It loads the mu4e-contexts with all the information about my account setup. I set this in a private and encrypted file. Do check the example in the official docs. I based my work off of it.
  • Message citation is just an improved format for quoting a message in a reply. The format looks like "On 2019-12-09, 16:50 (CET), PERSON <EMAIL> wrote:".
(use-package mu4e
  :load-path "/usr/share/emacs/site-lisp/mu4e"
  :after (smtpmail smtpmail-async)
  :commands (mu4e mu4e-update-mail-and-index)
  :config
  (setq mail-user-agent 'mu4e-user-agent)
  (setq mu4e-get-mail-command "offlineimap")
  (setq mu4e-hide-index-messages t)
  (setq mu4e-update-interval nil)
  (setq mu4e-completing-read-function 'completing-read)
  (setq mu4e-compose-signature "Protesilaos Stavrou\nprotesilaos.com\n")
  (setq mu4e-compose-signature-auto-include t)
  (setq mu4e-maildir "~/Maildir")
  (setq mu4e-sent-messages-behavior 'sent)
  (setq message-kill-buffer-on-exit nil)
  (setq mu4e-view-show-addresses t)
  (setq mu4e-context-policy 'pick-first)
  (setq mu4e-compose-context-policy 'ask)
  (setq message-citation-line-format "On %Y-%m-%d, %R (%Z), %f wrote:\n")
  (setq message-citation-line-function 'message-insert-formatted-citation-line)

  (defun prot/mu4e-contexts ()
    "Loads a file with the specifics of my email account info."
    (let ((mails "~/.emacs.d/mu4e-contexts.el.gpg"))
      (when (file-exists-p mails)
        (load-file mails))))
  :hook (after-init . prot/mu4e-contexts)
  :bind (:map mu4e-main-mode-map
              ("g" . mu4e-update-mail-and-index)))

To set up offlineimap I used the Arch Wiki entry.

5.8.3 MU4E extension for org-capture

With this little snippet, we allow org-capture convert any email into a note, to-do item or whatever. The killer feature is that we get a direct link back to the original email. This way, we can avoid the problem of searching through a pile of messages until we find the one we really need. Nice!

(use-package org-mu4e                   ; no need for `:ensure t'
  :after (org mu4e)
  :config
  (setq org-mu4e-link-query-in-headers-mode nil))

5.9 Package lists

With this I just want to enable line highlighting when browsing the list of packages. I generally use hl-line-mode on all interfaces where the current line is more important than the exact column of the point.

(use-package package
  :commands (list-packages
             package-refresh-contents
             package-list-packages)
  :hook (package-menu-mode . hl-line-mode))

6 Language settings (spelling, abbrevs, commenting…)

This section is all about configurations and packages that deal with natural or programming language enhancements.

6.1 Recognise subwords

It is better you do C-h f subword-mode. Basically, this alters the way Emacs understands word boundaries. So, camelCaseWords are exposed as their constituents rather than one long word, meaning that motions will behave accordingly.

(use-package subword
  :delight
  :hook (prog-mode . subword-mode))

6.2 Auto-fill plain text and comments

Make sure we run the mode that keeps paragraphs within the column limit. The adaptive mode improves the handling of things like bulleted and numbered lists.

(use-package emacs
  :hook (text-mode . (lambda ()
                       (turn-on-auto-fill)
                       (delight 'auto-fill-function nil t)
                       (setq adaptive-fill-mode t))))

6.3 Comment lines, regions, boxes, etc.

Just some basic configurations for commenting structured text. This is mostly a placeholder for potentially more targeted and detailed settings that would involve per-mode hooks.

The purpose of my reviewed key bindings is to make them more consistent. Helps with mnemonics. They also are more ergonomic. To this end, I have the following:

  • The standard commenting function is now bound to the simple C-;. This runs a "do what I meant" function I have defined, whose detailed documentation can be read below.
  • C-: (C-S-;) will kill the comment on the current line. This is particularly helpful when the comment follows text you would like to keep. The operation can be performed regardless of where the point is on the line. Some modes disable this behaviour (e.g. trying it on source code inside of org-mode—for those cases, focus the block with C-c ').
  • The M-; will just append a comment to the line, rather than the default comment-dwim.

Note that C-; is occupied by some flyspell command that I have no use for (disabled in the relevant package declaration).

Lastly, use M-j (alias C-M-j) when you want to continue an existing comment on a new line with respect for the current indentation. If you are not inside of a comment, this will just create an indentation-aware new line.

(use-package newcomment
  :config
  (setq comment-empty-lines t)
  (setq comment-fill-column nil)
  (setq comment-multi-line t)
  (setq comment-style 'multi-line)

  (defun prot/comment-dwim (&optional arg)
    "Alternative to `comment-dwim': offers a simple wrapper
around `comment-line' and `comment-dwim'.

If the region is active, then toggle the comment status of the
region or, if the major mode defines as much, of all the lines
implied by the region boundaries.

Else toggle the comment status of the line at point."
    (interactive "*P")
    (if (use-region-p)
        (comment-dwim arg)
      (save-excursion
        (comment-line arg))))

  :bind (("C-;" . prot/comment-dwim)
         ("C-:" . comment-kill)
         ("M-;" . comment-indent)
         ("C-x C-;" . comment-box)))

6.4 Flyspell (spell check)

I need spell checking for both English and Greek. In previous versions of this section I had configurations that would automate spell checking for both languages at once. It worked but was rather slow.

Upon further inspection, I have realised that I seldom need to work in mixed language circumstances. Moreover, I now understand that I do not need to have spell checking always on. It can be activated manually, with the flyspell functions defined in the :commands segment below. It is thus possible to keep things simple and to switch dictionaries only when necessary.

Also bear in mind that the key binding C-; is disabled because I re-purpose that for a faster version of C-x C-; (much more useful for my work—see the section on comments).

The two functions I define are meant to ease my workflow, while superseding the default binding for ispell-word. What I normally use is my M-$, which will spell-check the active region or toggle the dictionaries I use. The C-M-$ is defined as a fallback option in case I have already highlighted a region and then realise I need the other dictionary.

Note that aspell and its dictionaries are not part of Emacs. I install the relevant packages from my distro's archive.

(use-package flyspell
  :commands (ispell-change-dictionary
             ispell-word
             flyspell-buffer
             flyspell-mode
             flyspell-region)
  :config
  (setq flyspell-issue-message-flag nil)
  (setq flyspell-issue-welcome-flag nil)
  (setq ispell-program-name "aspell")
  (setq ispell-dictionary "en_GB")

  (define-key flyspell-mode-map (kbd "C-;") nil)

  (defun prot/ispell-toggle-dictionaries ()
    "Toggle between English and Greek dictionaries."
    (interactive)
    (if (string= ispell-current-dictionary "en")
        (ispell-change-dictionary "el")
      (ispell-change-dictionary "en")))

  (defun prot/flyspell-dwim (&optional beg end)
    "Run `flyspell-region' on the active region, else toggle the
ispell dictionaries with `prot/ispell-toggle-dictionaries'."
    (interactive "r")
      (if (use-region-p)
          (flyspell-region beg end)
        (prot/ispell-toggle-dictionaries)))

  :bind (("M-$" . prot/flyspell-dwim)
         ("C-M-$" . prot/ispell-toggle-dictionaries)))

6.5 Flycheck (code linting)

This is a great tool for identifying errors or inconsistencies in programming syntax. I used it for my Modus themes (defined elsewhere in this document) to make the necessary checks for code quality, in preparation of their release as a MELPA package.

This package provides a broad range of customisation options, with many items targeting specific programming languages and/or style conventions. It seems very powerful! As these targeted operations require some more research, I leave everything to its default value and will be updating the package declaration over time.

Note that previous versions of this document included a package that adds a Flycheck indicator on the mode line. This has now been superseded by my custom mode line configurations.

(use-package flycheck
  :ensure t
  :commands flycheck-mode
  :config
  (setq flycheck-check-syntax-automatically
        '(save mode-enabled)))

There is also a built-in tool that covers Flycheck's niche, but I find its functionality somewhat more limited. As such I am defining it so that an autoload will be created for it (meaning that it will only be made available on demand).

(use-package flymake
  :commands flymake-mode)

6.5.1 Flycheck package metadata

This one integrates with Flycheck to provide the necessary checks for code that is intended for use in MELPA. I just define the linter for packages as a standalone declaration, in case I wish to keep this without the flycheck interface.

(use-package package-lint
  :ensure t)

(use-package flycheck-package
  :ensure t
  :after (flycheck package-lint)
  :config
  (flycheck-package-setup))

6.6 Markdown support

I edit lots of Markdown files. This makes things easier.

(use-package markdown-mode
  :ensure t
  :mode ("\\.md\\'" . markdown-mode))

6.7 YAML support

This adds support for YAML files.

(use-package yaml-mode
  :ensure t
  :mode (("\\.yml\\'" . yaml-mode)
         ("\\.yaml\\'" . yaml-mode)))

6.8 Support for various config files

The following rules implement the appropriate syntax highlighting in various configuration files that I access.

The list will be expanded over time.

(use-package emacs
  :mode (("offlineimaprc" . conf-mode)
         ("sxhkdrc" . conf-mode)
         ("Xmodmap" . conf-xdefaults-mode)
         ("template" . shell-script-mode)
         ("\\.rasi\\'" . css-mode)))

6.9 Simple abbreviations

This section stores all the "skeletons" I define. These are snippets of text, typically templates or code statements, that are meant to speed up typing. I combine them with abbreviations.

Please note that these will be very simplistic at first. I am aware that they can be abstracted using elisp—need to learn more on that front. Also note that wherever you see " _ " it signifies the position of the cursor after the skeleton has been inserted.

(use-package abbrev
  :delight
  :config
  (setq abbrev-file-name "~/.emacs.d/abbrevs")
  (setq only-global-abbrevs nil)

  ;;;;;;;;;;;;;;;;;;;;;;
  ;; simple skeletons ;;
  ;;;;;;;;;;;;;;;;;;;;;;
  (define-skeleton protesilaos-com-skeleton
    "Adds a link to my website while prompting for a possible
  extension."
    "Insert website extension: "
    "https://protesilaos.com/" str "")
  (define-abbrev global-abbrev-table "meweb"
    "" 'protesilaos-com-skeleton)

  (define-skeleton protesilaos-gitlab-skeleton
    "Adds a link to my GitLab account while prompting for a
  possible extension.  Makes it easy to link to my various git
  repos."
    "Website extension: "
    "https://gitlab.com/protesilaos/" str "")
  (define-abbrev global-abbrev-table "megit"
    "" 'protesilaos-gitlab-skeleton)
  :hook ((text-mode . abbrev-mode)
         (git-commit-mode . abbrev-mode)))

6.10 Dabbrev and hippie-expand (dynamic word completion)

This is Emacs' own approach to text completion: "dynamic abbreviation" and the corresponding "do what I mean wrapper" called hippie-expand. The latter is a superset of dabbrev.

To learn about hippie-expand-try-functions-list, read the introductory remarks in M-x find-library RET hippie-exp RET.

That granted, the tool that I actually use is called Company, which includes the functionality of dabbrev while offering several extensions. Refer to the Company section for a detailed explanation.

(use-package dabbrev
  :commands dabbrev-expand
  :config
  (setq dabbrev-abbrev-char-regexp nil)
  (setq dabbrev-backward-only nil)
  (setq dabbrev-case-distinction nil)
  (setq dabbrev-case-fold-search t)
  (setq dabbrev-case-replace nil)
  (setq dabbrev-eliminate-newlines nil)
  (setq dabbrev-upcase-means-case-search t))

(use-package hippie-exp
  :after dabbrev
  :commands hippie-expand
  :config
  (setq hippie-expand-try-functions-list
        '(try-expand-dabbrev
          try-expand-dabbrev-visible
          try-expand-dabbrev-from-kill
          try-expand-dabbrev-all-buffers
          try-expand-list
          try-expand-list-all-buffers
          try-expand-line
          try-expand-line-all-buffers
          try-complete-file-name-partially
          try-complete-file-name
          try-expand-all-abbrevs))
  (setq hippie-expand-verbose t))

6.11 Company (text completion/expansion framework)

Company has a modular design that allows it to adapt to the user's needs. Additional backends are provided as separate packages, though I currently do not need any of them.

Overview of the following settings.

  • Do not allow autocomplete. This might sound like a good idea, but it can have undesired effects: e.g. expanding "is" into "isearch" just by hitting the space key. That depends on the settings for prefix length and idle delay.
  • Use the built-in "dynamic abbreviation" (dabbrev) in comments and strings, as well as more generally. I find that this method gives me relevant results.
  • While using dabbrev make sure to populate the list with suggestions from all buffers, thus increasing the likelihood of showing something I already have and need again.
  • Never downcase an expanded completion candidate, but do allow for case insensitive candidates.
  • Set minimum length to 3 and delay the pop-up by 0.3 seconds. This avoids too many false positives or minor inconveniences that I encounter in my workflow (such as is => isearch).
  • Allow non-matching input. The cursor can thus be moved as expected while the pop-up menu with the suggestions is active. I find this necessary to avoid being locked into the completion list.
  • Let the list be cyclical, so moving to the end will bring you back at the top. In practice though, I never use this. If I need to run through a long list and come back again, I am probably doing something wrong. At any rate, you can search within the list of suggestions while it is active by using C-s.
  • Show numbers for the ten matching candidates at the top of the list. Select them with M-NUM.
  • Control how results are sorted once they are collected from all the relevant backends. What I have in company-transformers works fine for me, but I am not sure how it interacts with company-prescient (see relevant section).
  • Align annotations to the right. This concerns certain backends that display additional information for their candidates. This keeps things nice and tidy.
  • Keep the completion candidates to ten at a time, allowing us to target each item directly by its corresponding number (10 is 0 in this case). Remember to select the item with Meta and its number.
  • Keep the margin of the tooltip to a minimum and show a scrollbar when the list exceeds the limit of 10 candidates at a time.

Now a few words about the key bindings:

  • Company will start automatically based on the prefix and delay settings mentioned above. However, it is possible to launch it manually with M-/. Bear in mind that by default this key is used by the built-in Dabbrev tool. Since company encompasses the functionality of that backend, there is no need to keep a separate key for it.
  • While the list of suggestions is open, M-/ will switch to a different backend. This can help improve results for the task at hand.
  • If possible, the common part of completion candidates can be inserted directly with C-TAB. If no common prefix can be completed, this key will more the selection to the next item on the list. To select the current item without further questions, simply hit TAB.
  • C-n and C-p can be used to cycle the list of suggestions.
(use-package company
  :ensure t
  :delight
  :config
  (setq company-auto-complete nil)
  (setq company-dabbrev-code-everywhere t)
  (setq company-dabbrev-code-modes t)
  (setq company-dabbrev-code-other-buffers 'all)
  (setq company-dabbrev-downcase nil)
  (setq company-dabbrev-ignore-case t)
  (setq company-dabbrev-other-buffers 'all)
  (setq company-idle-delay 0.3)
  (setq company-minimum-prefix-length 3)
  (setq company-require-match nil)
  (setq company-selection-wrap-around t)
  (setq company-show-numbers t)
  (setq company-transformers
        '(company-sort-by-backend-importance
          company-sort-prefer-same-case-prefix
          company-sort-by-occurrence))
  (setq company-tooltip-align-annotations t)
  (setq company-tooltip-limit 10)
  (setq company-tooltip-margin 1)
  (setq company-tooltip-offset-display 'scrollbar)
  :hook (after-init . global-company-mode)
  :bind (:map company-mode-map
              ("M-/" . company-manual-begin)
              :map company-active-map
              (("M-/" . company-other-backend)
               ("<tab>" . company-complete-selection)
               ("<C-tab>" . company-complete-common-or-cycle)
               ("C-n" . company-select-next)
               ("C-p" . company-select-previous))))

6.11.1 Prescient filtering for Company

The prescient package provides scoring and filtering mechanisms for completion candidates. I am using it in tandem with Ivy (see the relevant section). Here I declare the extension for company.

(use-package company-prescient
  :ensure t
  :after (company prescient)
  :config (company-prescient-mode 1))

6.12 Eldoc (elisp live documentation feedback)

Displays information about the construct at point in the echo area. I find that I have no use for this, so I am disabling the mode for the time being.

(use-package eldoc
  :config
  (global-eldoc-mode -1))

7 Interface and interactions

This section contains lots of small tweaks and refinements that cover a wide range of item across the Emacs customisation settings.

7.1 Modus themes (my very accessible themes)

I am using my own themes. They are designed to conform with the highest accessibility standard for colour contrast between foreground and background values. This stands for a minimum contrast ratio of 7:1, also known as the WCAG AAA standard.

I call this project "Modus themes". It consists of "Modus Operandi" (light theme) and "Modus Vivendi" (dark). The source code and installation instructions are available on their GitLab page.

The themes are available on MELPA as standalone packages as of December 1, 2019. I did it that way instead of distributing them as a single package because I know that people tend to use one or the other. And also due to the fact that one is not a prerequisite for the other. I personally use both, switching between them depending on the ambient light in my room.

Note though that because I am using these themes locally as part of their development process, I am not using the MELPA packages directly.

(use-package emacs
  :config
  (setq custom-safe-themes t)

  (defun prot/modus-operandi ()
    "Enable some `modus-operandi' variables and load the theme."
    (setq modus-operandi-theme-slanted-constructs t
          modus-operandi-theme-bold-constructs t
          modus-operandi-theme-proportional-fonts nil
          modus-operandi-theme-scale-headings t
          modus-operandi-theme-scale-1 1.1
          modus-operandi-theme-scale-2 1.2
          modus-operandi-theme-scale-3 1.3
          modus-operandi-theme-scale-4 1.4)
    (load-theme 'modus-operandi t))

  (defun prot/modus-vivendi ()
    "Enable some `modus-vivendi' variables and load the theme."
    (setq modus-vivendi-theme-slanted-constructs t
          modus-vivendi-theme-bold-constructs t
          modus-vivendi-theme-proportional-fonts nil
          modus-vivendi-theme-scale-headings t
          modus-vivendi-theme-scale-1 1.1
          modus-vivendi-theme-scale-2 1.2
          modus-vivendi-theme-scale-3 1.3
          modus-vivendi-theme-scale-4 1.4)
    (load-theme 'modus-vivendi t))

  (defun prot/modus-themes-toggle ()
    "Simplistic toggle for my Modus Themes.  All it does is check
if `modus-operandi' (light version) is active and if so switch to
`modus-vivendi' (dark version).  Else it switches to the light
theme."
    (interactive)
    (if (eq (car custom-enabled-themes) 'modus-operandi)
        (prot/modus-vivendi)
      (prot/modus-operandi)))
  :bind ("<f5>" . prot/modus-themes-toggle)
  :hook (after-init . prot/modus-operandi))

7.2 Tabs for window layouts and buffers (Emacs 27.1)

Starting with version 27.1, Emacs has built-in support for two distinct concepts of "tabs":

  1. Spaces that contain windows in any given layout.
  2. A list of buffers presented as buttons at the top of the window.

The former, represented by the tab-bar library is best understood as the equivalent of "virtual desktops", as these are used in most desktop environments or minimalist window managers.

The latter, implemented in tab-line, is the same as the tabs you are used to in web browsers. Each buffer is assigned to a single tab. Clicking on the tab takes you to the corresponding buffer.

I do not need the tab-line as I find such tabs to be inefficient at scale. Finding a buffer through search mechanisms is generally faster: it does not matter whether you have ten or a hundred buffers on the list (unless, of course, they all have similar names in which case you are in trouble either way).

On the other hand, the workspaces (tab-bar) can prove very useful for organising the various applications that are running inside of Emacs. You can, for example, have your current project on tab (workspace) 1, your email and news reader on 2, music on 3, and so on. Of course, this can also be achieved by using separate frames for each of these, though I generally prefer working with a single frame.

At any rate, I do not need the tab bar to be active at all times. I enable it manually when it is necessary. This might change in the future as I experiment more with it as part of my workflow.

(use-package tab-bar
  :commands (tab-bar-mode tab-bar-history-mode)
  :config
  (setq tab-bar-close-button-show t)
  (setq tab-bar-close-last-tab-choice nil)
  (setq tab-bar-close-tab-select 'recent)
  (setq tab-bar-new-tab-choice t)
  (setq tab-bar-new-tab-to 'rightmost)
  (setq tab-bar-position nil)
  (setq tab-bar-show 1)
  (setq tab-bar-tab-hints nil)
  (setq tab-bar-tab-name-function 'tab-bar-tab-name-current)

  (tab-bar-mode -1)
  (tab-bar-history-mode -1))

;; This is only included as a reference.
(use-package tab-line
  :commands (tab-line-mode global-tab-line-mode)
  :config
  (global-tab-line-mode -1))

7.3 Custom mode line

I generally like the default mode line. But it has a major weakness: it concentrates all indicators to the left, making it feel a bit too busy. This detracts from its otherwise clean presentation. Given that this is an integral part of the Emacs experience, I feel that it is necessary to customise it, while retaining its simple looks.

The overall presentation should be just like the original one, with the exception that modes are pushed to the rightmost area. Transient feedback for keyboard macros and buffer narrowing are drawn in a more intense style, so as to eliminate any doubts as to what is going on.

An earlier version of this package declaration contained support for the counter of the search results displayed by the anzu package. It has been removed ever since I upgraded to Emacs 27.1, as it now comes with built-in support for counting the results of isearch.

My personal choice aside, all packages that modify the mode line are supported by my Modus themes.

(use-package telephone-line
  :ensure t
  :config
  (setq telephone-line-height nil)
  (setq telephone-line-primary-left-separator
        'telephone-line-nil)
  (setq telephone-line-primary-right-separator
        'telephone-line-halfsin-right)
  (setq telephone-line-secondary-left-separator
        'telephone-line-halfsin-hollow-left)
  (setq telephone-line-secondary-right-separator
        'telephone-line-halfsin-hollow-right)
  (setq telephone-line-separator-extra-padding 3)

  (telephone-line-defsegment prot/telephone-line-kbd-macro-segment ()
    "Offer keyboard macro feedback."
    (when (or defining-kbd-macro executing-kbd-macro)
      (telephone-line-raw "Macro")))

  (telephone-line-defsegment prot/telephone-line-narrow-segment ()
    "Offer feedback on narrowed buffer, with less padding than
default."
    (unless (eq (buffer-narrowed-p) nil)
      (telephone-line-raw "Narrow")))

  (telephone-line-defsegment prot/telephone-line-vcs-segment ()
    "Show current VCS branch and status indicator."
    (when (and vc-mode buffer-file-name)
      (let* ((backend (vc-backend buffer-file-name))
             (state (vc-state buffer-file-name backend)))
        (concat
         (telephone-line-raw
          (format "%s" (substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2))))
         (cond ((memq state '(edited added))
                (telephone-line-raw " *"))
               ((eq state 'needs-merge)
                (telephone-line-raw " ?"))
               ((eq state 'needs-update)
                (telephone-line-raw " !"))
               ((memq state '(removed conflict unregistered))
                (telephone-line-raw " ×"))
               (t
                (telephone-line-raw "")))))))

  (setq telephone-line-lhs
        '((accent . (prot/telephone-line-kbd-macro-segment
                     prot/telephone-line-narrow-segment))
          (nil    . (telephone-line-buffer-segment
                     telephone-line-position-segment))))
  (setq telephone-line-rhs
        '((nil    . (telephone-line-process-segment
                     telephone-line-misc-info-segment
                     telephone-line-flycheck-segment
                     prot/telephone-line-vcs-segment
                     telephone-line-minor-mode-segment
                     telephone-line-major-mode-segment))))
  :hook (after-init . telephone-line-mode))

7.3.1 TODO create mode line segment for ace-window

7.4 Feedback

The common thread of these options is the feedback they provide us with or simplify common tasks:

  • Answer with just the initials when dealing with "yes/no" questions.
  • Follow symlinks without asking.
  • Faster feedback for key chords (keys appear in the echo area).
  • Be quiet about auto-revert messages. They interrupt the minibuffer.
  • Enable actions for narrowing, region {up,down}casing (all caps or no caps), dired single-buffer navigation (bound to a). Disable overwrite-mode.
  • Allow inputting Greek while preserving Emacs keys. Toggle with C-\.
  • Ignore visual/audible bells, because Emacs more appropriate ways of providing error/warning feedback (e.g. a failed isearch will return no results, while the failed match will be styled accordingly in the echo area)
(use-package emacs
  :config
  (setq vc-follow-symlinks t)
  (setq frame-title-format '("%b %& GNU Emacs"))
  (setq echo-keystrokes 0.25)
  (setq auto-revert-verbose nil)
  (setq default-input-method "greek")
  (setq ring-bell-function 'ignore)

  (defalias 'yes-or-no-p 'y-or-n-p)
  (put 'narrow-to-region 'disabled nil)
  (put 'upcase-region 'disabled nil)
  (put 'downcase-region 'disabled nil)
  (put 'dired-find-alternate-file 'disabled nil)
  (put 'overwrite-mode 'disabled t))

7.5 Current line highlight (hl-line-mode)

This is a mode that I only activate via hooks for certain buffers where the current line itself is more important that the actual column (e.g. in Dired buffers). Here I configure it so that the highlight applies only to the current window. There is also a "global" variant, for when the equivalent mode is used (I have no plan to use that).

(use-package hl-line
  :config
  (setq hl-line-sticky-flag nil))

7.6 Auto revert mode

This mode ensures that the buffer is updated whenever the file changes. A change can happen externally or by some other tool inside of Emacs (e.g. kill a Magit diff).

(use-package autorevert
  :delight
  :config
  (setq auto-revert-verbose t)
  :hook (after-init . global-auto-revert-mode))

7.7 Parentheses

Configure the mode that highlights matching delimiters or parentheses. I consider this of utmost importance when working with languages such as elisp.

Summary of what these do:

  • Activate the mode.
  • Show the matching delimiter/parenthesis if on screen, else show nothing. It is possible to highlight the expression enclosed by the delimiters, by using either mixed or expression. The latter always highlights the entire balanced expression, while the former will only do so if the matching delimiter is off screen.
  • Highlight parentheses even if the point is in their vicinity. This means the beginning or end of the line, with space in between.
  • Do not highlight a match when the point is on the inside of the parenthesis.
(use-package paren
  :config
  (setq show-paren-style 'parenthesis)
  (setq show-paren-when-point-in-periphery t)
  (setq show-paren-when-point-inside-paren nil)
  :hook (after-init . show-paren-mode))

7.8 Configure 'electric' behaviour

Emacs labels as "electric" any behaviour that involves contextual auto-insertion of characters. This is a summary of my settings:

  • Indent automatically.
  • If electric-pair-mode is enabled (which I might do manually), insert quotes and brackets in pairs. Only do so if there is no alphabetic character after the cursor.
  • The cryptic numbers in the pairs set, correspond to curly single and double quotes and these «». The contents of this set are always inserted in pairs, regardless of major mode.
    • To get those numbers, evaluate (string-to-char CHAR) where CHAR is the one you are interested in. For example, get the literal tab's character with (string-to-char "\t").
  • While inputting a pair, inserting the closing character will just skip over the existing one, rather than add a new one. So typing ( will insert () and then typing ) will just be the same as moving forward one character C-f.
  • Do not skip over whitespace when operating on pairs. Combined with the above point, this means that a new character will be inserted, rather than be skipped over. I find this better, because it prevents the point from jumping forward, plus it allows for more natural editing.
  • The whitespace characters are space (\s), tab (\t), and newline (\n).
  • The rest concern the conditions for transforming quotes into their curly equivalents. I keep this disabled, because curly quotes are distinct characters. It is difficult to search for them. Just note that on GNU/Linux you can type them directly by hitting the "compose" key and then an angled bracket (< or >) followed by a quote mark.
(use-package electric
  :config
  (setq electric-pair-inhibit-predicate'electric-pair-conservative-inhibit)
  (setq electric-pair-preserve-balance t)
  (setq electric-pair-pairs
        '((8216 . 8217)
          (8220 . 8221)
          (171 . 187)))
  (setq electric-pair-skip-self 'electric-pair-default-skip-self)
  (setq electric-pair-skip-whitespace nil)
  (setq electric-pair-skip-whitespace-chars
        '(9
          10
          32))
  (setq electric-quote-context-sensitive t)
  (setq electric-quote-paragraph t)
  (setq electric-quote-string nil)
  :hook (after-init . (lambda ()
                        (electric-indent-mode 1)
                        (electric-pair-mode -1)
                        (electric-quote-mode -1))))

7.9 Tabs, indentation, and the TAB key

I believe tabs, in the sense of inserting the tab character, are best suited for indentation. While spaces are superior at precisely aligning text. However, I understand that elisp uses its own approach, which I do not want to interfere with. Also, Emacs tends to perform alignments by mixing tabs with spaces, which can actually lead to misalignments depending on the tab size. As such, I am disabling tabs by default.

(use-package emacs
  :config
  (setq-default tab-always-indent 'complete)
  (setq-default tab-width 4)
  (setq-default indent-tabs-mode nil))

7.10 Cursor appearance and tweaks

My cursor is a box character that blinks. After lots of testing with packages like beacon and using a bar cursor, I set back to what I always had. Combined with the default blinking settings, it makes for the most accessible combination: the blinking box is easy to spot, even when the point is placed over an inaccessible colour combination (very low contrast between the background and the foreground).

The "stretch" variable can make the box cover the entirety of a character's width. I disable it because it changes the consistency of things on the screen while moving around (such as when passing over a tab character).

(use-package emacs
  :config
  (setq cursor-type 'box)
  (setq cursor-in-non-selected-windows 'hollow)
  (setq x-stretch-cursor nil))

7.11 Line length (column count)

The column count is set to 72. The standard line length is 80 characters, so having it at something less allows for such things as quoting plain text, indenting, etc. git commit messages also make good use of this method. The column count is used by auto-fill-mode and similar tools (or when manually invoking text formatting with M-q).

(use-package emacs
  :config
  (setq fill-column 72)
  (setq sentence-end-double-space t)
  (setq sentence-end-without-period nil)
  (setq colon-double-space nil)
  :hook (after-init . column-number-mode))

7.12 Delete trailing whitespace

This always creates unnecessary diffs in git. Just delete it upon saving.

(use-package emacs
  :hook (before-save . delete-trailing-whitespace))

7.13 Preserve contents of system clipboard

Say you copied a link from your web browser, then switched to Emacs to paste it somewhere. Before you do that, you notice something you want to kill. Doing that will place the last kill to the clipboard, thus overriding the thing you copied earlier. We can have a kill ring solution to this with the following:

(use-package emacs
  :config
  (setq save-interprogram-paste-before-kill t))

Now the contents of the clipboard are stored in the kill ring and can be retrieved from there (e.g. with M-y).

7.14 Mouse behaviour

I seldom use the mouse with Emacs. But when I do, I am most likely highlighting some area that I would like to copy. This setting has the same behaviour as terminal emulators that place the selection to the clipboard (or the primary selection).

The other options in short:

  • Hide pointer while typing.
  • Enable mouse scroll.
  • Faster wheel movement means faster scroll.
(use-package mouse
  :config
  (setq mouse-drag-copy-region t)
  (setq make-pointer-invisible t)
  (setq mouse-wheel-progressive-speed t)
  :hook (after-init . mouse-wheel-mode))

7.15 Selection

This is a very helpful mode. It kills the marked region when inserting directly to it. It also has checks to ensure that yanking over a selected region will not insert itself (e.g. mouse-drag-copy-region is in effect).

(use-package delsel
  :hook (after-init . delete-selection-mode))

Pro tip: On Emacs 27.1 you can create a rectangular region by holding down Ctrl and Meta while dragging the mouse with the left click pressed.

7.16 Scrolling behaviour

Page scrolling should keep the point at the same visual position, rather than force it to the top or bottom of the viewport. This eliminates the friction of guessing where the point has warped to.

As for per-line scrolling, I dislike the default behaviour of visually re-centring the point. With the following, it will stay at the top/bottom of the screen while moving in that direction (use C-l to reposition it). This does not result in more manual interventions to recenter text, because of the above.

(use-package emacs
  :config
  (setq scroll-preserve-screen-position t)
  (setq scroll-conservatively 1)
  (setq scroll-margin 0))

7.17 Tool tips

These settings control how tool tips are to be handled when hovering the mouse over an actionable item. I just want to make sure that the GTK theme is used for those, instead of the generic graphical display. The delay is slightly reduced for the initial pop-up, while it has been increased for immediate pop-ups thereafter.

(use-package tooltip
  :config
  (setq tooltip-delay 0.5)
  (setq tooltip-short-delay 0.5)
  (setq x-gtk-use-system-tooltips t)
  :hook (after-init . tooltip-mode))

7.18 Altered zap and easier repeat

Some minor conveniences:

Zap
I do not like the default behaviour of M-z (zap-to-char): it deletes the character you provide it with. Fortunately, there is a built-in replacement that deletes everything up to the character. Let's just rebind the key stroke.
Repeat
With this you can repeat the last command with C-x z and then just press z to run it over and over… Quite useful! IF you find yourself in need of something more complex, use keyboard macros.
Mark
You can move back to a previous mark by pressing C-u SPC. For a single movement that key chord is fine, but for multiple invocations it becomes tiresome. The following allows you to repeat the motion with C-SPC.
(use-package emacs
  :config
  (setq repeat-on-final-keystroke t)
  (setq set-mark-command-repeat-pop t)
  :bind ("M-z" . zap-up-to-char))

7.19 Toggle visual elements

Display line numbers (buffer-local)
I seldom use line numbers, but here it is. This toggles the setting for the local buffer. A global option is also available, but I prefer the buffer-specific variant because there are contexts where global display is not useful (such as occur).
Display invisible characters (whitespace)
Viewing invisible characters (whitespace) can be very helpful under certain circumstances. Generally though, I do not keep it active.
(use-package emacs
  :config
  (defun prot/toggle-invisibles ()
    "Toggles the display of indentation and space characters."
    (interactive)
    (if (bound-and-true-p whitespace-mode)
        (whitespace-mode -1)
      (whitespace-mode)))

  (defun prot/toggle-line-numbers ()
    "Toggles the display of line numbers.  Applies to all buffers."
    (interactive)
    (if (bound-and-true-p display-line-numbers-mode)
        (display-line-numbers-mode -1)
      (display-line-numbers-mode)))
  :bind (("<f6>" . prot/toggle-invisibles)
         ("<f7>" . prot/toggle-line-numbers)))

7.20 Rainbow blocks

This package is quite useful when debugging highly structured code that you are not familiar with. It will highlight an entire code block in a single colour, making it easier to understand the overall structure (my Modus themes support it, of course).

Also note that there is another package that applies a rainbow effect only to the delimiters. Between the two, I prefer this one. At any rate, I activate rainbow-blocks-mode manually, when I feel that I am missing something that I cannot spot right away.

A less intrusive, built-in alternative is to set the variable show-parent-style 'expression (see my configs for parentheses).

(use-package rainbow-blocks
  :ensure t
  :delight
  :commands rainbow-blocks-mode
  :config
  (setq rainbow-blocks-highlight-braces-p t)
  (setq rainbow-blocks-highlight-brackets-p t)
  (setq rainbow-blocks-highlight-parens-p t))

7.21 Rainbow mode (for colour testing)

The following package reads a colour value, such as hexadecimal RGB, and sets the background for the value in that colour. Quite useful when reviewing my themes (rainbow-mode is activated manually).

(use-package rainbow-mode
  :ensure t
  :delight
  :commands rainbow-mode
  :config
  (setq rainbow-ansi-colors nil)
  (setq rainbow-x-colors nil))

7.22 Which key (key chord hints)

By default you can receive hints for possible key chord chains by hitting C-h once you start a sequence (e.g. C-x C-k C-h will offer hints about keyboard macros). The following package simplifies this step by presenting a table with the completion targets, once certain configurable criteria are met. By default the criterion is a short timer for idleness. I find that too intrusive, opting instead to invoke the command manually (also helps that I somehow memorise all these keys…).

The "special keys" are represented by their initial letter, which is displayed in a differently coloured foreground than standard keys (as per my Modus themes).

Note that placing the mouse pointer over a which-key item will display relevant documentation in a tooltip.

(use-package which-key
  :ensure t
  :delight
  :commands which-key-C-h-dispatch
  :config
  (setq which-key-show-early-on-C-h t)
  (setq which-key-idle-delay 10000)
  (setq which-key-idle-secondary-delay 0.05)
  (setq which-key-popup-type 'side-window)
  (setq which-key-show-prefix 'echo)
  (setq which-key-max-display-columns 6)
  (setq which-key-separator " ")
  (setq which-key-special-keys '("SPC" "TAB" "RET" "ESC" "DEL"))
  :hook (after-init . which-key-mode))

8 Custom movements and motions

I generally rely on the default keys to move around (plus my Super-KEY additions to economise on some repetitive tasks). There are, however, some motions that are rather cumbersome or too specialised. While there are some commands that are not available at all. For those cases, I rely on custom functions.

8.1 Collection of unpackaged commands or tweaks

These are small functions that facilitate my day-to-day work with Emacs. They are written by me, unless otherwise noted.

Align to whitespace
This will align a portion of text using whitespace as an indicator for creating separate columns. I bind it to C-c C-=. Got it from this Pragmatic Emacs blog post.
Balance windows gracefully
I find that balance-windows-area is less intrusive than the balance-windows normally bound to C-x +. I thus choose to assign that key chord to the function I prefer.
Copy the current line
Just place the entire line in the kill ring. Do not be smart about it.
Count words in buffer
By default, the binding M-= will count the lines, words, characters in the region. I never use that. What I do use is the equivalent for the whole buffer.
Faster multiline move
Move up or down by 15 lines at a time. This is the equivalent of C-u 15 C-n or C-u 15 C-p.
Kill this buffer
A faster way of killing the focused buffer, without the need for confirming which buffer to kill. Unsaved files will still ask for confirmation though, which is exactly as it should be. Bound to s-k.
Kill to the begging of line
Just remove everything from the point till the beginning of the line. Do not try anything fancy like adjusting indentation or removing the line break.
Mark whole word
The default behaviour of M-@ is to mark a word, but will only expand from point till the end of the word. This means that to truly mark the word at point, you must first make a backward motion and then run the command. Whereas with this function, the word at point is marked from its beginning until its end. Got it from this Stack Exchange thread.
New line above or below
Create a new line above or below the current one and place the point at its beginning. Does not matter where the point is on the current line. Does not try to account for any indentation rules.
Shrink whitespace
By default, M-SPC will reduce multiple spaces into a single one, while M-\ will remove all space. Then there is C-x C-o which is the inverse of C-o. For the first two of the afore-mentioned, I use cycle-spacing: a single invocation will leave one space; a second removes all space; while a third consecutive call restores the original state. Also, since I have no use for the default function bound to M-o I make it an alias for C-x C-o.
Swap non-adjacent sentences
Allows you to set the mark at one sentence, then move to another one and exchange their positions. This uses the built-in mechanism for transposing sentences, but does not operate on constructs that are next to each other. Mostly useful for prose.
Transpose characters
Tweak the way the original command works, so that it always transposes the two characters before point. This will no longer have the effect of moving the character forward following repeated invocations of the command. Just a toggle to quickly fix the last typo.
Transpose or swap lines
Behave as the original transpose-lines, but also be aware of the active region in which case swap the line at point (region end) with the line at mark (region beginning).
Transpose or swap paragraphs
A wrapper around the original transpose-paragraphs that fulfils my expectations: if region is active, swap paragraph at mark (region beginning) with one at point (region end), otherwise transpose current paragraph with the one after it (drag paragraph downward/forward).
Transpose or swap sentences
Same principle as above. When region is active it exchanges the sentences at either end of it, otherwise is drags the sentence forward. The added effect here is that if point is at the end of the paragraph, where no further 'dragging' can occur, it transposes the sentence at point with the one before it.
Transpose or swap words
Same as with the other "swap" commands. In addition, this alters transpose-words while at the end of line where it will only transpose the last two words before the point. It thus avoids transposing words across lines or paragraphs.
Unfill text DWIM
With M-q we can break a long line into a block that ends each line at the fill-column (I configure Emacs to do this automatically, where appropriate, but often do it manually). With M-^ we can join the line at point with the one above. But there seems to be no way of doing this for a block of text. So I assign the following function to M-Q (must also hold down Shift).
Yank over current line or region
Replace the contents of the line at point (or the active region) with the most recent item from the kill ring.
(use-package emacs
  :config
  (defun bjm/align-whitespace (start end)
    "Align columns by whitespace"
    (interactive "r")
    (align-regexp start end "\\(\\s-*\\)\\s-" 1 0 t))

  (defun prot/copy-line ()
    "Copies the entirety of the current line."
    (interactive)
    (copy-region-as-kill (point-at-bol) (point-at-eol))
    (message "Current line copied"))

  (defun prot/multi-line-next ()
    "Moves the point 15 lines down."
    (interactive)
    (next-line 15))

  (defun prot/multi-line-prev ()
    "Moves the point 15 lines up."
    (interactive)
    (forward-line -15))

  (defun prot/kill-line-backward ()
    "Kill line backwards from the point to the beginning of the
line.  This will not remove the line."
    (interactive)
    (kill-line 0))

  (defun contrib/mark-whole-word (&optional arg allow-extend)
    "Like `mark-word', but selects whole words and skips over
whitespace.  If you use a negative prefix arg then select words
backward.  Otherwise select them forward.

If cursor starts in the middle of word then select that whole word.

If there is whitespace between the initial cursor position and the first
word (in the selection direction), it is skipped (not selected).

If the command is repeated or the mark is active, select the next NUM
words, where NUM is the numeric prefix argument.  (Negative NUM selects
backward.)"
    (interactive "P\np")
    (let ((num  (prefix-numeric-value arg)))
      (unless (eq last-command this-command)
        (if (natnump num)
            (skip-syntax-forward "\\s-")
          (skip-syntax-backward "\\s-")))
      (unless (or (eq last-command this-command)
                  (if (natnump num)
                      (looking-at "\\b")
                    (looking-back "\\b")))
        (if (natnump num)
            (left-word)
          (right-word)))
      (mark-word arg allow-extend)))

  (defun prot/new-line-below ()
    "Create a new line below the current one.  Move the point to
the absolute beginning.  Also see `prot/new-line-above'."
    (interactive)
    (end-of-line)
    (newline))

  (defun prot/new-line-above ()
    "Create a new line above the current one.  Move the point to
the absolute beginning.  Also see `prot/new-line-below'."
    (interactive)
    (beginning-of-line)
    (newline)
    (forward-line -1))

  (defun prot/transpose-chars ()
    "Always transposes the two characters before point.  There is
no 'dragging' the character forward.  This is the behaviour of
`transpose-chars' when point is at end-of-line."
    (interactive)
    (transpose-chars -1)
    (forward-char))

  (defun prot/transpose-or-swap-lines (arg)
    "If region is active, swap the line at mark (region
beginning) with the one at point (region end).  This leverages a
facet of the built-in `transpose-lines'.  Otherwise transpose the
current line with the one before it ('drag' line downward)."
    (interactive "p")
    (if (use-region-p)
        (transpose-lines 0)
      (transpose-lines arg)))

  (defun prot/transpose-or-swap-paragraphs (arg)
    "If region is active, swap the paragraph at mark (region
beginning) with the one at point (region end).  This leverages a
facet of the built-in `transpose-paragraphs'.  Otherwise
transpose the current paragraph with the one after it ('drag'
paragraph downward)."
    (interactive "p")
    (if (use-region-p)
        (transpose-paragraphs 0)
      (transpose-paragraphs arg)))

  (defun prot/transpose-or-swap-sentences (arg)
    "If region is active, swap the sentence at mark (region
beginning) with the one at point (region end).  This leverages a
facet of the built-in `transpose-sentences'.  Otherwise transpose
the sentence before point with the one after it ('drag' sentence
forward/downward).  Also `fill-paragraph' afterwards.

Note that, by default, sentences are demarcated by two spaces."
    (interactive "p")
    (if (use-region-p)
        (transpose-sentences 0)
      (transpose-sentences arg))
    (fill-paragraph))

  (defun prot/transpose-or-swap-words (arg)
    "If region is active, swap the word at mark (region
beginning) with the one at point (region end).

Otherwise, and while inside a sentence, this behaves as the
built-in `transpose-words', dragging forward the word behind the
point.  The difference lies in its behaviour at the end of a
line, where it will always transpose the word at point with the
one behind it (effectively the last two words).

This addresses two patterns of behaviour I dislike in the
original command:

1. When a line follows, `M-t' will transpose the last word of the
line at point with the first word of the line below.

2. While at the end of the line, `M-t' will not transpose the
last two words, but will instead move point one word backward.
To actually transpose the last two words, you need to invoke the
command twice."
    (interactive "p")
    (if (use-region-p)
        (transpose-words 0)
      (if (eq (point) (point-at-eol))
          (progn
            (backward-word 1)
            (transpose-words 1)
            (forward-char 1))
        (transpose-words arg))))

  (defun prot/unfill-region-or-paragraph (&optional region)
    "Join all lines in a region, if active, while respecting any
empty lines (so multiple paragraphs are not joined, just
unfilled).  If no region is active, operate on the paragraph.
The idea is to produce the opposite effect of both
`fill-paragraph' and `fill-region'."
    (interactive)
    (let ((fill-column most-positive-fixnum))
      (if (use-region-p)
          (fill-region (region-beginning) (region-end))
        (fill-paragraph nil region))))

  (defun prot/yank-replace-line-or-region ()
    "Replace the line at point with the contents of the last
stretch of killed text.  If the region is active, operate over it
instead.  This command can then be followed by the standard
`yank-pop' (default is bound to M-y)."
    (interactive)
    (if (use-region-p)
        (progn
          (delete-region (region-beginning) (region-end))
          (yank))
      (delete-region (point-at-bol) (point-at-eol))
      (yank)))
  :bind (("C-c C-=" . bjm/align-whitespace)
         ("C-x +" . balance-windows-area)
         ("C-S-w" . prot/copy-line)
         ("M-=" . count-words)
         ("s-k" . kill-this-buffer)
         ("M-k" . prot/kill-line-backward)
         ("C-S-SPC" . contrib/mark-whole-word)
         ("C-S-n" . prot/multi-line-next)
         ("C-S-p" . prot/multi-line-prev)
         ("<C-return>" . prot/new-line-below)
         ("<C-S-return>" . prot/new-line-above)
         ("M-SPC" . cycle-spacing)
         ("M-o" . delete-blank-lines)
         ("C-t" . prot/transpose-chars)
         ("C-x C-t" . prot/transpose-or-swap-lines)
         ("C-S-t" . prot/transpose-or-swap-paragraphs)
         ("C-x M-t" . prot/transpose-or-swap-sentences)
         ("M-t" . prot/transpose-or-swap-words)
         ("M-Q" . prot/unfill-region-or-paragraph)
         ("C-S-y" . prot/yank-replace-line-or-region)))

8.2 Crux (extra utilities for some useful tasks)

The package crux contains a number of useful functions. The ones I need are:

Convenient renaming
An easy way to rename the current file and its corresponding buffer. No need to switch to Dired, run a shell command, or something along those lines.
Dired open with
Allows us to easily invoke xdg-open (or equivalent on non-Linux systems) for the file at point. This means to use the default program for accessing the item (e.g. run VLC for the .mkv at point). Note that we can do this by default inside of a Dired buffer by running ! xdg-open RET, where ! is the key for running an arbitrary command for the [marked] file[s].
Duplicate line or region
Just copy the line at point and place a duplicate right below it. Does the same for a region, if active.
(use-package crux
  :ensure t
  :bind (("s-t" . crux-transpose-windows)
         ("C-c w" . crux-duplicate-current-line-or-region)
         ("<C-f2>" . crux-rename-file-and-buffer)
         :map dired-mode-map
         ("<M-return>" . crux-open-with)))

8.3 Go to last change

I could not find any built-in method of reliably moving back to the last change. Using the mark ring is always an option, but does not fill the exact same niche.

The C-z binding is disabled in the opening sections of this document. It minimises the Emacs GUI. A complete waste of an extremely valuable key binding!

(use-package goto-last-change
  :ensure t
  :bind ("C-z" . goto-last-change))

8.4 Compare last two kills

This comes from Marcin Borkowski's blog on diffing buffer fragments.

(use-package emacs
  :commands contrib/diff-last-two-kills
  :config
  (defun contrib/diff-last-two-kills ()
    "Put the last two kills to temporary buffers and diff them."
    (interactive)
    (let ((old (generate-new-buffer "old"))
          (new (generate-new-buffer "new")))
      (set-buffer old)
      (insert (current-kill 0 t))
      (set-buffer new)
      (insert (current-kill 1 t))
      (diff old new)
      (kill-buffer old)
      (kill-buffer new))))

9 History and desktop state

This section contains configurations for packages that are dedicated to the task of recording the state of various Emacs tools. As such, this section does not cover packages that may define functionality beyond the narrow confines of tracking and storing history. For example, Ivy's filtering mechanism (prescient) defines its own saved state, but it would not make sense to declare here, since it also plugs into various Ivy-specific items.

9.1 Persistent session

These are about the general state of Emacs.

9.1.1 Emacs server

The following uses the first running process as the one others may connect to. This means that calling emacsclient (with or without --create-frame), will share the same buffer list and data as the original running process. The server persists for as long as there is an Emacs frame attached to said server.

(use-package server
  :hook (after-init . server-start))

9.1.2 Emacs "desktop" (state of buffers)

What I find particularly useful is the ability to save the state I was in: the name and position of buffers, and the like. Emacs calls this state of affairs the "desktop". Preserving it saves me from any possible crash or when I need to close Emacs and re-launch it later (my hardware is limited, so I do not keep it running while I am away).

Overview of my settings:

  • Enable the mode that saves the "desktop", instructing it to load a small number of buffers at launch (desktop-restore-eager). The remainder of the buffer list will be loaded lazily.
  • Now we must tell it where to store the files it generates and how often it should save. Concerning the latter, the default is to store the state every time it changes. I find that a bit too much, so I set a timeout of five minutes of idleness.
  • Note the desktop-load-locked-desktop. By default, Emacs locks the desktop file while it runs. The lock is removed upon exiting. This is a safety mechanism. There are two cases where the lock can create issues:
    • Emacs has crashed, meaning that it exited abruptly and was not able to unlock the desktop. Upon re-lauch Emacs will prompt you whether to load the locked file. You normally want to answer affirmatively.
    • Emacs runs in daemon mode, where it does not ask questions upon loading. In this case the lock is ignored.
    • Because I am only affected by the former, I choose to disable the prompt and just load the thing directly. Otherwise, I would set it to nil.
  • Do not restore frame configurations. Causes problems with the way my themes are loaded. Besides, window layouts are not important to me, since I use the buffer-switching methods to move around.
  • Ask what to do in case the session has a newer file that the one it initially started out with (e.g. when a new frame runs in parallel to the older one).
(use-package desktop
  :config
  (setq desktop-auto-save-timeout 300)
  (setq desktop-dirname "~/.emacs.d/")
  (setq desktop-base-file-name "desktop")
  (setq desktop-files-not-to-save nil)
  (setq desktop-globals-to-clear nil)
  (setq desktop-load-locked-desktop t)
  (setq desktop-missing-file-warning t)
  (setq desktop-restore-eager 3)
  (setq desktop-restore-frames nil)
  (setq desktop-save 'ask-if-new)
  (desktop-save-mode 1))

9.2 Record history

9.2.1 Recentf (recent files and directories)

This is a built-in mode that keeps track of the files you have opened, allowing you go back to them faster. It can also integrate with a completion framework to populate their "virtual buffers" list.

A few words about the variables I configure:

  • Enable the mode and define the file it should use to store the list of files.
  • Allow only 10 items in the menu. This is used by the menu bar, which I disable by default.
  • Store up to 200 items at a time. The number is arbitrary but seems good enough for me to (a) find common items quickly, (b) do not keep track of everything I ever access.
  • Do not prepend a number to the first ten files that appear in the dedicated recentf buffer (accessible via recentf-open-files).

Now some notes on my extensions:

  • The functions whose name starts with "rjs" are intended to address a limitation in the original package that does not keep track of file name changes. With these we make sure that the list is updated any time a file is moved/renamed. My sole contribution to these functions is to append the recentf-cleanup function where appropriate, to ensure that only the new name is tracked, while the old is discarded.
  • The function that includes Dired buffers to the list, is extracted from the recentf-ext file on the Emacs Wiki. I use this in tandem with my completion framework's virtual buffers. This practically eliminates whatever need for a dedicated command to display recently-accessed directories (dired buffers).
(use-package recentf
  :config
  (setq recentf-save-file "~/.emacs.d/recentf")
  (setq recentf-max-menu-items 10)
  (setq recentf-max-saved-items 200)
  (setq recentf-show-file-shortcuts-flag nil)

  ;; rename entries in recentf when moving files in dired
  (defun rjs/recentf-rename-directory (oldname newname)
    ;; oldname, newname and all entries of recentf-list should already
    ;; be absolute and normalised so I think this can just test whether
    ;; oldname is a prefix of the element.
    (setq recentf-list
          (mapcar (lambda (name)
                    (if (string-prefix-p oldname name)
                        (concat newname (substring name (length oldname)))
                      name))
                  recentf-list))
    (recentf-cleanup))

  (defun rjs/recentf-rename-file (oldname newname)
    (setq recentf-list
          (mapcar (lambda (name)
                    (if (string-equal name oldname)
                        newname
                      oldname))
                  recentf-list))
    (recentf-cleanup))

  (defun rjs/recentf-rename-notify (oldname newname &rest args)
    (if (file-directory-p newname)
        (rjs/recentf-rename-directory oldname newname)
      (rjs/recentf-rename-file oldname newname)))

  (advice-add 'dired-rename-file :after #'rjs/recentf-rename-notify)

  (defun contrib/recentf-add-dired-directory ()
    "Include Dired buffers in the `recentf' list.  Particularly
useful when combined with a completion framework's ability to
display virtual buffers."
    (when (and (stringp dired-directory)
               (equal "" (file-name-nondirectory dired-directory)))
      (recentf-add-file dired-directory)))

  :hook ((after-init . recentf-mode)
         (dired-mode . contrib/recentf-add-dired-directory)))

9.2.2 Minibuffer

Keeps a record of actions involving the minibuffer.

(use-package savehist
  :config
  (setq savehist-file "~/.emacs.d/savehist")
  (setq history-length 10000)
  (setq savehist-save-minibuffer-history t)
  (savehist-mode 1))

9.2.3 Point (cursor position)

Just remember where the point is in any given file.

(use-package saveplace
  :config
  (setq save-place-file "~/.emacs.d/saveplace")
  (save-place-mode 1))

9.2.4 Backups

This section is subject to review.

And here are some settings pertaining to backups.

(use-package emacs
  :config
  (setq backup-directory-alist
        '(("." . "~/.emacs.d/backup/")))
  (setq backup-by-copying t)
  (setq version-control t)
  (setq delete-old-versions t)
  (setq kept-new-versions 6)
  (setq kept-old-versions 2)
  (setq create-lockfiles nil))