Protesilaos Stavrou
Philosopher. Polymath.

Emacs initialisation file (dotemacs)

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

Created: 2019-08-15
Updated: 2019-12-05, 14:20 EET.
See this file's upstream git history. Everything is part of my dotfile's repository.

Table of Contents

1 Overview

1.1 Canonical links to this document

1.2 What is this

My initialisation file adds MELPA to the list of package repositories and loads the file with my configurations. The latter is written using org-mode. It is represented by this document.

For reference, these are the contents of my init.el. In the source code, not the website version of this page, I make sure these are not part of an emacs-lisp block, so they are not accidentally parsed by the actual setup.

(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"))

The present document is an example 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.

I find this paradigm particularly helpful for sharing Emacs configurations with a wider audience that includes new or potential users (I started in early July 2019).

Code blocks are wrapped between #+BEGIN_SRC and #+END_SRC tags (not visible in the website version of this page), which can be quickly inserted with the key chord <s TAB. Alternatively, we can employ the following to pass a specific language to the block, accompanied by a corresponding keyboard shortcut (I got this from a Hacker News thread).

(setq org-structure-template-alist
      (cons '("se" "#+BEGIN_SRC emacs-lisp\n?\n#+END_SRC" "<src
  lang=\"emacs-lisp\">\n?\n</src>")
            org-structure-template-alist))

Now <se TAB will give you a block for emacs-lisp.

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 Void Linux. I use the Emacs package provided by my distro. I do not optimise for portability across different versions or operating systems.

1.4 Note about my methodology

I choose not to use many external packages until I familiarise myself with the defaults and/or with functionality I carefully introduce. 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 amx nicely complements the built-in completion mechanism when running the M-x prompt.

Though a former Vim user, I choose not to use evil-mode or similar implementations from the start. I want to do things differently in order to ultimately set on the best approach for my use case.

When I first switched to Emacs, in early July 2019, I kept my "dotemacs" separate from my dotfiles. The idea was not to break things until I could reach a stable state. My Emacs usage has evolved ever since to encompass workflows that were once covered by standalone CLI/TUI tools that were part of my dotfiles. For example, I now only use mu4e instead of mutt, or elfeed instead of newsboat.

As of November 12, 2019, Emacs is a core part of my dotfiles. The repo is still in a state of transition, as I am reviewing practically every aspect of it, both to accommodate Emacs and to optimise for my new distro: Void Linux.

1.5 Note about the use of the Super key

Some sections of this document assign functions to s-KEY. These are alternative ways of invoking common commands that are 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 or the 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.

1.7 COPYING

Copyright (c) 2019 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

2.1 Prepare use-package

This is a tool that streamlines the configuration of packages. Though this 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 such I have two ways of implementing 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.
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(eval-when-compile
  (require 'use-package))

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

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 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
  :custom
  (use-file-dialog nil)
  (use-dialog-box nil)
  (inhibit-splash-screen t)
  :config
  (menu-bar-mode -1)
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
  (global-unset-key (kbd "C-z"))
  (global-unset-key (kbd "C-x C-z"))
  (global-unset-key (kbd "C-h h")))

2.4 Default typeface

2.4.1 Font settings

I use Hack, which is a derivative of the venerable DejaVu Sans Mono. Hack is my favourite typeface overall, though I do not enjoy everything in the standard Hack distribution. The lack of a slab in the letter "i" can cause issues, the shape of "1" is exaggerated, the dot/spot inside the "0" is disproportionately large. Thankfully, upstream provides a repository with alternatives glyphs, for those who wish to build a variant themselves. I used this to do the following:

  • Use slabbed version of letter "i" only for the Regular and Bold variants. Italics and Bold Italics will use the default slab-less glyph.
  • Use a slab-less "1", which clearly disambiguates it from slabbed "i".
  • Use "3" with a flat top in Regular and Bold variants. Leave them unchanged for their italicised counterparts.
  • Use knife variant of "f" for the Italic and Bold Italic sets. The default glyph remains in tact for Regular and Bold.
  • Use dotted zero in Regular and Bold sets, while reverting to diamond zero for the italic variants. The use of a dot and a diamond is necessary to offer the impression of similarity between roman and italic variations.

Here is the git repo of my custom Hack font. It is available under the same terms as "Hack" itself (MIT License). Note that you should better remove any other build of the original typeface before using my mod. On Debian and Void Linux (and probably all other distros) per-user fonts are read from ~/.local/share/fonts/. If you are on Void, check my personal templates for xbps-src, which includes a build for this font that installs system-wide. Also refer to my dotfiles for the relevant fontconfig settings.

(use-package emacs
  :custom
  (x-underline-at-descent-line t)
  (underline-minimum-offset 1)
  :config
  (defun prot/font-family-size (family size)
    "Set frame font for FAMILY at SIZE."
    (set-frame-font (concat family "-" (number-to-string size)) 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 "Hack" 10.5)))

  (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 "Hack" 13)))

  (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/desktop-fonts)
      (prot/laptop-fonts))))

  :hook
  (after-init . prot/fonts-per-monitor))

And here is a typeface suitability test: can you discern the character at a quick glance? If yes, the font is good, else search for something else.

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

Sample character set
Check for monospacing and Greek glyphs

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

2.5 Persistent state

2.5.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))

I personally have no need for the server per se: I launch Emacs and keep it open for as long as I am on the computer. That is mostly there in case some external functionality calls the $EDITOR environment variable. Though, again, this has never happened in practice as I use Emacs for practically everything.

2.5.2 Emacs "desktop" (state of buffers)

What I find more 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
  :init
  (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)
  :config
  (desktop-save-mode 1))
2.5.2.1 TODO store window configuration registers (C-x r w)?

2.5.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
  :custom
  (custom-file "~/.emacs.d/custom.el")
  :hook (after-init . (lambda ()
                        (unless (file-exists-p custom-file)
                          (write-region "" nil custom-file))
                        (load custom-file))))

2.6 Record history

2.6.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 100 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
  :init
  (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)
  :config
  (recentf-mode 1)
  ;; Magic advice to rename entries in recentf when moving files in
  ;; dired.
  (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)))

  (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-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)

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

  (defun contrib/recentf-add-dired-directory ()
    "Include Dired buffers in the 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))))

2.6.2 Minibuffer

Keeps a record of actions involving the minibuffer.

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

2.6.3 Point (cursor position)

Just remember where the point is in any given file.

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

2.6.4 Backups

This section is subject to review.

And here are some settings pertaining to backups.

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

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

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 counsel-switch-buffer as if the kill had never occured.
  • ivy-re-builders-list allows us to specify the algorithm for matching candidates. Unless specified otherwise, I am using regexp matching by default.
  • 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.

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.

(use-package ivy
  :ensure t
  :delight
  :custom
  (ivy-count-format "(%d/%d) ")
  (ivy-height-alist '((t lambda (_caller) (/ (window-height) 4))))
  (ivy-use-virtual-buffers t)
  (ivy-wrap nil)
  (ivy-re-builders-alist
   '((counsel-M-x . ivy--regex-fuzzy)
     (t . ivy--regex-plus)))
  (ivy-display-style 'fancy)
  (ivy-use-selectable-prompt t)
  (ivy-set-occur 'ivy-switch-buffer 'ivy-switch-buffer-occur)
  (ivy-set-occur 'swiper 'swiper-occur)
  :config
  (ivy-mode 1)
  :hook
  (ivy-occur-mode . hl-line-mode)
  :bind (("<s-up>" . ivy-push-view)
         ("<s-down>" . ivy-switch-view)
         :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 AMX (M-x history and matches)

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
  :after ivy
  :custom
  (amx-backend 'auto)
  (amx-save-file "~/.emacs.d/amx-items")
  (amx-history-length 50)
  (amx-show-key-bindings nil)
  :config
  (amx-mode 1))
3.1.1.3 Counsel configurations and key bindings

Recall that counsel-M-x benefits from amx, as noted in the subsection above.

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

(use-package counsel
  :ensure t
  :after (ivy amx)
  :custom
  (counsel-yank-pop-preselect-last t)
  (counsel-yank-pop-separator "\n—————————\n")
  :config
  ;; 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)
         :map minibuffer-local-map
         ("C-r" . counsel-minibuffer-history)
         :map ivy-minibuffer-map
         ("s-y" . ivy-next-line)        ; Avoid 2× `counsel-yank-pop'
         ("C-SPC" . ivy-restrict-to-matches)))
3.1.1.4 Swiper

This probably is my least used tool out of the set: I only resort to swiper when I am not sure whether the item I am looking for is before or after the point. It also is useful for cases where a long list of matches is expected.

I think it does not provide a replacement for regular isearch because that can be used to quickly jump to a character, whereas Swiper's expanded preview can make the interface jump around unnecessarily.

Furthermore, the fact that Swiper searches using regular expressions by default is no real upside in my case, since I have already "hacked" isearch to do the same (see isearch enhancements).

(use-package swiper
  :ensure t
  :after ivy
  :custom
  (swiper-action-recenter t)
  (swiper-goto-start-of-match t)
  (swiper-include-line-number-in-search t)
  :bind (("C-S-s" . swiper)))

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.

(use-package ivy-rich
  :ensure t
  :config
  (setcdr (assq t ivy-format-functions-alist)
          #'ivy-format-function-line)
  (ivy-rich-mode 1))
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.

(use-package ivy-posframe
  :ensure t
  :delight
  :custom
  (ivy-posframe-height-alist
   '((swiper . 15)
     (t . 10)))
  (ivy-posframe-display-functions-alist
   '((complete-symbol . ivy-posframe-display-at-point)
     (counsel-describe-function . nil)
     (counsel-describe-variable . nil)
     (swiper . nil)
     (swiper-isearch . nil)
     (t . ivy-posframe-display-at-frame-center)))
  :config
  (ivy-posframe-mode 1))

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

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

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. This concerns regular searches (the standard C-s and C-r). The regexp functions remain in tact. You can toggle this behaviour, while performing a search, with M-s SPC (revert back to 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, 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.
Search for region
Populate the search prompt with the contents of the region. Select a word or a phrase that would be harder to otherwise type out and run a search. Got this snippet from a Reddit post on r/emacs.
DWIM delete non-match
The isearch+.el library provides a ton of additions to the search function. I do not need any of them, except the following snippet, which deletes the entire failed match or just the last character (whatever is appropriate). This removes the entirety of a mismatch, just by hitting backspace. 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. For that you should anyway be doing a proper edit with M-e.
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 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 word boundaries. For those cases moving to the opposite end requires multiple key presses. This function addresses the issue (bound to C-RET while running a successful search). The source is this forum answer.
(use-package isearch
  :init
  (setq search-whitespace-regexp ".*?")
  ;; Or use the following for greedy matches
  ;; (setq search-whitespace-regexp ".*")
  (setq isearch-lax-whitespace t)
  (setq isearch-regexp-lax-whitespace nil)
  :config
  (defun prot/isearch-mark-and-exit ()
    "Marks the current search string.  Can be used as a building
block for a more complex chain, such as to kill a region, or
place multiple cursors."
    (interactive)
    (push-mark isearch-other-end t 'activate)
    (setq deactivate-mark nil)
    (isearch-done))

  (defun stribb/isearch-region (&optional not-regexp no-recursive-edit)
    "If a region is active, make this the isearch default search
pattern."
    (interactive "P\np")
    (when (use-region-p)
      (let ((search (buffer-substring-no-properties
                     (region-beginning)
                     (region-end))))
        (message "stribb/ir: %s %d %d" search (region-beginning) (region-end))
        (setq deactivate-mark t)
        (isearch-yank-string search))))
  (advice-add 'isearch-forward-regexp :after 'stribb/isearch-region)
  (advice-add 'isearch-forward :after 'stribb/isearch-region)
  (advice-add 'isearch-backward-regexp :after 'stribb/isearch-region)
  (advice-add 'isearch-backward :after 'stribb/isearch-region)

  (defun contrib/isearchp-remove-failed-part-or-last-char ()
    "Remove failed part of search string, or last char if successful.
Do nothing if search string is empty to start with."
    (interactive)
    (if (equal isearch-string "")
        (isearch-update)
      (if isearch-success
          (isearch-delete-char)
        (while (isearch-fail-pos) (isearch-pop-state)))
      (isearch-update)))

  (defun contrib/isearch-done-opposite-end (&optional nopush edit)
    "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)
    (funcall #'isearch-done nopush edit)
    (when isearch-other-end (goto-char isearch-other-end)))

  ;; Uncomment if you want to auto-reset to "hacked" whitespace after
  ;; having toggled literal spaces (with `M-s SPC')
  ;;
  ;; :hook
  ;; (isearch-mode-end . (lambda ()
  ;;                        (setq search-whitespace-regexp ".*?")
  ;;                        (setq isearch-lax-whitespace t)))

  :bind (:map isearch-mode-map
              ("C-SPC" . prot/isearch-mark-and-exit)
              ("DEL" . contrib/isearchp-remove-failed-part-or-last-char)
              ("<C-return>" . contrib/isearch-done-opposite-end)))

3.3 Faster grep with deadgrep (ripgrep front-end)

This is a great alternative to grep. For me the main selling point is its improved speed. It also has some nice usability enhancements. To get this to work, you must first install the ripgrep binary, which is not an Emacs package. On Void Linux: xbps-install -S ripgrep.

The key chord is similar to occur. I just also keep F5 there in case I need it.

(use-package deadgrep
  :ensure t
  :bind (("M-s d" . deadgrep)
         ("<f5>" . deadgrep)))

4 Directory, project, 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.
  • 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.
(use-package dired
  :custom
  (dired-recursive-copies 'always)
  (dired-recursive-deletes 'always)
  (dired-isearch-filenames 'dwim)
  (delete-by-moving-to-trash t)
  (dired-listing-switches "-AFhlv --group-directories-first")
  (dired-dwim-target t)
  :hook
  (dired-mode . dired-hide-details-mode)
  (dired-mode . hl-line-mode))

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

(use-package async
  :ensure t)

(use-package dired-async
  :after (dired async)
  :config
  (dired-async-mode 1))

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
  :custom
  (dired-narrow-exit-when-one-left t)
  (dired-narrow-enable-blinking t)
  (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
  :init
  (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))
  :custom
  (peep-dired-cleanup-on-disable t)
  (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. On Void Linux install it with xbps-install -S 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
  :custom
  (image-dired-external-viewer "xdg-open")
  (image-dired-thumb-size 80)
  (image-dired-thumb-margin 2)
  (image-dired-thumb-relief 0)
  (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
  :custom
  (dgi-commit-message-format "%h\t%s\t%cr")
  :bind (:map dired-mode-map
              (")" . dired-git-info-mode)))

4.2 Git front-end (Magit)

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.

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

The following package is configured in accordance with the guidelines provided by this article on writing a Git commit message.

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

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 for just the focused hunk (there is an option for 'all).

My Modus themes are configured to style these appropriately.

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

4.3 Git project toolbox (Projectile)

This tool allows us to determine a directory tree with our "projects". In my case, these are all git repositories. It is then possible to perform a set of actions on a per-project level.

The :delight statement customises the minor mode's presentation to only show the project's name. For non-project buffers, a dash appears instead.

The key binding used here is just the common prefix to a list of key chords. You can learn about them with C-c p C-h (so append C-h).

(use-package projectile
  :ensure t
  :delight '(:eval (concat " " (projectile-project-name)))
  :init
  (setq projectile-project-search-path '("~/Git/Projects/"))
  (setq projectile-indexing-method 'alien)
  (setq projectile-enable-caching t)
  (setq projectile-completion-system 'ivy)
  :config
  (projectile-mode 1)
  :bind (("C-c p" . projectile-command-map)))

It is worth noting that Projectile is not a replacement for Dired. It rather complements it nicely, especially when working with both unversioned and git-controlled directories.

4.4 Working with 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 first displaying their path inside angled brackets, while stripping the part 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
  :custom
  (uniquify-buffer-name-style 'post-forward-angle-brackets)
  (uniquify-strip-common-suffix t)
  (uniquify-after-kill-buffer-p t))

4.4.1 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).
  • Remap default key to launch ibuffer instead of list-buffers.
  • Use colours for common actions more consistently. Inherit styles from Dired (see my Modus themes).
(use-package ibuffer
  :custom
  (ibuffer-expert t)
  (ibuffer-display-summary nil)
  (ibuffer-use-other-window nil)
  (ibuffer-deletion-face 'dired-flagged)
  (ibuffer-marked-face 'dired-marked)
  :bind ("C-x C-b" . ibuffer))

The following package groups buffers in relation to their version control project (I am already using projectile for that). While the visuals are more consistent this way, its true power lies in its ability to operate at once on a per-project basis. Just mark for deletion a project heading, confirm it, and boom all of the project's buffers are gone.

(use-package ibuffer-projectile
  :ensure t
  :after (ibuffer projectile)
  :hook
  (ibuffer . (lambda ()
               (ibuffer-projectile-set-filter-groups)
               (unless (eq ibuffer-sorting-mode 'recency)
                 (ibuffer-do-sort-by-recency)))))

4.5 Window configuration

I believe that Emacs' true power lies in its buffer management rather than its multiplexing, as I explain in my video about the Emacs way to buffer and window management.

That granted, the default experience can be further enhanced with a few careful configurations.

4.5.1 Basic tweaks

These key bindings are complementary to the standard ones. They do not replace the defaults, just provide faster access to their corresponding functions. They all involve the Super key (see the relevant note in the introductory section of this document).

(use-package window
  :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)))

Note that there also exists tear-off-window: it allows us to place the current window in its own frame. Search elsewhere in this document for the key I assign it to.

The default keys for delete-frame are C-x 5 0.

Remember—what Emacs call a "window" is the split of the viewport. Whereas a "frame" is the rectangle your window manager controls.

4.5.2 winner-mode

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

There also exist other functions for switching to a window in any of the cardinal directions. This is something I do not use, as I always work with two windows at a time (where other-window is all I need to move back and forth).

I have a video on how to manage window layouts in Emacs, but have since decided to just use registers for storing window layouts (instead of using the functions provided by Ivy). Use C-x r w and then a key to store the current layout to it. At any time you can jump to that layout with C-x r j KEY.

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

This section will expand gradually, as I continue to explore Org's seemingly infinite capabilities.

Overview of the following settings:

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.
(use-package org
  :custom
  (org-directory "~/Org")
  ;; code blocks
  (org-src-window-setup 'current-window)
  (org-src-fontify-natively t)
  (org-src-tab-acts-natively t)
  (org-confirm-babel-evaluate nil)
  (org-edit-src-content-indentation 0)
  ;; export
  (org-export-with-toc t)
  (org-export-headline-levels 8)
  (org-export-backends '(ascii html latex md))
  :config
  ;; 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 l" . org-store-link)))

5.1.1 Export Org to HTML

Use this package to output to HTML. While in the export interface, type C-b to only produce the HTML body (useful when embedding to an existing template/website).

(use-package htmlize
  :ensure t
  :after org
  :config
  (global-set-key (kbd "C-c o") (kbd "C-c C-e C-b h H")))

5.1.2 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
  :init
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
  :config
  (defun eos/org-custom-id-get (&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 eos/org-add-ids-to-headlines-in-file ()
    "Add CUSTOM_ID properties to all headlines in the
   current file which do not already have one."
    (interactive)
    (org-map-entries (lambda ()
                       (eos/org-custom-id-get (point) 'create))))
  :hook
  ;; automatically add ids to captured headlines
  (org-capture-prepare-finalize-hook . (lambda ()
                                         (eos/org-custom-id-get (point) 'create)))
  ;; automatically add ids to saved org-mode headlines
  (org-mode . (lambda ()
                (add-hook 'before-save-hook
                          (lambda ()
                            (when (and (eq major-mode 'org-mode)
                                       (eq buffer-read-only nil))
                              (eos/org-add-ids-to-headlines-in-file)))))))

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

The key binding that invokes the standard shell (s-RET) will take you back to the shell buffer if it exists. To always spawn a new shell use s-S-RET. The latter can be achieved without any configurations by running C-u M-x shell (but who has time for that?).

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 idiosyncracies 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 shell-command)
  :custom
  (ansi-color-for-comint-mode 'filter)
  :config
  (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)))))
      (split-window-sensibly)
      (other-window 1)
      (shell "new")
      (rename-buffer (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
  :custom
  (term-buffer-maximum-size 9999)
  (term-completion-autolist t)
  (term-completion-recexact t)
  (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.

(use-package proced
  :commands proced
  :custom
  (proced-toggle-auto-update t)
  (proced-auto-update-interval 1)
  (proced-descend t)
  (proced-filter 'user))

5.4 Pass interface (password-store)

The "pass" program, aka "password-store", is a password manager that uses GPG and standand 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. Install it on Void Linux with xbps-install -S pass.

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)
  :custom
  (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)

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
  :custom
  (elfeed-use-curl t)
  (elfeed-curl-max-connections 10)
  (elfeed-db-directory "~/.emacs.d/elfeed")
  (elfeed-enclosure-default-dir "~/Downloads")
  (elfeed-search-clipboard-type 'CLIPBOARD)
  (elfeed-search-title-max-width (current-fill-column))
  (elfeed-search-title-max-width 100)
  (elfeed-search-title-min-width 30)
  (elfeed-search-trailing-width 16)
  (elfeed-show-truncate-long-urls t)
  (elfeed-show-unique-buffers t)
  :config
  (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)
  :custom
  (browse-url-browser-function 'eww-browse-url)
  (shr-use-fonts nil)
  (shr-use-colors nil)
  (shr-max-image-proportion 0.2)
  (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
  :custom
  (bongo-default-directory "~/Music")
  (bongo-prefer-library-buffers nil)
  (bongo-insert-whole-directory-trees t)
  (bongo-logo nil)
  (bongo-action-track-icon nil)
  (bongo-display-track-icons nil)
  (bongo-display-track-lengths nil)
  (bongo-display-header-icons nil)
  (bongo-display-playback-mode-indicator t)
  (bongo-display-inline-playback-progress nil)
  (bongo-mark-played-tracks nil)
  (bongo-header-line-mode nil)
  (bongo-header-line-function nil)
  (bongo-mode-line-indicator-mode nil)
  (bongo-vlc-program-name "cvlc")
  :config
  (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 start bongo in the background when emacs launches

5.7.2 TODO save/load playlists

5.7.3 TODO read metadata

5.7.4 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 Void Linux: xbps-install -S mu mu4e.
  • The offlineimap utility to store a local copy of my email (from Void: xbps-install -S offlineimap). 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
  :custom
  (smtpmail-default-smtp-server "mail.gandi.net")
  (smtpmail-smtp-server "mail.gandi.net")
  (smtpmail-stream-type 'ssl)
  (smtpmail-smtp-service 465)
  (user-full-name "Protesilaos Stavrou")
  (auth-sources '("~/.authinfo.gpg" "~/.authinfo"))
  (epa-file-cache-passphrase-for-symmetric-encryption t))

(use-package smtpmail-async
  :after smtpmail
  :custom
  (send-mail-function 'async-smtpmail-send-it)
  (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.
(use-package mu4e
  :load-path "/usr/share/emacs/site-lisp/mu4e"
  :after (smtpmail smtpmail-async)
  :commands mu4e
  :custom
  (mail-user-agent 'mu4e-user-agent)
  (mu4e-get-mail-command "offlineimap")
  (mu4e-hide-index-messages t)
  (mu4e-update-interval nil)
  (mu4e-completing-read-function 'completing-read)
  (mu4e-compose-signature "Protesilaos Stavrou\nprotesilaos.com\n")
  (mu4e-compose-signature-auto-include t)
  (mu4e-maildir "~/Maildir")
  (mu4e-sent-messages-behavior 'sent)
  (message-kill-buffer-on-exit nil)
  (mu4e-view-show-addresses t)
  (mu4e-context-policy 'pick-first)
  (mu4e-compose-context-policy 'ask)
  :config
  (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))

To set up offlineimap I used the Arch Wiki entry.

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 targetted 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
  :custom
  (comment-empty-lines t)
  (comment-fill-column nil)
  (comment-multi-line t)
  (comment-style 'multi-line)
  :config
  (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 Spell check

I need spell checking for both English and Greek. Activation is automatic.

  • These settings are meant to check for mixed language content, so there is no need to changed dictionaries.
  • Note that hunspell is not part of Emacs. I install the relevant packages from the Void Linux archive (NOTE 2019-11-12: the Greek dictionary is missing, but I will try to make a package for it).
  • The value of ispell-local-dictionary-alist is based on the information provided in Chen Bin's blog.
  • The key binding C-; is disabled because I repurpose that for a faster version of C-x C-; (much more useful for my work—see the section on comments).
(use-package flyspell
  :init
  (setq flyspell-issue-message-flag nil)
  (setq flyspell-issue-welcome-flag nil)
  (setq ispell-program-name "hunspell")
  (setq ispell-local-dictionary "en_GB")
  (setq ispell-local-dictionary-alist
        '(("en_GB"
           "[[:alpha:]]"
           "[^[:alpha:]]"
           "[']"
           nil
           ("-d" "en_GB,el_GR")
           nil
           utf-8)))
  :config
  (define-key flyspell-mode-map (kbd "C-;") nil)
  :hook
  (text-mode . turn-on-flyspell)
  (prog-mode . turn-off-flyspell))

6.5 Markdown support

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

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

6.6 YAML support

This adds support for YAML files.

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

6.7 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.8 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 targetting specific programming languages and/or style conventions. It seems very powerful! As these targetted operations require some more research, I leave everything to its default value and will be updating the package declaration over time.

(use-package flycheck
  :ensure t
  :hook (emacs-lisp-mode . flycheck-mode))

6.8.1 Flycheck posframe (reposition flycheck's feedback)

While we can always hover over a Flycheck message or use a dedicated buffer in the form of flycheck-list-errors it can be more expedient to just rely to a "popup" that appears right below the point. This is what the following package declaration is all about.

(use-package flycheck-posframe
  :ensure t
  :after flycheck
  :hook (flycheck-mode-hook . flycheck-posframe-mode))

6.8.2 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))

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
  :init
  (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.

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

(use-package hippie-exp
  :after dabbrev
  :custom
  (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))
  (hippie-expand-verbose t)
  :bind ("M-/" . hippie-expand))

6.11 DISABLED YASnippet (templating system)

The code in this section is disabled until further review.

Yasnippet is a tool for defining and using code or text templates. The template can have several "stops", which are the mutable parts of it that accept user input.

It provides similar functionality to Emacs' built-in "skeletons". Its one major advantage, from my perspective, is that it has a strong community behind it, which has already written lots of useful templates for a variety of languages and scenaria (I use skeletons as well—see my simple abbreviations). These snippets are distributed as a separate package.

  • TODO document the use of custom snippets.

Note that :delight does not work without an argument here. It needs to target yas-minor-mode.

(use-package yasnippet
  :disabled
  :ensure t
  :delight yas-minor-mode
  :init
  (setq yas-snippet-dirs (append yas-snippet-dirs
                                 '("~/.emacs.d/snippets/")))
  :config
  (yas-global-mode 1))

(use-package yasnippet-snippets
  :disabled
  :ensure t
  :after yasnippet)

6.12 DISABLED Company (completion framework)

The code in this section is disabled until further review.

Company has a modular design that allows it to adapt to the user's needs. Additional backends are provided as separate packages.

Overview of the following settings.

  • Allow non-matching input. The cursor can thus be moved as expected while the pop-up menu with the suggestions is active.
  • Align annotations to the right.
  • Do not downcase completions. There is a function further below that allows lower casing only while filling completions. I found it in the README page of this package.
  • Show numbers for the ten matching candidates at the top of the list. Select them with M-NUM.
  • Wrap around the list of candidates (go back to start when reaching the end and vice versa).
  • 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.
  • Set minimum length to 3 and delay the pop-up by half a second. This avoids too many false positives or minor inconveniences that I encounter in my workflow (such as is => isearch).
  • Keep the completion candidates to ten at a time. This allows us to target them directly by their number (10 is 0 in this case).
  • Determine how completions are sorted. This is subject to review.
  • The function that includes Yasnippet to all backends is provided by Nasy's Emacs configuration file.

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 s-/ (that is the Super key). Bear in mind that this key is similar to M-/ which is used by the built-in Dabbrev tool (defined in a previous sections). I only use Dabbrev for those cases where Company does not seem to get the job done (probably because it requires further configuration).
  • While the list of suggestions is open, M-/ will switch to a different backend. This can help improve results for the task at hand.
  • The common part of completion suggestions can be inserted directly with TAB. Hit it twice to select the current item.
  • Alternatively, C-TAB inserts the selection right away.
  • C-n and C-p can be used to cycle the list of suggestions. I have configured it to also complete the common prefix while doing so (whereas the generic approach would be to just move up or down).

Additionally, you can also use C-s and C-r to perform a search inside the list of suggestions.

(use-package company
  :disabled
  :ensure t
  :after yasnippet
  :delight
  :init
  (setq company-require-match nil)
  (setq company-tooltip-align-annotations t)
  (setq company-dabbrev-downcase nil)
  (setq company-show-numbers t)
  (setq company-selection-wrap-around t)
  (setq company-auto-complete nil)
  (setq company-minimum-prefix-length 3)
  (setq company-idle-delay 0.5)
  (setq company-tooltip-limit 10)
  (setq company-transformers
        '(company-sort-by-backend-importance
          company-sort-prefer-same-case-prefix
          company-sort-by-occurrence))
  :config
  (global-company-mode 1)
  (defun jcs--company-complete-selection--advice-around (fn)
    "Enable downcase only when completing the completion.  Advice
execute around `company-complete-selection' command."
    (let ((company-dabbrev-downcase t))
      (call-interactively fn)))
  (advice-add 'company-complete-selection :around #'jcs--company-complete-selection--advice-around)

  (defun contrib/company-backend-with-yas (backends)
    "Add :with company-yasnippet to company BACKENDS.
  Taken from https://github.com/syl20bnr/spacemacs/pull/179."
    (if (and (listp backends) (memq 'company-yasnippet backends))
        backends
      (append (if (consp backends)
                  backends
                (list backends))
              '(:with company-yasnippet))))
  ;; add yasnippet to all backends
  (setq company-backends
        (mapcar #'contrib/company-backend-with-yas company-backends))
  :bind (:map company-mode-map
              ("s-/" . company-manual-begin)
              :map company-active-map
              (("s-/" . company-other-backend)
               ("C-d" . company-show-doc-buffer)
               ("<tab>" . company-complete)
               ("<C-tab>" . company-complete-selection)
               ("C-n" . (lambda ()
                          (interactive)
                          (company-complete-common-or-cycle 1)))
               ("C-p" . (lambda ()
                          (interactive)
                          (company-complete-common-or-cycle -1))))))

7 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
  :custom
  (custom-safe-themes t)
  :config
  (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)
        (load-theme 'modus-vivendi t)
      (load-theme 'modus-operandi t)))
  :bind ("s-t" . prot/modus-themes-toggle)
  :hook (after-init . (lambda () (load-theme 'modus-operandi t))))

8 Interface and interactions

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

8.1 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
  :custom
  (vc-follow-symlinks t)
  (frame-title-format '("%b %& GNU Emacs"))
  (echo-keystrokes 0.25)
  (auto-revert-verbose nil)
  (default-input-method "greek")
  (ring-bell-function 'ignore)
  :config
  (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))

8.2 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 highlight the expression enclosed by it.
  • 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
  :custom
  (show-paren-style 'mixed)
  (show-paren-when-point-in-periphery t)
  (show-paren-when-point-inside-paren nil)
  :config
  (show-paren-mode 1))

8.3 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.
  • 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 standard double quotes, their fancy (curly) equivalents, and these «». The contents of this set are always inserted in pairs, regardless of major mode. I do not add straight single quotes or backticks because of their importance in elisp.
  • When 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.
  • 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 <" , >", <', >'.
(use-package electric
  :custom
  (electric-pair-inhibit-predicate 'electric-pair-default-inhibit)
  (electric-pair-pairs '((8216 . 8217)
                         (8220 . 8221)
                         (171 . 187)))
  (electric-pair-skip-self 'electric-pair-default-skip-self)
  (electric-quote-context-sensitive t)
  (electric-quote-paragraph t)
  (electric-quote-string nil)
  :config
  (electric-indent-mode 1)
  (electric-pair-mode 1)
  (electric-quote-mode -1))

8.4 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
  :init
  (setq-default tab-always-indent 'complete)
  (setq-default tab-width 4)
  (setq-default indent-tabs-mode nil))

8.5 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
  :custom
  (cursor-type 'box)
  (cursor-in-non-selected-windows 'hollow)
  (x-stretch-cursor nil))

8.6 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
  :custom
  (fill-column 72)
  (sentence-end-double-space t)
  (sentence-end-without-period nil)
  (colon-double-space nil)
  :config
  (column-number-mode 1))

8.7 Delete trailing whitespace

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

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

8.8 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
  :custom
  (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).

8.9 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
  :init
  (setq mouse-drag-copy-region t)
  (setq make-pointer-invisible t)
  (setq mouse-wheel-progressive-speed t)
  :config
  (mouse-wheel-mode 1))

8.10 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
  :config
  (delete-selection-mode 1))

8.11 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-centering 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
  :custom
  (scroll-preserve-screen-position t)
  (scroll-conservatively 1)
  (scroll-margin 0))

8.12 TODO tool tips

8.13 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
  :custom
  (repeat-on-final-keystroke t)
  (set-mark-command-repeat-pop t)
  :bind ("M-z" . zap-up-to-char))

8.14 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-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)))

  (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)))
  :bind (("<f7>" . prot/toggle-line-numbers)
         ("<f8>" . prot/toggle-invisibles)))

8.15 Multiple cursors

For me, this package is useful for quickly operating on multiple positions within the same viewport (use occur, keyboard macros, regexp replacement etc. for more demanding tasks). Here I only include the actions that I find useful for this task. Note that I do not rely on this package for complex tasks, because it does not scale well. Read this detailed analysis on the matter by Chris Wellons.

(use-package multiple-cursors
  :ensure t
  :bind (("C->" . mc/mark-next-like-this)
         ("C-<" . mc/mark-previous-like-this)
         ("s->" . mc/mark-all-like-this) ; Use with narrow commands
         ("C-S-<mouse-1>" . mc/add-cursor-on-click)))

8.16 Broadcast mode

This package provides a mode for syncing input across buffers. The way to link the affected buffers is to run M-x broadcast-mode inside each of them. For this to work, the buffers must both be visible.

(use-package broadcast
  :ensure t)

8.17 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
  :custom
  (rainbow-blocks-highlight-braces-p t)
  (rainbow-blocks-highlight-brackets-p t)
  (rainbow-blocks-highlight-parens-p t))

8.18 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
  :custom
  (rainbow-ansi-colors nil)
  (rainbow-x-colors nil))

8.19 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 of 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).

(use-package which-key
  :ensure t
  :delight
  :custom
  (which-key-show-early-on-C-h t)
  (which-key-idle-delay 10000)
  (which-key-idle-secondary-delay 0.05)
  (which-key-popup-type 'side-window)
  (which-key-show-prefix 'echo)
  (which-key-max-display-columns 3)
  (which-key-separator " ")
  (which-key-special-keys '("SPC" "TAB" "RET" "ESC" "DEL"))
  :config
  (which-key-mode 1))

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

9.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 a. Got it from this Pragmatic Emacs blog post.
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.
Tear off window (place in frame)
This will place the current window in a new frame. Recall that in Emacs parlance "windows" are the splits that contain buffers, while "frames" are the nodes that are controlled by your operating system's window manager (and usually have the "close", "minimise", "maximise" buttons at a top corner).
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)
    (previous-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)
      (progn
        (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))
      (progn
        (delete-region (point-at-bol) (point-at-eol))
        (yank))))

  :bind (("C-c a" . bjm/align-whitespace)
         ("C-S-w" . prot/copy-line)
         ("M-=" . count-words)
         ("s-k" . kill-this-buffer)
         ("C-S-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)
         ("<f6>" . tear-off-window)
         ("C-t" . prot/transpose-chars)
         ("C-x C-t" . prot/transpose-or-swap-lines)
         ("C-S-t" . prot/transpose-or-swap-paragraphs)
         ("C-x 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)))

9.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 (("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)))

9.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))

9.4 Window actions

Pierre Neidhardt has released a package called "Windower". It implements several window management techniques that should be familiar to anyone who has used a tiling window manager. As I only need a couple of those functions, I extract them and place them here.

(use-package emacs
  :config
  (defun windower-toggle-single ()
    "Un-maximize current window.
If multiple windows are active, save window configuration and
delete other windows.  If only one window is active and a window
configuration was previously save, restore that configuration."
    (interactive)
    (if (= (count-windows) 1)
        (when windower--last-configuration
          (set-window-configuration windower--last-configuration))
      (setq windower--last-configuration (current-window-configuration))
      (delete-other-windows)))

  (defun windower-toggle-split ()
    "Switch between vertical and horizontal split.
It only works for frames with exactly two windows."
    (interactive)
    (if (= (count-windows) 2)
        (let* ((this-win-buffer (window-buffer))
               (next-win-buffer (window-buffer (next-window)))
               (this-win-edges (window-edges (selected-window)))
               (next-win-edges (window-edges (next-window)))
               (this-win-2nd (not (and (<= (car this-win-edges)
                                           (car next-win-edges))
                                       (<= (cadr this-win-edges)
                                           (cadr next-win-edges)))))
               (splitter
                (if (= (car this-win-edges)
                       (car (window-edges (next-window))))
                    'split-window-horizontally
                  'split-window-vertically)))
          (delete-other-windows)
          (let ((first-win (selected-window)))
            (funcall splitter)
            (if this-win-2nd (other-window 1))
            (set-window-buffer (selected-window) this-win-buffer)
            (set-window-buffer (next-window) next-win-buffer)
            (select-window first-win)
            (if this-win-2nd (other-window 1))))))
  :bind (("s-m" . windower-toggle-single) ; toggle maximisation
         ("s-s" . windower-toggle-split)))

9.5 Compare last two kills

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

(use-package emacs
  :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)))
  :bind ("C-c d" . contrib/diff-last-two-kills))