Protesilaos Stavrou
Philosopher. Polymath.

Emacs initialisation file (dotemacs)

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

Created: 2019-08-15
Updated: 2020-02-18, 11:17 +0200.
See this file's upstream git history. Everything is part of my dotfiles' repository.

Table of Contents

1 Overview

1.1 Canonical links to this document

1.2 What is this

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

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

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

1.2.1 Contents of my init.el

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

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

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

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

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

(require 'package)

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

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

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

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

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

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

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

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

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

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

1.2.2 About `use-package'

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

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

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

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

The two main functions of use-package:

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

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

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

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

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

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

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

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

1.2.3 About the source code version of this document

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

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

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

1.3 Where I run Emacs

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

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

1.4 Note about my methodology

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

The process of learning the internals of Emacs means that I will, at times, write my own elisp functions. External packages that I do use are either a clear upgrade over the defaults or otherwise extend the functionality of what is already available. You will not find any superficialities herein: no rainbow-coloured mode lines, no icons, nothing.

Though a former Vim user, I decided not to use evil-mode or any kind of Vi emulation. I wanted to do things differently in order to ultimately set on the best approach going forward. I have eventually settled on a system that builds on top of the "Emacs way" to key bindings. I believe that a mnemonics-based set of keys is easier to memorise and to expand considerably, especially in light of Emacs' multitude of applications beyond the narrow confines of editing text.

1.5 Note about the use of the Super key

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

To find all my keybindings of this sort in the source code version of this document from inside of Emacs, do M-s o (or M-x occur) followed by the pattern "s-[a-z]".

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

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

1.6 Note about the source file

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

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

1.7 COPYING

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

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

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

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

2 Base settings

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

2.1 Disable GUI components

Overview of these settings:

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

2.2 Edit modeline "lighters"

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

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

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

2.3 Custom.el

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

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

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

  (load custom-file))

2.4 Base typeface configurations

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

2.4.1 Primary font settings

Any font I use must support Latin and Greek character sets, offer both roman and italic variants, be readable at both small and large sizes, not be too thin, not have too short of an x-height, not be too compact, not have a name that directly advertises some brand, and be equally readable against light and dark backdrops.

While there are many good free/libre options available, none of them copes well with my fairly demanding needs. Some look good at large point sizes. Others lack Greek characters. While a few of them are practically unreadable when cast on a light background (bitmaps fonts in particular).

As such, I opt for my custom build of the Hack typeface. It is a true workhorse of a font. It works well in every context I have ever used it in.

Honourable mentions of libre typefaces that I could use, but have some fault that puts them below Hack in my order of preference:

Typeface Subjective faults
DejaVu Sans Mono Minor details.
Iosevka (extended) Smaller glyphs.
Fira Mono/Code No italics.
Source Code Pro Too thin.
Mononoki Too fancy.
Victor Mono Work-in-progress.

Moving on to my configurations, the functions that follow the pattern prot/SCOPE-font allow me to conveniently define the font settings I need to use in the given context.

Each font declaration can accept several fontconfig parameters, as shown in prot/fixed-pitch-params. Read the relevant spec for further details, while also noting that only some of these are supported by most typefaces. The parameters we can pass here are exceptions to environment-wide rules that one can specify for fontconfig, at the user or system level.

For all my font-related settings, consult my dotfiles. Note also that on a modern GNU/Linux system per-user fonts are preferably read from ~/.local/share/fonts/.

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

  (defconst prot/fixed-pitch-font "Hack"
    "The default fixed-pitch typeface.")

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

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

  (defun prot/laptop-fonts ()
    "Pass desired argument to `prot/font-sizes' for use on my
small laptop monitor."
    (interactive)
    (when window-system
      (prot/default-font prot/fixed-pitch-font 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/default-font prot/fixed-pitch-font 11)))

  (defun prot/screencast-fonts ()
    "Pass desired argument to `prot/font-sizes' for use during
screen casting."
    (interactive)
    (when window-system
      (prot/default-font prot/fixed-pitch-font 14)))

  (defun prot/presentation-fonts ()
    "Pass desired argument to `prot/font-sizes' for use during
presentations.  Also see `prot/org-presentation'."
    (interactive)
    (when window-system
      (prot/default-font prot/fixed-pitch-font 20)))

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

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

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

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

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

Sample character set
Check for monospacing and Greek glyphs

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

3 Selection candidates and search methods

3.1 Completion framework and extras

As discussed in my video about Emacs' buffer and window management, the optimal way of using Emacs is through searching and narrowing selection candidates. Spend less time worrying about where things are on the screen and more on how fast you can bring them into focus. This is, of course, a matter of realigning priorities, as we still wish to control every aspect of the interface.

Since the day I switched to Emacs (July 2019), I was using some completion framework other than the default. I started out with ivy and its companion packages, switched to the built-in ido and then went back to the former. I never experimented with any sort of customisations to the generic minibuffer experience. Nor did I ever bother with the oldest built-in tool of the sort (icomplete) that is designed to complement the minibuffer's internal mechanisms for matching items. Not until ~10 February 2020…

It turns out that, despite appearances to the contrary, the defaults are very powerful, opening up a range of possibilities to those eager to learn and experiment (a common theme in Emacs).

In the following package declarations I am defining several functions that enhance the experience of icomplete. These are part of a learning process to (i) explore the internal of Emacs and study how various problems are solved with elisp, and (ii) determine how far one can go, in terms of efficient functionality, without deviating from the norms inherent to the tools that are shipped with Emacs.

What this also means is that I am deprecating Ivy and its dependants as well as reviewing any other package that expected their presence or somehow contributed to them. I do understand, however, that some users may still need to see the code and customisations I had for those, thus I am keeping everything in place with the :disabled keyword. Everything is under the heading of important configurations that I stopped using.

3.1.1 Minibuffer essentials and Icomplete (built-in completion)

The minibuffer is the locus of extended command interaction. Whether it is about offering input to a prompt, performing a search, executing a function by its name, the minibuffer remains at the epicentre. The default experience is far more powerful than it seems to be. It can get even better by tweaking the available customisation options and defining our own extensions.

While icomplete is the tool that offers incremental completion feedback for what the minibuffer is doing (where appropriate). There is no added layer of complexity. Just a visualisation of what is going on under the hood. As such, icomplete is designed with the generic minibuffer in mind. The two are meant to work in tandem in accordance with the design conventions of the upstream Emacs distribution.

Now some comments about my implementation:

  • The key bindings in the pattern of s-KEY follow the principles I outlined in my introductory note about the use of the Super key.
  • The flex option in completion-styles and relevant places is only available for Emacs versions after 27. As of this writing (2020-02-10) the current stable release is 26.3.
  • The completion-category-overrides provide exceptions to the fallback completion styles.
  • The completions-format concerns the layout of the *Completions* buffer that pops up after trying to complete a non-unique match. By default, it can be focused directly with M-v while inside an icomplete prompt.
  • To enhance the experience of the Completions buffer, I define several keys that make motions easier and consistent with other read-only interfaces. The h key calls a command of mine for seeking help for the item at point, typically a function or a variable. I also define M-v to take you back to the minibuffer while inside the *Completions* (and s-v to do it from anywhere else).
    • The placement of the Completions, Help, and other buffers is defined in detail in the section about Window rules and basic tweaks, specifically within the display-buffer-alist.
  • I enable recursive minibuffers. This practically means that you can start something in the minibuffer, switch to another window, call the minibuffer again, run some commands, and then move back to what you initiated in the original minibuffer. To exit such recursive edits, hit C-] (abort-recursive-edit). The minibuffer-depth-indicate-mode will show an indicator next to the minibuffer prompt if a recursive edit is in progress.
  • The values of all variables that pertain to the delay of feedback are tentative. My initial tests suggest that they behave exactly the way I want, but this might change once I test them further.
  • For versions of Emacs above 27, there is a mode called fido (Fake IDO, where ido is an alternative option). This changes some of the primary key bindings and commands of icomplete so that it meets the expectations of Ido users. It is not meant as a fully fledged replacement for Ido, as its scope is much narrower (for the time being). If you are curious, check the source code for both icomplete and ido with M-x find-library.
  • All my functions that somehow extend the functionality of Icomplete have their own documentation. No need to reproduce it here.

An important note about every prot/icomplete-* function that contains a setq in its body: the way the variables are declared is not robust, because it assumes default values that may not exist. I am aware that it is possible to perform tests inside a setq and return their result, but this added complexity is not needed for my case: I know the default values of those variables and keep them constant. If the variables need to be changed, then the functions will be updated accordingly. At any rate, some examples of better toggle design can be discovered in the isearch.el library with M-x find-library isearch.

Also check my configurations pertaining to the minibuffer history. I am still not sure whether we need a scoring mechanism like prescient or amx. My first few rounds of testing suggest that we do not, but this might still be proven otherwise, in which case I will just edit things accordingly.

Now here is the actual code for the minibuffer part:

(use-package minibuffer
  :config
  (setq completion-cycle-threshold 3)
  (setq completion-flex-nospace nil)
  (setq completion-pcm-complete-word-inserts-delimiters t)
  (setq completion-pcm-word-delimiters "-_./:| ")
  ;; NOTE: flex completion is introduced in Emacs 27
  (setq completion-styles '(partial-completion substring initials flex))
  (setq completion-category-overrides
        '((file (styles initials basic))
          (buffer (styles initials basic))
          (info-menu (styles basic))))
  (setq completions-format 'vertical)   ; *Completions* buffer
  (setq enable-recursive-minibuffers t)
  (setq read-answer-short t)
  (setq read-buffer-completion-ignore-case t)
  (setq read-file-name-completion-ignore-case t)

  (file-name-shadow-mode 1)
  (minibuffer-depth-indicate-mode 1)
  (minibuffer-electric-default-mode 1)

  (defun prot/describe-symbol-at-point (&optional arg)
    "Get help (documentation) for the symbol at point.

With a prefix argument, switch to the \\*Help\\* window.  If that
is already focused, switch to the most recently used window
instead."
    (interactive "P")
    (let ((symbol (symbol-at-point)))
      (when symbol
        (describe-symbol symbol)))
    (when current-prefix-arg
      (let ((help (get-buffer-window "*Help*")))
        (when help
          (if (not (eq (selected-window) help))
              (select-window help)
            (select-window (get-mru-window)))))))

  (defun prot/focus-minibuffer ()
    "Focus the active minibuffer.

Bind this to `completion-list-mode-map' to M-v to easily jump
between the list of candidates present in the \\*Completions\\*
buffer and the minibuffer (because by default M-v switches to the
completions if invoked from inside the minibuffer."
    (interactive)
    (let ((mini (active-minibuffer-window)))
      (when mini
        (select-window mini))))

  (defun prot/focus-minibuffer-or-completions ()
    "Focus the active minibuffer or the \\*Completions\\*.

If both the minibuffer and the Completions are present, this
command will first move per invocation to the former, then the
latter, and then continue to switch between the two.

The continuous switch is essentially the same as running
`prot/focus-minibuffer' and `switch-to-completions' in
succession."
    (interactive)
    (let* ((mini (active-minibuffer-window))
           (completions (get-buffer-window "*Completions*")))
      (cond ((and mini
                  (not (minibufferp)))
             (select-window mini nil))
            ((and completions
                  (not (eq (selected-window)
                           completions)))
             (select-window completions nil)))))

  :bind (("s-f" . find-file)
         ("s-F" . find-file-other-window)
         ("s-d" . dired)
         ("s-D" . dired-other-window)
         ("s-b" . switch-to-buffer)
         ("s-B" . switch-to-buffer-other-window)
         ("s-h" . prot/describe-symbol-at-point)
         ("s-H" . (lambda ()
                      (interactive)
                      (let ((current-prefix-arg t))
                        (prot/describe-symbol-at-point))))
         ("s-v" . prot/focus-minibuffer-or-completions)
         :map completion-list-mode-map
         ("h" . prot/describe-symbol-at-point)
         ("n" . next-line)
         ("p" . previous-line)
         ("f" . next-completion)
         ("b" . previous-completion)
         ("M-v" . prot/focus-minibuffer)))

And the following is for the completion interface.

Note that while running a shell, you can still use icomplete by means of tab-completion, but to confirm a choice you need to hit C-m. Hitting RET after succesful tab completions will just give you the final part of the candidate. I have yet to figure out why we cannot lock in the entire sequence and why my prot/icomplete-force-complete-and-exit does not work as intended.

(use-package icomplete
  :demand
  :config
  (setq icomplete-delay-completions-threshold 500)
  (setq icomplete-max-delay-chars 2)
  (setq icomplete-compute-delay 0.2)
  (setq icomplete-show-matches-on-no-input t)
  (setq icomplete-hide-common-prefix nil)
  (setq icomplete-prospects-height 1)
  (setq icomplete-separator " │ ")
  (setq icomplete-with-completion-tables t)
  (setq icomplete-in-buffer t)

  (fido-mode -1)                        ; Emacs 27.1
  (icomplete-mode 1)

  (defun prot/icomplete-show-vertical (&optional str)
    "Allow `icomplete' to present results vertically.

This is meant to be used by other functions that need to show
their results as a vertical list, with an optional string marking
the demarcation line.

For an interactive version see `prot/icomplete-toggle-vertical'."
    (setq resize-mini-windows 'grow-only)
    (setq icomplete-prospects-height 10)
    (if str
        (setq icomplete-separator
              (concat "\n" (propertize str 'face 'shadow) "\n "))
      (setq icomplete-separator "\n ")))

  (defun prot/icomplete-restore-horizontal ()
    "Restore `icomplete' to its horizontal layout.

This is meant to be run by the `minibuffer-exit-hook'."
    (unless (string= icomplete-separator " │ ")
      (setq icomplete-prospects-height 1)
      (setq icomplete-separator " │ ")))

  (defun prot/icomplete-recentf ()
    "Open `recent-list' item in a new buffer.

The user's $HOME directory is abbreviated as a tilde."
    (interactive)
    (prot/icomplete-show-vertical)
    (let ((files (mapcar 'abbreviate-file-name recentf-list)))
      (find-file
       (completing-read "Open recentf entry: " files nil t))))

  (defun prot/icomplete-font-family-list ()
    "Add item from the `font-family-list' to the `kill-ring'.

This allows you to save the name of a font, which can then be
used in commands such as `set-frame-font'."
    (interactive)
    (prot/icomplete-show-vertical)
    (kill-new
     (completing-read "Copy font family: "
                      (print (font-family-list))
                      nil t)))

  (defun prot/icomplete-yank-kill-ring ()
    "Insert the selected `kill-ring' item directly at point.

Defaults to a vertical layout.  This is restored on exit by means
of `prot/icomplete-restore-horizontal'."
    (interactive)
    (prot/icomplete-show-vertical "··········")
    (insert
     (completing-read "Yank from kill ring: " kill-ring nil t)))

  (defun prot/icomplete-toggle-vertical ()
    "EXPERIMENTAL Toggle vertical view for `icomplete'.

This is intended as a temporary adjustment of the layout,
possibly to read a list of long names.  It is for this reason
that `prot/icomplete-restore-horizontal' exists and is called by
the `minibuffer-exit-hook'.

NOTE: there still needs to be a way to show the minibuffer input
on its own line while also displaying the list of candidates."
    (interactive)
    (when (active-minibuffer-window)
      (if (not (string= icomplete-separator "\n "))
          (progn
            (setq-local icomplete-prospects-height 10)
            (setq-local icomplete-separator "\n ")
            (setq-local resize-mini-windows 'grow-only))
        (setq icomplete-prospects-height 1)
        (setq icomplete-separator " │ ")
        (setq-local resize-mini-windows t))))

  (defun prot/icomplete-toggle-flex ()
    "Toggle between flex and partial-completion (regexp)."
    (interactive)
    (if (eq (car completion-styles) 'partial-completion)
        (progn
          (setq-local completion-styles '(flex initials substring partial-completion))
          (message "%s" (propertize "Prioritising FLEX" 'face 'highlight)))
      (setq-local completion-styles '(partial-completion substring initials flex))
      (message "%s" (propertize "Prioritising PREFIX REGEXP" 'face 'highlight))))

  (defun prot/icomplete-toggle-basic ()
    "Toggle between basic and partial-completion (regexp)."
    (interactive)
    (if (eq (car completion-styles) 'partial-completion)
        (progn
          (setq-local completion-styles '(basic))
          (message "%s" (propertize "Prioritising BASIC matching" 'face 'highlight)))
      (setq-local completion-styles '(partial-completion substring initials flex))
      (message "%s" (propertize "Prioritising PREFIX REGEXP" 'face 'highlight))))

  (defun prot/icomplete-force-complete-and-exit ()
    "Complete the current `icomplete' match and exit the minibuffer.

Contrary to `icomplete-force-complete-and-exit', this will
confirm your choice without complaining about incomplete matches.

Those incomplete matches can block you from performing legitimate
actions, such as defining a new tag in an `org-capture' prompt.

In my testing, this is necessary when the variable
`icomplete-with-completion-tables' is non-nil, because then
`icomplete' will be activated practically everywhere it can."
    (interactive)
    (icomplete-force-complete)
    (exit-minibuffer))

  (defun prot/icomplete-kill-ring-save (&optional arg)
    "Expand and save current `icomplete' match to the kill ring.

With a prefix argument, insert the match to the point in the
current buffer and exit the recursive edit (or just close the
minibuffer)."
    (interactive "*P")
    (when (and (active-minibuffer-window)
               (minibufferp)
               (bound-and-true-p icomplete-mode))
      (icomplete-force-complete)
      (kill-new (field-string-no-properties))
      (when current-prefix-arg
        (kill-new (field-string-no-properties))
        (select-window (get-mru-window))
        (insert (car kill-ring))
        (prot/focus-minibuffer)
        (exit-recursive-edit))))

  :hook (minibuffer-exit . prot/icomplete-restore-horizontal)
  :bind (("s-y" . prot/icomplete-yank-kill-ring)
         ("s-r" . prot/icomplete-recentf)
         :map icomplete-minibuffer-map
         ("C-n" . icomplete-forward-completions)
         ("<right>" . icomplete-forward-completions)
         ("<down>" . icomplete-forward-completions)
         ("C-p" . icomplete-backward-completions)
         ("<left>" . icomplete-backward-completions)
         ("<up>" . icomplete-backward-completions)
         ("<return>" . prot/icomplete-force-complete-and-exit)
         ("M-o w" . prot/icomplete-kill-ring-save)
         ("M-o i" . (lambda ()
                      (interactive)
                      (let ((current-prefix-arg t))
                        (prot/icomplete-kill-ring-save))))
         ("C-M-|" . prot/icomplete-toggle-vertical)
         ("C-M-," . prot/icomplete-toggle-flex)
         ("C-M-." . prot/icomplete-toggle-basic)))

3.1.2 Completion for projects and directory trees

These are a set of commands for interacting with version-controlled directories, aka "projects", or directory trees in general. With these I have no need for the third-party "Projectile" package.

Some of the functions furnished herein are built into Emacs, while others are defined by me to satisfy my particular needs.

Everything I have here presupposes a completion framework, so make sure to check the previous section on Minibuffer essentials and Icomplete.

Concerning the design of these key bindings, they are consistent with all "advanced search methods" (e.g. the default M-s o for occur).

Note that project-find-regexp produces an xref buffer from where one can run a query-replace on the results by hitting r. If the intention is to make complex changes, consider project-query-replace-regexp instead. There also are other techniques which are project-agnostic, such as multi-occur, ibuffer-do-occur, dired-do-find-regexp-and-replace. Read their respective docs (with C-h f FUNCTION).

(use-package project
  :config
  (defun prot/find-file-from-dir-recursive ()
    "WORK IN PROGRESS"
    (interactive)
    (let* ((file-list (directory-files-recursively default-directory "" nil))
           (files (mapcar 'abbreviate-file-name file-list)))
      (find-file
       (completing-read "Find file recursively: " files nil t))))

  (defvar prot/projects-dir "~/Git/Projects/"
    "Root directory that hosts all my Git projects.")

  (defun prot/find-project ()
    "Use `dired' for sub-directory at `prot/projects-dir'.

Allows you to switch directly to the root directory of a project
inside a given location."
    (interactive)
    (let* ((dotless directory-files-no-dot-files-regexp)
           (project-list (project-combine-directories
                          (directory-files prot/projects-dir t dotless)))
           (projects (mapcar 'abbreviate-file-name project-list)))
      (dired
       (completing-read "Find project: " projects nil t))))

  :bind (("M-s p" . prot/find-project)
         ("M-s f" . project-find-file)
         ("M-s z" . prot/find-file-from-dir-recursive)
         ("M-s r" . project-find-regexp)
         ("M-s C-M-%" . project-query-replace-regexp)))

3.1.3 In-buffer completions

3.1.3.1 Dabbrev and hippie-expand (dynamic word completion)

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

After trying the popular third-party "Company" package, I felt that it does not offer me much of an added value, while its popup feature detracted from the otherwise frugal aesthetics of my setup. Whereas Dabbrev works in a way I find intuitive enough, while it remains minimalist in its presentation.

To learn about hippie-expand-try-functions-list, read the introductory remarks in M-x find-library RET hippie-exp RET. The M-/ is bound by default to Dabbrev, but I am repurposing it for its built-in superset.

The dabbrev-abbrev-char-regexp is configured to match both regular words and symbols (e.g. with hyphenation like this variable). This makes it suitable for code and ordinary language.

While the dabbrev-abbrev-skip-leading-regexp is instructed to also expand words and symbols that start with any of these: $, *, /, =. This regexp may be expanded in the future, but the idea is to be able to perform completion in contexts where the known word/symbol is preceded by a special characters. For example, in the org-mode version of this document, all inline code must be placed between the equals sign. So now typing the =, then a letter, will still allow me to expand text based on that input.

To check what I have on regular expressions, see further below my configurations and documentation for re-builder and visual-regexp.

(use-package dabbrev
  :commands (dabbrev-expand dabbrev-completion)
  :config
  (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
  (setq dabbrev-abbrev-skip-leading-regexp "\\$\\|\\*\\|/\\|=")
  (setq dabbrev-backward-only nil)
  (setq dabbrev-case-distinction nil)
  (setq dabbrev-case-fold-search t)
  (setq dabbrev-case-replace nil)
  (setq dabbrev-check-other-buffers t)
  (setq dabbrev-eliminate-newlines nil)
  (setq dabbrev-upcase-means-case-search t))

(use-package hippie-exp
  :after dabbrev
  :config
  (setq hippie-expand-try-functions-list
        '(try-expand-dabbrev-visible
          try-expand-dabbrev
          try-expand-dabbrev-all-buffers
          try-expand-dabbrev-from-kill
          try-expand-list-all-buffers
          try-expand-list
          try-expand-line-all-buffers
          try-expand-line
          try-complete-file-name-partially
          try-complete-file-name
          try-expand-all-abbrevs))
  (setq hippie-expand-verbose nil)
  :bind ("M-/" . hippie-expand))
3.1.3.2 Simple abbreviations

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

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

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

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

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

3.2 Configurations for—or extensions to—built-in search commands

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

3.2.1 Isearch enhancements

The built-in search mechanism is a thing of beauty: minimal in its presentation, powerful in its applications.

I use isearch all the time for quick navigation, either to a visible part of the buffer or to some specific string I am aware of. It also is essential when used in the context of a keyboard macro, as demonstrated in my video about Isearch powers in keyboard macros (2020-01-21).

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

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

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

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

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

Now on to some custom functions, all of which are derived from the source code of isearch (do it with M-x find-library RET isearch RET). Here is an overview of what goes into this package declaration.

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

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

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

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

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

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

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

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

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

3.2.2 Regular expressions: re-builder and visual-regexp

To learn more about regular expressions, read the relevant pages in the official manual. Assuming you have this installed properly on your system, run C-h r i regexp to get to the starting chapter.

Also watch my ~35 minute-long primer on Emacs regexp (2020-01-23).

Emacs offers a built-in package for practising regular expressions. By default, re-builder uses Emacs-style escape notation, in the form of double backslashes. You can switch between the various styles by using C-c TAB inside of the regexp builder's buffer. I choose to keep this style as the default. Other options are string and rx.

(use-package re-builder
  :config
  (setq reb-re-syntax 'read))

Another option (though the two are not mutually exclusive) is to use the third-party package visual-regexp. This one is meant as a drop-in replacement for query-replace (and the regexp variant). I prefer not to use it that way, but only invoke it via M-x when I need to test a regular expression that I would then replace with something else. The major upside of this tool is that it highlights groups individually and offers a live preview of the replacement, making it absolutely great when dealing with complex sets of regexp constructs.

(use-package visual-regexp
  :ensure t
  :config
  (setq vr/default-replace-preview nil)
  (setq vr/match-separator-use-custom-face t))

3.2.3 wgrep (writable grep)

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

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

4 Directory, buffer, window management

4.1 Dired (directory editor, file manager)

The directory editor abbreviated as "Dired" (which I pronounce like "tired", "mired", etc.) is a built-in tool that performs file management operations inside of an Emacs buffer. It is simply superb! I use it daily for a number of tasks.

You can interactively copy, move (rename), symlink, delete files and directories, handle permissions, compress or extract archives, run shell commands, combine Dired with regular editing capabilities as part of a keyboard macro, search[+replace] across multiple files, encrypt/decrypt files, and more. Combine that with the possibility of matching items with regular expressions or creating an editable Dired buffer and you have everything you need to maximise your productivity.

Check some of my videos:

4.1.1 Base settings for Dired

The options here are meant to do the following:

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

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

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

(use-package dired-aux
  :config
  (setq dired-isearch-filenames 'dwim)
  ;; The following variables were introduced in Emacs 27.1
  (setq dired-create-destination-dirs 'ask)
  (setq dired-vc-rename-file t)
  :bind (:map dired-mode-map
              ("C-c +" . dired-create-empty-file)))

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

(use-package async
  :ensure t)

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

Pro tip while renaming or copying a file, M-n will return its original name, thus allowing you to easily {pre,ap}pend to it. This leverages an intriguing concept of Emacs' design called "future history" (because M-p goes back to your previous entries). The notion of the future history, when applied, is basically an educated guess of what the user would want to do in the current context, given that they are not searching through their previous actions.

4.1.2 Narrowed dired

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

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

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

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

4.1.3 wdired (writable dired)

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

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

4.1.4 peep-dired (file previews including images)

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

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

4.1.5 image-dired (image thumbnails and previews)

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

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

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

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

Tree-style navigation means that the subdirectories of the current Dired buffer can be expanded and contracted in place. It then is possible to perform the same kind of folding on their subdirectories, and so on.

This is, in my opinion, a far more intuitive interaction than the default way of inserting subdirectories in the current buffer below their parent (type i over the target dir). There still are uses for that technique, but tree-style navigation is easier for day-to-day tasks.

What I have here:

  • 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.
(use-package dired-subtree
  :ensure t
  :after dired
  :config
  (setq dired-subtree-use-backgrounds nil)
  :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)

These are some additional features that are shipped with Emacs. The one I need the most 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 winner-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!

I keep dired-clean-confirm-killing-deleted-buffers to t as a safety mechanism: if a file is ever deleted by accident I can use its buffer to restore it (never happened in practice).

With regard to binding keys, I choose to handle things myself. There has never been a case where I had to run info or man inside of a directory listing and wished there was some keyboard shortcut readily available.

While in dired-mode, if you need to open all marked files at once, you can hit F. It calls dired-do-find-marked-files.

As for my two functions, they leverage a command found in this library. I call them with M-x as their utility is very specialised.

(use-package dired-x
  :after dired
  :config
  (setq dired-clean-up-buffers-too t)
  (setq dired-clean-confirm-killing-deleted-buffers t)
  (setq dired-x-hands-off-my-keys t)
  (setq dired-bind-man nil)
  (setq dired-bind-info nil)

  (defun prot/kill-current-filename ()
    "Place the current buffer's file name in the `kill-ring'."
    (interactive)
    (kill-new (dired-filename-at-point)))

  (defun prot/insert-current-filename ()
    "Insert at point the current buffer's file name."
    (interactive)
    (insert (dired-filename-at-point)))

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

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, as well as a ton of other packages (see the section on my Modus themes).

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

4.1.10 Git overview in Dired

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

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

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

4.2 Working with buffers

4.2.1 Unique names for buffers

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

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

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

4.2.2 Configure ibuffer

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

Some tweaks to the default behaviour and presentation:

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

4.3 Window configuration

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

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

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

4.3.1 Window rules and basic tweaks

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

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

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

With regard to the contents of the :bind keyword of the window library, most key combinations are complementary to the standard ones, such as C-x 1 becoming s-1, C-x o turning into s-o and the like. They do not replace the defaults: they just provide more convenient access to their corresponding functions. They all involve the Super key, following the norms described in the introductory note on the matter. Concerning the balance-windows-area I find that it is less intrusive than the original balance-windows normally bound to the same C-x +.

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

(use-package window
  :init
  (setq display-buffer-alist
        '(;; top side window
          ("\\*\\(Flycheck\\|Package-Lint\\).*"
           (display-buffer-in-side-window)
           (window-height . 0.16)
           (side . top)
           (slot . 0)
           (window-parameters . ((no-other-window . t))))
          ("\\*\\(Backtrace\\|Warnings\\|Compile-Log\\|Messages\\)\\*"
           (display-buffer-in-side-window)
           (window-height . 0.16)
           (side . top)
           (slot . 1)
           (window-parameters . ((no-other-window . t))))
          ;; bottom side window
          (".*\\*Completions.*"
           (display-buffer-in-side-window)
           (window-height . 0.16)
           (side . bottom)
           (slot . 0)
           (window-parameters . ((no-other-window . t))))
          ("\\*e?shell.*"
           (display-buffer-in-side-window)
           (window-height . 0.16)
           (side . bottom)
           (slot . 1))
          ;; left side window
          ("\\*Help.*"
           (display-buffer-in-side-window)
           (window-width . 0.20)       ; See the :hook
           (side . left)
           (slot . 0)
           (window-parameters . ((no-other-window . t))))
          ;; right side window
          ("\\*Faces\\*"
           (display-buffer-in-side-window)
           (window-width . 0.25)
           (side . right)
           (slot . 0)
           (window-parameters . ((no-other-window . t)
                                 (mode-line-format . (" "
                                                      mode-line-buffer-identification)))))
          ("\\*Custom.*"
           (display-buffer-in-side-window)
           (window-width . 0.25)
           (side . right)
           (slot . 1))))
  (setq window-combination-resize t)
  (setq even-window-sizes 'height-only)
  (setq window-sides-vertical nil)
  :hook ((help-mode . visual-line-mode)
         (custom-mode . visual-line-mode))
  :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)
         ("C-x +" . balance-windows-area)
         ("<f8>" . window-toggle-side-windows)))

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

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

This passes certain parameters to the newly created frame:

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

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

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

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

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

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

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

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

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

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

4.3.3 ace-window (more flexible window motions)

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

Make sure to also refer to the previous sections on:

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

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

5 Applications and utilities

This section includes configurations for programs like email clients, news reader, music players… Anything you would normally see in a standalone application. The end goal is to eventually integrate every aspect of my computing inside of Emacs.

5.1 Built-in calendar

Some basic settings for this tool. It is used by all Org-mode facilities that require date/time input (see following sections).

(use-package calendar
  :config
  (setq calendar-week-start-day 1)      ; Monday
  (setq calendar-date-style 'iso))

5.2 Org-mode (personal information manager)

Org offers you the basic tools to organise your life in super-efficient ways using nothing but plain text.

In its purest form, Org is a markup language that is similar to Markdown: symbols are used to denote the meaning of a construct in its context, such as what may represent a headline element or a phrase that calls for emphasis.

What lends Org its super powers though is everything else built around it: a rich corpus of elisp functions that automate, link, combine, enhance, structure, or otherwise enrich the process of using this otherwise simple markup language. This very document is written in org-mode while its website version is produced by a function that exports Org notation into its HTML equivalent.

This section contains several sub-sections, each dedicated to a particular aspect of Org. Unless otherwise specified, everything is a work-in-progress as I gradually build up my knowledge of this killer app.

5.2.1 Org basic configurations

These are the base settings that other more specialised functions of Org depend on. Here is an overview:

Agenda and default setup
The bulk of the org-agenda configurations is defined in a subsequent section. Here we just declare the default file system paths for searching for relevant files. The "notes" file is meant as a fallback option for when org-capture has not been given a file to write at (also see the org-capture section).
Re-filing items
This is done with C-c C-w which then prompts us for a heading under which the current item should be positioned. I set my Org agenda files as one possible target and the current buffer as the other. The maximum depth should be 3 levels. Re-filing can also be done from inside an org-capture interface. Any new entry should go at the end of the heading it is filled under.
To-do settings
I generally use a very simple system of writing tasks, in the sense that I do not really care about time-tracking or assigning an intermediate state, etc. I let tags and the description further qualify the meaning. The letter inside parentheses is for faster access when using the C-c C-t method. The upside of having lots of specialised keywords is that it becomes easier to filter your tasks in the relevant agenda views.
Logging meta data
I do not really care about tracking all the minutia of why a deadline was reviewed or whatnot. Though it is kind of nice to have a timestamp of when a task was concluded (still don't care about it).

Now on to the miscellaneous settings:

  • With the t value of org-special-ctrl-a/e we assign a special meaning to the motions that take us to the beginning or end of the line when those are performed over a heading. The idea here is that we can always perform changes to the absolute beginning of the line, such as by increasing the heading's depth with M-right.
  • I do not enable this sort of contextual awareness for the C-k command, because I do consider it rather unpredictable.
  • All the markup characters should be hidden from view, in the same way links are. This generally reduces the distractions in the document.
  • The org-structure-template-alist had its value and functionality changed in Org version 9.2, which ships with Emacs 27. To insert a template you must now use C-c C-,.
  • The return key should never follow a link because I sometimes call it by accident. Use C-c C-o instead.
  • The org-store-link is one of the nicest additions. Use it to store a direct link to the heading at point. Then invoke C-c C-l with or without an active region, to create a link to that location (when the region is active, its text will be used for the link, subject to confirmation and further editing). This is how I create the various internal links that point to other parts of this document.
  • With org-loop-over-headlines-in-active-region we can perform actions such as tagging and scheduling on the items within the active region. I configure it to only apply to headings of the same level, in order to avoid possible inconveniences.

2020-02-07: WORK IN PROGRESS

(use-package org
  :config
  ;; agenda and basic directory structure
  (setq org-directory "~/Org")
  (setq org-default-notes-file "~/Org/notes.org")
  (setq org-agenda-files
        '("~/Org"
          "~/.emacs.d"
          "~/Documents"))
  (setq org-deadline-warning-days 3)
  ;; refile, todo
  (setq org-refile-targets
        '((org-agenda-files . (:maxlevel . 3))
          (nil . (:maxlevel . 3))))
  (setq org-refile-use-outline-path t)
  (setq org-refile-allow-creating-parent-nodes 'confirm)
  (setq org-refile-use-cache t)
  (setq org-reverse-note-order nil)
  (setq org-todo-keywords
        '((sequence "TODO(t)" "|" "DONE(D)" "CANCELLED(C)")
          (sequence "ACT(a)" "|" "ACTED(A)")
          (sequence "BUY(b)" "|" "BOUGHT(B)")
          (sequence "MEET(m)" "|" "MET(M)" "POSTPONED(P)")
          (sequence "STUDY(s)" "|" "STUDIED(S)")
          (sequence "RECORD(r)" "|" "RECORDED(R)")))
  (setq org-fontify-done-headline t)
  (setq org-enforce-todo-dependencies t)
  (setq org-enforce-todo-checkbox-dependencies t)
  (setq org-track-ordered-property-with-tag t)
  (setq org-highest-priority ?A)
  (setq org-lowest-priority ?C)
  (setq org-default-priority ?A)
  ;; code blocks
  (setq org-confirm-babel-evaluate nil)
  ;; log
  (setq org-log-done 'time)
  (setq org-log-note-clock-out nil)
  (setq org-log-redeadline nil)
  (setq org-log-reschedule nil)
  (setq org-read-date-prefer-future 'time)
  ;; general
  (setq org-special-ctrl-a/e t)
  (setq org-special-ctrl-k nil)
  (setq org-hide-emphasis-markers t)
  (setq org-structure-template-alist    ; CHANGED in Org 9.2, Emacs 27.1
        '(("s" . "src")
          ("E" . "src emacs-lisp")
          ("e" . "example")
          ("q" . "quote")
          ("v" . "verse")
          ("V" . "verbatim")
          ("c" . "center")
          ("C" . "comment")))
  (setq org-catch-invisible-edits 'show)
  (setq org-return-follows-link nil)
  (setq org-loop-over-headlines-in-active-region 'start-level)
  ;; 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.2.2 Org-capture templates

The org-capture tool is a powerful way to quickly produce some kind of structured information. The type of data and the way to store is determined by a system of templates which accepts a series of possible specifiers as well as the evaluated part of arbitrary elisp code.

Each template is accessed via a key. These are listed in a buffer when you call org-capture. Unique keys give direct access to their template, whereas templates that share a common initial key will produce a second selection list with the remaining options. In the latter case, the initial key entry has no call to an actual function, but is just written as a heading. For an example, look how I do the "contact information": all templates whose keys follow the pattern cX are only visible after hitting c and are then accessed via X.

The visibility of a template is further controlled by another variable: org-capture-templates-contexts. This allows us to tell Org the context in which we want certain options to appear in. Otherwise they remain concealed from our view. Equipped with this piece of functionality, we can freely write highly-specialised templates that capture structured text when viewing some particular item, but are not needed for more general purposes. I do this for certain actions that only come into effect when reading email inside of the relevant gnus buffers (also check my comprehensive configurations for email and the Gnus news/mail reader).

Speaking of mail, you will notice some specifiers like :fromname. This refers to the From field in emails and will capture the name part only. Other similar keywords are :from (name and email), :fromaddress (email only), :subject.

Specifiers that start with the caret sign (^) represent prompts for further user input. The pattern ^{TEXT} is a prompt whose name is TEXT. To offer possible options, use ^{Initial|ONE|TWO|THREE}, where the first entry is the text of the prompt and all the rest are the available choices. In some templates I use the ^t specifier, which is a built-in method to ask for a specific date.

The text that goes into a template can be written as part of a string or inside a function that is then evaluated. I generally prefer to use simple strings, though I might revise this approach going forward. To insert a new line inside of a string, use \n.

The %? determines where the point should be once the template is filled in. While %i will insert the contents of the active region.

As things currently stand, my capture templates always write to headings inside of files. Note though that there are more possibilities, as described in the manual.

A file can be specified by its absolute path or just a name. In the latter case, its location is understood relative to org-directory. When using the file+headline pattern, non-existing files are created automatically once you call the relevant template. Same for their respective headings.

Finally, the contrib/org-capture-no-delete-windows and relevant advice address a problem I have when org-capture fails to conclude its actions when called from inside of a side window (for more on those, refer to the section on Window rules and basic tweaks). The code is taken directly from this Stack Overflow thread.

(use-package org-capture
  :after org
  :config
  (setq org-capture-templates
        '(("b" "Basic task" entry
           (file+headline "tasks.org" "Basic tasks that need to be reviewed")
           "* %?")
          ("c" "Capture some concise actionable item and exit immediately" entry
           (file+headline "tasks.org" "Task list without a defined date")
           "* TODO [#B] %^{Title}\n :PROPERTIES:\n :CAPTURED: %U\n :END:\n\n %i %l" :immediate-finish t)
          ("t" "Task of importance with a tag, deadline, and further editable space" entry
           (file+headline "tasks.org" "Task list with a date")
           "* %^{Scope of task||TODO [#A]|STUDY [#A]|MEET with} %^{Title} %^g\n DEADLINE: %^t\n :PROPERTIES:\n :CONTEXT: %a\n:CAPTURED: %U\n :END:\n\n %i %?")
          ("r" "Reply to an email" entry
           (file+headline "tasks.org" "Mail correspondence")
           "* TODO [#B] %:subject\n SCHEDULED: %t\n :PROPERTIES:\n :CONTEXT: %a\n :END:\n\n %i %?")
          ("i" "Idea")
          ("ia" "Activity or event" entry
           (file+headline "ideas.org" "Activities or events")
           "* ACT %^{Act about what}%? :private:\n :PROPERTIES:\n :CAPTURED: %U\n :END:\n\n %i")
          ("ie" "Essay or publication" entry
           (file+headline "ideas.org" "Essays or publications")
           "* STUDY %^{Expound on which thesis}%? :private:\n :PROPERTIES:\n :CAPTURED: %U\n :END:\n\n %i")
          ("iv" "Video blog or screen cast" entry
           (file+headline "ideas.org" "Screen casts or vlogs")
           "* RECORD %^{Record on what topic}%? :private:\n :PROPERTIES:\n :CAPTURED: %U\n :END:\n\n %i")))
  (setq org-capture-templates-contexts
        '(("r" ((in-mode . "gnus-article-mode")
                (in-mode . "gnus-summary-mode")))))

  (defun contrib/org-capture-no-delete-windows (oldfun args)
    (cl-letf (((symbol-function 'delete-other-windows) 'ignore))
      (apply oldfun args)))

  (with-eval-after-load "org-capture"
    (advice-add 'org-capture-place-template :around 'contrib/org-capture-no-delete-windows))

  :bind ("C-c c" . org-capture))

5.2.3 Org agenda

The org-agenda is not just a single interface. It rather is your conduit to a set of utilities from where you can keep track of all the tasks you have written in the files declared as part of org-agenda-files (see its value in the section that covers the base Org configurations). Invoking org-agenda will present you with a list of possible options.

  • The a is where you keep track of all the items that have a date assigned to them, be it SCHEDULED or DEADLINE. To assign such a value to a heading use C-c C-s or C-c C-d respectively.
  • The t will list all your tasks, regardless of whether they have a date assigned to them. You can then filter by keyword. There also are several options for narrowing the list using various search techniques.
  • And the n will offer you a combined view of the above.

2020-02-07: WORK IN PROGRESS

(use-package org-agenda
  :after org
  :config
  (setq org-agenda-additional-timestamps-same-entry t)
  (setq org-agenda-include-diary nil)
  (setq org-agenda-show-all-dates t)
  (setq org-agenda-show-outline-path nil)
  (setq org-agenda-skip-additional-timestamps-same-entry t)
  (setq org-agenda-skip-deadline-prewarning-if-scheduled t)
  (setq org-agenda-skip-scheduled-delay-if-deadline t)
  (setq org-agenda-skip-scheduled-if-deadline-is-shown t)
  (setq org-agenda-skip-scheduled-if-done t)
  (setq org-agenda-skip-timestamp-if-deadline-is-shown t)
  (setq org-agenda-skip-timestamp-if-done t)
  (setq org-agenda-span 3)
  (setq org-agenda-start-on-weekday 1)  ; Monday
  (setq org-agenda-timegrid-use-ampm nil)
  (setq org-agenda-window-setup 'current-window)
  (setq org-agenda-dim-blocked-tasks t)
  :bind ("C-c a" . org-agenda))

5.2.4 Org source code blocks

These are just some basic settings that are particularly useful when inserting source code blocks. I do not want Org to mess up with my indentation, while I need to see the native syntax highlighting for that language.

The org-src-window-setup is accessed via the C-c ' key once inside a code block that has a language assigned to it.

(use-package org-src
  :after org
  :config
  (setq org-src-window-setup 'current-window)
  (setq org-src-fontify-natively t)
  (setq org-src-preserve-indentation t)
  (setq org-src-tab-acts-natively t)
  (setq org-edit-src-content-indentation 0))

5.2.5 Org export

Org's "export" facility has the power to convert a .org file into a number of common formats, including .pdf. I only ever use it to produce the HTML version of this document or similar tasks along those lines. In the future, I might spend some time leveraging its potential for more demanding workflows.

(use-package ox
  :after org
  :config
  (setq org-export-with-toc t)
  (setq org-export-headline-levels 8)
  (setq org-export-backends
        '(ascii html latex md))
  (setq org-export-dispatch-use-expert-ui t))
5.2.5.1 TODO review Org's exporting facilities [0/2]   emacs org
5.2.5.1.1 TODO research derived export backends
5.2.5.1.2 TODO automate webpage creation for dotemacs

5.2.6 Inherit theme styles in Org HTML export

With this package, every exported HTML code will inherit the styles of the current theme. This makes it particularly useful for highlighting blocks of source code.

(use-package htmlize
  :ensure t
  :after org
  (setq htmlize-ignore-face-size t))

5.2.7 Consistent heading IDs (and anchor tags)

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

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

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

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

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

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

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

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

(use-package darkroom
  :ensure t
  :config
  (setq darkroom-text-scale-increase 0))

(use-package org-bullets
  :ensure t
  :after org)

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

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

5.3 Email settings

Configuring email can be quite the challenge, largely because we have been used to the likes of Thunderbird, where you log in once and then everything "just works". The toolset for my current setup consists of the following:

  • Gnus (also pronounced as "News" or "Nooz", etc.), which is a powerful newsreader and email client that is built into Emacs.
  • The built-in capabilities to send email.

Previous versions of this document (prior to 2020-01-30) relied on external tools for fetching and reading email, in particular mu4e, the front-end to the mu mail indexer, and offlineimap. Whereas now I let Gnus handle the task of synchronising with the IMAP server and with all the news groups I am subscribed to. No need to worry about some complex mechanism for syncing, storing, indexing email.

Note that I do not work in an office context, do not need HTML in my email, and do not try to come up with some fancy mechanism for updating my sources and notifying me of the results. My approach is to update my sources manually: the idea is that if I have time to check my news, I also am able to act on them. If you have more complex needs, you will definitely need to adapt things accordingly. Still, this section contains lots of useful information to get you started.

5.3.1 Base email settings

Before configuring the email client, we need to establish the absolute essentials: who we are, where our credentials are stored, and whether encryption is supported. This is done in the first two package declarations. The others are of secondary importance, though still very useful.

Consider reviewing nnmail-expiry-wait only after you have some experience with Gnus.

As for the message-citation-line-format, its value is expanded into something like "NAME <EMAIL> [2020-02-07 Fri]:". To find about all the date-related specifiers, read the relevant documentation with C-h v format-time-string.

(use-package auth-source
  :config
  (setq auth-sources '("~/.authinfo.gpg" "~/.authinfo"))
  (setq user-full-name "Protesilaos Stavrou")
  (setq user-mail-address "public@protesilaos.com"))

(use-package epa-file
  :config
  (setq epa-file-cache-passphrase-for-symmetric-encryption t))

(use-package message
  :config
  (setq message-citation-line-format "%f [%Y-%m-%d, %R %z]:\n")
  (setq message-citation-line-function
        'message-insert-formatted-citation-line)
  (setq message-kill-buffer-on-exit nil)
  (setq message-wide-reply-confirm-recipients t))

(use-package nnmail
  :config
  (setq nnmail-expiry-wait 30))

(use-package nnir
  :config
  (setq nnir-imap-default-search-key 'imap))

Below is a sample with the contents of my authinfo.gpg. This is read by gnus and smtpmail to be able to both fetch and send messages from the given account. I strongly encourage you to encrypt this file if you add your login credentials there. Do it from inside dired with : e while the point is over the file. Emacs can decrypt all encrypted files automatically.

machine prv port 993 login MAIL password SECRET
machine inf port 993 login MAIL password SECRET
machine pub port 993 login MAIL password SECRET

machine mail.gandi.net port 465 login MAIL password SECRET
machine mail.gandi.net port 465 login MAIL password SECRET
machine mail.gandi.net port 465 login MAIL password SECRET

Refer to your email provider's documentation in order to determine the port number and server address you need to use for sending and receiving messages. The MAIL is either your email address or some username for logging into the account.

Note that the terms I use above for prv, inf, and pub are just arbitrary names for the given MAIL and SECRET combination. This allows us to reference each name in the Gnus configurations, and share those in a public document like this one, without worrying about leaking private data.

5.3.2 Gnus for reading email, mailing lists, and more

The documentation describes Gnus as the "coffee-brewing, all singing, all dancing, kitchen sink newsreader". I chuckled when I first read it, thinking to myself that the developers have an interesting sense of humour. Then I decided to quickly go through the list of user-facing customisation options: M-x customize-apropos-groups RET gnus RET … Not so funny after all!

Simply put, Gnus is massive. This makes it both extremely powerful and incredibly complicated for new users. Do not let that scare you though: start small and gradually tweak things as you go. This is how you approach Emacs itself. Learn the basics and then figure out your needs from then on.

Now some basic information on the abstractions that Gnus relies on:

  1. The default Gnus buffer is called "Group". It will present you with a list of all the news sources you have subscribed to. By default, Gnus only displays messages that have not been read. The same applies for groups. The "Group" buffer will be empty the very first time you log in because you have not subscribed to anything yet. Use g to fetch new messages from the sources. If you only want to refresh the group at point, do it with M-g.
  2. The "Server" buffer contains a list with all the sources you have specified for discovering news. In my case, these are my email accounts and a Usenet server where mailing lists are hosted. To access the "Server" buffer from inside the "Group" buffer, just hit the caret sign ^. To subscribe to an item, place the point over it and hit u. Do that for your email's inbox and for whatever mailing lists you intend to follow.
  3. The "Summary" buffer contains all the messages of a group. Hitting the return key over a message will split the view in two, with the list above and the message below. Use n or p to move to the next or previous unread message (or N and P to just the next/prev). You access the "Summary" buffer both from the "Group" and the "Server" by entering a group.

It is essential to take things slowly (and first test whether your messages are being sent and that you can receive them). Each buffer has some unique functions that are relevant to the current interface. To learn more about them, use C-h m. Do it for all three of the above. Also rely on C-h k to get information about what each key does in the given context (or just start a key sequence and then hit C-h to display possible combinations in a new Help buffer).

Now a couple more things about the "Group" buffer:

  • A group can be assigned a level of importance. This is a grade whose highest score is 1 and the lowest is 6 (customisable though). Each level has a different colour. To assign a new value to the group at point, do it with S l and then give it a number. Once you have graded your groups, you can perform various actions on a per-level basis. For example, to refresh all levels from 1 up to 3 but not higher, pass a numeric argument to the standard g command. So C-3 g (this is the same as C-u 3 g).
  • Groups can be organised by topic. Create a new one with T n and give it a name. Move a group to a topic with T m. To toggle the view of topics use t (I have a hook that does this automatically at startup). The level of indentation tells us whether a topic is a sub-set of another. Use TAB or C-u TAB to adjust it accordingly. As with levels, you can operate on a per-topic basis. For example, to catch up on all the news of a given topic (mark all as read), you place the point over it, hit c and then confirm your choice.

As noted, Gnus will only show you a list of unread items. To view all your groups, hit L. Use the lower case version l to view only the unread ones. To produce a Summary buffer with read items, hit C-u RET over a group and specify the number of messages you want to list (the other option is C-u M-g from inside the Summary). Another useful trick for the Summary buffer is the use of the caret sign (^) to show you the previous message that the current item is a reply to.

Consider watching my Introduction to Gnus (2020-02-02).

Notwithstanding the customisation options and various idiosyncratic design choices, some prior experience with Emacs' various interfaces will definitely come in handy: Gnus uses similar metaphors for navigating and parsing information. It still is important to read the manual though.

Now here comes the nice part of leveraging the integration that Emacs offers: in my Org mode configurations I have a simple template to capture the current buffer's link. This means that we can quickly convert any item into a task/note and always be able to go back to the original message by following the link. Found an interesting suggestion in some mailing list? Capture it. Need to act on an email later? Capture, capture, capture. Same principle applies to the integration with Dired as a means of attaching files to emails (see next section).

The package declarations below are divided into several subsections to make things easier to read and keep track of. Remember to use C-h v VAR to read documentation about each VAR or simply place the point over it and then hit C-h v to pre-populate the results (C-h f is the equivalent for functions, C-h o for other symbols). Whenever you see some formatting customisations concerning time units, it is better refer to the documentation of the function format-time-string to understand the meaning of the various date/time specifiers.

5.3.2.1 Gnus account settings and essential configurations

Here I only furnish the essentials for the basic Gnus functionality.

The gnus-select-method sets the default method for fetching news items. As I want to read mail from several accounts in addition to following Usenet sources, I choose to set it to nil.

The gnus-secondary-select-methods is where my accounts are specified. Each nnimap list points to a specific line in my authinfo.gpg file (whose format I described in the base email settings). My emails all use the same server so this method allows me to specify the username (email) and password combination for each of them without making this information public. I am not sure whether the nnimap-stream and nnimap-authinfo-file are needed, but I keep them for the sake of completeness.

The gnus-parameters are designed to simply move my outgoing messages to the "Sent" folder of the relevant account. While gnus-gcc-mark-as-read ensures that these are marked as read.

The "agent" is enabled here and configured in the following section.

(use-package gnus
  :config
  ;; accounts
  (setq gnus-select-method '(nnnil))
  (setq gnus-secondary-select-methods
        '((nntp "news.gwene.org")
          (nnimap "prv"
                  (nnimap-address "mail.gandi.net")
                  (nnimap-stream ssl)
                  (nnimap-authinfo-file "~/.authinfo.gpg"))
          (nnimap "inf"
                  (nnimap-address "mail.gandi.net")
                  (nnimap-stream ssl)
                  (nnimap-authinfo-file "~/.authinfo.gpg"))
          (nnimap "pub"
                  (nnimap-address "mail.gandi.net")
                  (nnimap-stream ssl)
                  (nnimap-authinfo-file "~/.authinfo.gpg"))))
  (setq gnus-parameters
        '(("prv"
           (posting-style
            (gcc "nnimap+prv:Sent")))
          ("inf"
           (posting-style
            (gcc "nnimap+inf:Sent")))
          ("pub"
           (posting-style
            (gcc "nnimap+pub:Sent")))))
  (setq gnus-gcc-mark-as-read t)
  (setq mail-signature "Protesilaos Stavrou\nprotesilaos.com\n")
  (setq message-signature "Protesilaos Stavrou\nprotesilaos.com\n")
  (setq gnus-agent t)
  (setq mail-user-agent 'gnus-user-agent) ; also works with `sendmail-user-agent'
  (setq gnus-novice-user nil)
  ;; checking sources
  (setq gnus-check-new-newsgroups 'ask-server)
  (setq gnus-read-active-file 'some)
  ;; dribble
  (setq gnus-use-dribble-file t)
  (setq gnus-always-read-dribble-file t)
  :bind ("C-c m" . gnus))
5.3.2.2 Gnus agent

The "agent" is a technical term described in the Gnus manual which basically represents the bridge between our Gnus and the server to which it connects to. Gnus is said to be "plugged" when a connection is established. Else it is "unplugged".

Technicalities aside, we can use the agent to configure the handling of messages. For example, we can set an expiry date, after which the message is deleted, or we can create a queue of outgoing messages when Gnus is in an unplugged state.

(use-package gnus-agent
  :after gnus
  :config
  (setq gnus-agent-article-alist-save-format 1)  ; uncompressed
  (setq gnus-agent-cache t)
  (setq gnus-agent-confirmation-function 'y-or-n-p)
  (setq gnus-agent-consider-all-articles nil)
  (setq gnus-agent-directory "~/News/agent/")
  (setq gnus-agent-enable-expiration 'ENABLE)
  (setq gnus-agent-expire-all nil)
  (setq gnus-agent-expire-days 30)
  (setq gnus-agent-mark-unread-after-downloaded t)
  (setq gnus-agent-queue-mail t)        ; queue if unplugged
  (setq gnus-agent-synchronize-flags nil))
5.3.2.3 Gnus article (message view)

In Gnus parlance the "article" is the window that contains the summary's news/email currently selected item. This has its own major mode, which is great for us: we can define key bindings that only apply when the article is in focus.

I have no particular interest in the HTML-related variables, because I practically never have to read any such messages. As a general rule, email that can only be read in HTML is likely spam or annoying enough to be treated as such.

The variable gnus-mode-line-image-cache is set to nil to make sure that the blue Gnus icon is not shown in the mode line. That icon has a predefined colour, making it a potentially poor choice for colour contrast accessibility.

Note that gnus-article-sort-functions requires the most important function to be declared last.

With regard to the key bindings, I have redefined some of the existing ones to suit of workflow and better match my intuitions. For example, in the Article view, hitting s takes you to the Summary buffer. I find that to be a waste, since we can already move between buffers with standard keys. Instead, The s can be used to save the attachment at point. Similarly, I want o to behave just like in dired, where it opens the attachment at point (MIME part) in another buffer.

(use-package gnus-art
  :after gnus
  :demand
  :config
  (setq gnus-article-browse-delete-temp 'ask)
  (setq gnus-article-over-scroll nil)
  (setq gnus-article-show-cursor t)
  (setq gnus-article-sort-functions
        '((not gnus-article-sort-by-number)
          (not gnus-article-sort-by-date)))
  (setq gnus-article-truncate-lines nil)
  (setq gnus-html-frame-width 80)
  (setq gnus-html-image-automatic-caching t)
  (setq gnus-inhibit-images t)
  (setq gnus-max-image-proportion 0.3)
  (setq gnus-mode-line-image-cache nil)
  (setq gnus-treat-display-smileys nil)
  (setq gnus-article-mode-line-format "Gnus: %S %m")
  (setq gnus-visible-headers
        '("^From:" "^To:" "^Cc:" "^Newsgroups:" "^Subject:" "^Date:"
          "Followup-To:" "Reply-To:" "^Organization:" "^X-Newsreader:"
          "^X-Mailer:"))
  (setq gnus-sorted-header-list gnus-visible-headers)
  :bind (:map gnus-article-mode-map
              ("s" . gnus-mime-save-part)
              ("o" . gnus-mime-copy-part)))
5.3.2.4 Gnus asynchronous operations

By default, Gnus performs all its actions in a synchronous fashion. This means that Emacs is blocked until Gnus has finished. By enabling this library, we can use certain functions in a non-blocking way. I do this for sending email.

(use-package gnus-async
  :after gnus
  :config
  (setq gnus-asynchronous t)
  (setq gnus-use-article-prefetch 15))
5.3.2.5 Gnus group (main interface)

I already outlined the utility of the group buffer in the introductory section on Gnus for reading email, mailing lists, and more. In short, it is the epicentre of Gnus, where all your subscribed groups are presented and from where you can browse through your updates.

I use groups in tandem with topics, which allows me to quickly follow updates on the theme I am interested in at the moment. It also allows me to perform per-topic actions, such as updating only the groups it contains or "catching up" to them (marking them as read).

I choose to disable the default behaviour of always showing a group that has "ticked" items (the equivalent of starred or marked as important).

Note that gnus-group-sort-functions requires the most important function to be declared last.

(use-package gnus-group
  :after gnus
  :demand
  :config
  (setq gnus-level-subscribed 6)
  (setq gnus-level-unsubscribed 7)
  (setq gnus-level-zombie 8)
  (setq gnus-list-groups-with-ticked-articles nil)
  (setq gnus-group-sort-function
        '((gnus-group-sort-by-unread)
          (gnus-group-sort-by-alphabet)
          (gnus-group-sort-by-rank)))
  (setq gnus-group-mode-line-format "Gnus: %%b")
  :hook
  (gnus-group-mode . hl-line-mode)
  (gnus-select-group-hook . gnus-group-set-timestamp)
  :bind (:map gnus-agent-group-mode-map
              ("M-n" . gnus-topic-goto-next-topic)
              ("M-p" . gnus-topic-goto-previous-topic)))

(use-package gnus-topic
  :after (gnus gnus-group)
  :config
  (setq gnus-topic-display-empty-topics t)
  :hook
  (gnus-group-mode . gnus-topic-mode))
5.3.2.6 Gnus summary

This section assumes you have already read my introductory remarks on Gnus for reading email, mailing lists, and more.

Note that the various sort functions expect the primary filter method to be declared last, in case more that one function is to be invoked. The sorting is set to reverse chronological order (newest first).

Threads should not be hidden, while messages whose root has been removed should be grouped together in some meaningful way. Furthermore, when moving up or down in the list of messages using just n or p, I want to go to the next message, regardless of whether it has been read or not. I can otherwise rely on standard Emacs motions.

The formatting of the threads using Unicode characters was taken from the relevant Emacs wiki entry plus some minor tweaks by me.

The gnus-user-date-format-alist, this basically adapts the date to whether the message was within the day or the one before, else falls back to a default value. It is then called with %&user-date;.

(use-package gnus-sum
  :after (gnus gnus-group)
  :demand
  :config
  (setq gnus-auto-select-first nil)
  (setq gnus-summary-ignore-duplicates t)
  (setq gnus-suppress-duplicates t)
  (setq gnus-summary-goto-unread nil)
  (setq gnus-summary-make-false-root 'adopt)
  (setq gnus-summary-thread-gathering-function 'gnus-gather-threads-by-subject)
  (setq gnus-thread-sort-functions
        '((not gnus-thread-sort-by-number)
          (not gnus-thread-sort-by-date)))
  (setq gnus-subthread-sort-functions
        'gnus-thread-sort-by-date)
  (setq gnus-thread-hide-subtree nil)
  (setq gnus-thread-ignore-subject t)
  (setq gnus-user-date-format-alist
        '(((gnus-seconds-today) . "Today at %R")
          ((+ 86400 (gnus-seconds-today)) . "Yesterday, %R")
          (t . "%Y-%m-%d %R")))
  (setq gnus-summary-line-format "%U%R%z %-16,16&user-date;  %4L:%-30,30f  %B%S\n")
  (setq gnus-summary-mode-line-format "Gnus: %p (%U)")
  (setq gnus-sum-thread-tree-false-root "")
  (setq gnus-sum-thread-tree-indent " ")
  (setq gnus-sum-thread-tree-leaf-with-other "├─➤ ")
  (setq gnus-sum-thread-tree-root "")
  (setq gnus-sum-thread-tree-single-leaf "└─➤ ")
  (setq gnus-sum-thread-tree-vertical "│")
  ;; (defun prot/gnus-summary-save-parts-all ()
  ;;   "Save all MIME parts (attachments) to the ~/Downloads directory."
  ;;   (interactive)
  ;;   (let ((directory "~/Downloads"))
  ;;     (gnus-summary-save-parts ".*" directory)))
  :hook
  (gnus-summary-mode . hl-line-mode)
  (gnus-summary-exit-hook . gnus-topic-sort-groups-by-alphabet)
  (gnus-summary-exit-hook . gnus-group-sort-groups-by-rank)
  :bind (:map gnus-agent-summary-mode-map
              ("<delete>" . gnus-summary-delete-article)
              ("n" . gnus-summary-next-article)
              ("p" . gnus-summary-prev-article)
              ("N" . gnus-summary-next-unread-article)
              ("P" . gnus-summary-prev-unread-article)
              ("M-n" . gnus-summary-next-thread)
              ("M-p" . gnus-summary-prev-thread)
              ("C-M-n" . gnus-summary-next-group)
              ("C-M-p" . gnus-summary-prev-group)
              ("C-M-^" . gnus-summary-refer-thread)))
5.3.2.7 Gnus server

The "server" is where your news sources are listed and from where you can browse items you would like to subscribe to (e.g. your email account's Inbox or some mailing list on Usenet). Make sure to read about these concepts in the introductory section about Gnus.

(use-package gnus-srvr
  :after gnus
  :hook
  ((gnus-browse-mode gnus-server-mode) . hl-line-mode))
5.3.2.8 Gnus intersection with Dired

We can use the built-in directory editor (file manager) as a more convenient way of performing certain tasks that relate to emails, such as attaching all the marked items of the dired buffer to an email we are currently composing or wish to initiate the composition of.

Run C-h m inside of a Dired buffer that has gnus-dired-mode enabled and search for "gnus" to see all the relevant key bindings and the functions they call. I only ever use C-c C-m C-a (C-m is the same as RET).

By the way, make sure to check my comprehensive Dired configurations.

(use-package gnus-dired
  :after (gnus dired)
  :hook (dired-mode . gnus-dired-mode))
5.3.2.9 TODO Extend Gnus [0/5]   emacs gnus
5.3.2.9.1 TODO Integrate contacts database and completion (BBDB)
5.3.2.9.2 TODO learn more about mail searching (nnir?, nnimap?)
5.3.2.9.3 TODO non-blocking RSS feeds (use topic scores?)
5.3.2.9.4 TODO parse Atom feeds as well?
5.3.2.9.5 TODO Try "org-msg" package to compose email in Org-mode

5.3.3 Sending email (SMTP)

These are the base settings for the SMTP functionality. Passwords and other critical information are stored in ~/.authinfo.gpg, as demonstrated in the base email settings. What follows is just a mirroring of the contents of that file.

With regard to the asynchronous functionality, it is meant to improve performance by carrying out the relevant tasks in a non-blocking way.

(use-package smtpmail
  :init
  (setq smtpmail-default-smtp-server "mail.gandi.net")
  :config
  (setq smtpmail-smtp-server "mail.gandi.net")
  (setq smtpmail-stream-type 'ssl)
  (setq smtpmail-smtp-service 465)
  (setq smtpmail-queue-mail nil))

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

5.4 Git front-end (Magit) and relevant configurations

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

5.4.1 Base Magit settings

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

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

5.4.2 Git commits

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

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

5.4.3 Git diffs

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

My Modus themes are configured to style these appropriately.

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

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

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

5.4.4 Magit repo list

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

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

5.4.5 Git time machine

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

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

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

5.5 Shells and terminal emulators

5.5.1 Shell (M-x shell)

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

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

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

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

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

5.5.2 Terminal emulators

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

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

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

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

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

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

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

5.7 Pass interface (password-store)

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

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

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

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

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

5.8 Elfeed (feed reader for RSS/Atom)

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

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

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

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

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

5.9 Emacs web browser and HTML parser

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

What these do:

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

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

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

5.10 Bongo (Music player)

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

Concerning the customisations below, these can be summarised thus:

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

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

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

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

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

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

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

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

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

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

6 General interface and interactions

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

6.1 Modus themes (my very accessible themes) and other visuals

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

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

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

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

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

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

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

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

6.1.1 Window divider mode

This is a built-in mode that allows us to display configurable (and theme-able) window dividers. Whereas the default is to draw a single pixel line using the primary foreground value of the current theme.

The configurations below produce virtually the same results as with the generic window border, only the colour of the divider is less intense (has a grey colour compared to pure white/black). Here is an excerpt of what I had to write about it in commit 373178f8 to my Modus themes:

The default width of the divider is 6 pixels. The first and last pixels are meant as narrow outlines that are drawn in a slightly more intense colour than the remaining 4 inner pixels.

Due to the possibility of adjusting the thickness of the divider, plus the generous default width, the colours I have chosen are less intense than those used in the default window border (those are black on white and vice versa, as per the value of the `default' face).

(use-package emacs
  :config
  (setq window-divider-default-right-width 1)
  (setq window-divider-default-bottom-width 1)
  (setq window-divider-default-places 'right-only)
  :hook (after-init . window-divider-mode))

6.1.2 Optional visual indicators or layout elements

This is a collection of modes or interfaces I seldom use or, rather, I use under special circumstances. They are useful, but there is not need for them to be available at all times.

6.1.2.1 Tabs for window layouts and buffers (Emacs 27.1)

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

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

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

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

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

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

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

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

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

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

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

(use-package hl-line
  :config
  (setq hl-line-sticky-flag nil))
6.1.2.3 Rainbow mode for colour testing

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

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

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

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

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

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

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

6.2 Language settings for prose and code

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

6.2.1 Line length (column count)

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

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

6.2.2 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.3 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.2.4 Comment lines, regions, boxes, etc.

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

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

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

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

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

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

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

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

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

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

6.2.5 Flyspell (spell check)

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

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

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

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

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

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

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

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

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

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

6.2.6 Flycheck (code linting)

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

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

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

Also offer a Flycheck indicator in the mode line.

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

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

(use-package flymake
  :commands flymake-mode)
6.2.6.1 Flycheck package metadata

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

(use-package package-lint
  :ensure t)

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

6.2.7 Eldoc (elisp live documentation feedback)

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

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

6.2.8 Markdown support

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

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

6.2.9 YAML support

This adds support for YAML files.

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

6.2.10 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.2.11 Configure 'electric' behaviour

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

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

6.2.12 Parentheses

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

Summary of what these do:

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

6.2.13 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 certain variables such as the size of the tab. As such, I am disabling tabs by default.

If there ever is a need to use different settings in other modes, we can customise them via hooks. This is not an issue I have encountered yet and am therefore refraining from solving a problem that does not affect me.

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

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

6.3.1 Collection of unpackaged commands or tweaks

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6.3.2 Crux (extra utilities for some useful tasks)

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

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

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

6.3.4 Compare last two kills

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

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

6.4 Cursor and mouse settings

6.4.1 Cursor appearance and tweaks

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

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

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

6.4.2 Mouse behaviour

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

The other options in short:

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

6.4.3 Delete selection

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

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

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

6.4.4 Scrolling behaviour

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

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

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

6.4.5 Tool tips

These settings control how tool tips are to be handled when hovering the mouse over an actionable item:

  • I just want to make sure that the GTK theme is not used for those: I prefer the generic display which follows my current theme's styles.
  • The delay is slightly reduced for the initial pop-up, while it has been increased for immediate pop-ups thereafter.
(use-package tooltip
  :config
  (setq tooltip-delay 0.5)
  (setq tooltip-short-delay 0.5)
  (setq x-gtk-use-system-tooltips nil)
  :hook (after-init . tooltip-mode))

6.5 Keycast mode

I am experimenting with this package as a means of enhancing the videos I do on emacs. Once enabled, it uses the mode line to show the keys being pressed and the command they call.

(use-package keycast
  :ensure t
  :config
  (defun prot/keycast-bottom-left-window-p ()
    (and (window-at-side-p nil 'left)
         (window-at-side-p nil 'bottom)))
  (defun prot/keycast-active-frame-bottom-left-p ()
    (and (prot/keycast-bottom-left-window-p)
         (keycast--active-frame-p)))
  (setq keycast-window-predicate 'prot/keycast-active-frame-bottom-left-p)
  (setq keycast-separator-width 3)
  (setq keycast-insert-after 'mode-line-buffer-identification)
  (setq keycast-remove-tail-elements nil))

6.6 Conveniences and minor extras

6.6.1 Auto revert mode

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

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

6.6.2 Preserve contents of system clipboard

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

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

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

6.6.3 Delete trailing whitespace

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

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

6.6.4 Generic 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).
  • Enable actions for narrowing, region {up,down}casing (all caps or no caps), dired single-buffer navigation (bound to a). Disable overwrite-mode.
  • Allow inputting Greek while preserving Emacs keys. Toggle with C-\.
  • Ignore visual/audible bells, because Emacs more appropriate ways of providing error/warning feedback (e.g. a failed isearch will return no results, while the failed match will be styled accordingly in the echo area)
(use-package emacs
  :config
  (setq vc-follow-symlinks t)
  (setq frame-title-format '("%b %& GNU Emacs"))
  (setq echo-keystrokes 0.25)
  (setq default-input-method "greek")
  (setq ring-bell-function 'ignore)

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

6.6.5 Altered zap and easier repeats

I seldom use the functionality related to this section, but when I do I prefer it to work the way I expect. zap-up-to-char will delete everything from point up to the character you provide it with. Think of how you may want to delete a file name but keep its file type extension.

The repeat command is bound by default to C-x z. I make it so that subsequent repetitions require only hitting another z. In practice though, you should not bother with this. Let keyboard macros handle that task.

Pro tip: to make a keyboard macro out of your most recent commands, use C-x C-k l which calls kmacro-edit-lossage. The list is editable, so remove anything that is not required and then save what is left. The result is stored as the latest keyboard macro (and you also have the power to cycle through kmacros, store them in specific keys, etc.).

Moving on to the mark, practically every Emacs motion that operates on a portion of text will set the mark automatically. You can also do it manually with C-SPC (hit it twice if you do not wish to activate the region). It is then possible to cycle through the marks in reverse order by passing a prefix argument C-u C-SPC. With the evaluation of set-mark-command-repeat-pop as t we can continue cycling by repeated presses of C-SPC. Again though, this is not the type of functionality I rely on: for more deliberate actions of this sort, consider Emacs' notion of "registers".

(use-package emacs
  :config
  (setq repeat-on-final-keystroke t)
  (setq set-mark-command-repeat-pop t)
  :bind ("M-z" . zap-up-to-char))

6.6.6 Package lists

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

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

7 History and state

This section contains configurations for packages that are dedicated to the task of recording the state of various Emacs tools, such as the history of the minibuffer or the list of recently-visited files.

In practice, these are some of the most useful configurations one can make, as lots of functions depend on them. For example, a record of the minibuffer's history of inputs allows the completion framework to guess the most likely course of action. Typing M-x g gives me gnus as the first possible option, which is exactly what I want.

7.1 Emacs server and desktop (state of buffers)

The following uses the first running process of Emacs 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, aka "the server". The server persists for as long as there is an Emacs frame attached to it.

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

With some exceptions aside, I only ever use Emacs in a single frame. 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
  :config
  (setq desktop-auto-save-timeout 300)
  (setq desktop-dirname "~/.emacs.d/")
  (setq desktop-base-file-name "desktop")
  (setq desktop-files-not-to-save nil)
  (setq desktop-globals-to-clear nil)
  (setq desktop-load-locked-desktop t)
  (setq desktop-missing-file-warning t)
  (setq desktop-restore-eager 3)
  (setq desktop-restore-frames nil)
  (setq desktop-save 'ask-if-new)
  (desktop-save-mode 1))

7.2 Record various types of history

7.2.1 Recentf (recent files and directories)

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

A few words about the variables I configure:

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

Now some notes on my extensions:

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

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

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

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

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

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

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

7.2.2 Minibuffer history

Keeps a record of actions involving the minibuffer. This is of paramount importance to a fast and efficient workflow involving any completion framework that leverages the built-in mechanisms.

Emacs will remember your input and choices and will surface the desired results towards the top as the most likely candidates. Make sure to also check my configurations for the minibuffer and icomplete.

I set the length to a fairly high number, while I make sure that duplicate entries remain in tact. The assumption is that duplicate entries increase the likelihood of returning the candidate I am searching or.

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

7.2.3 Record cursor position

Just remember where the point is in any given file. This can often be a subtle reminder of what you were doing the last time you visited that file, allowing you to pick up from there.

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

7.2.4 Backups

And here are some settings pertaining to backups. I rarely need those, but I prefer to be safe in the knowledge that if something goes awry there is something to fall back to.

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

8 Important configurations that I stopped using

As my understanding of Emacs improves, I critically assess the utility of tools I once used. What is not needed is promptly removed. However, there are some cases where the deprecation of a package is basely solely on subjective criteria: the tools are still good, it just happens that I discovered something I like more.

Furthermore, there may be packages I once demonstrated in a video of mine, where I added my own extensions or whatnot and which people found useful.

Those are all stored in this section.

8.1 DEPRECATED Ivy/Counsel/Swiper plus filtering and scoring

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

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.

8.1.1 DEPRECATED Configurations for Ivy

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

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

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

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

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

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

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

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

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

8.1.2 DEPRECATED Prescient (sort, filter results)

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

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

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

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

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

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

(use-package ivy-prescient
  :disabled                             ; switched to `icomplete'
  :ensure t
  :after (prescient ivy)
  :config
  (setq ivy-prescient-sort-commands
        '(:not counsel-grep
               counsel-rg
               counsel-switch-buffer
               ivy-switch-buffer
               swiper
               swiper-multi))
  (setq ivy-prescient-retain-classic-highlighting t)
  (setq ivy-prescient-enable-filtering nil)
  (setq ivy-prescient-enable-sorting t)
  (ivy-prescient-mode 1))

8.1.3 DEPRECATED Counsel configurations and key bindings

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

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

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

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

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

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

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

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

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

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

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

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

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

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

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

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

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

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

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

8.1.4 DEPRECATED Swiper commands and settings

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

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

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

(use-package swiper
  :disabled                             ; switched to `icomplete'
  :ensure t
  :after ivy
  :config
  (setq swiper-action-recenter t)
  (setq swiper-goto-start-of-match t)
  (setq swiper-include-line-number-in-search t)
  :bind (("C-S-s" . swiper)
         ("M-s s" . swiper-multi)
         ("M-s w" . swiper-thing-at-point)
         :map swiper-map
         ("M-%" . swiper-query-replace)))

8.1.5 DEPRECATED Ivy extensions

These tools build on the foundation of Ivy and friends.

8.1.5.1 DEPRECATED Ivy rich

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

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

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

(use-package ivy-rich
  :disabled                             ; switched to `icomplete'
  :ensure t
  :after ivy
  :config
  (setq ivy-rich-path-style 'abbreviate)

  (setcdr (assq t ivy-format-functions-alist)
          #'ivy-format-function-line)
  (ivy-rich-mode 1))
8.1.5.2 DEPRECATED Ivy posframe

WARNING 2020-02-10: I have switched to a simpler, custom solution that uses the built-in icomplete tool. All deprecated packages contain the special :disabled keyword. For the new system, refer to the previous sections:

Below is the latest state of these settings prior to the switch.

This package allows us to reposition Ivy's window anywhere inside of the Emacs frame by placing it inside of a so-called "child frame", i.e. the "posframe". Furthermore, it is possible to use this feature on a per-command basis, all while assigning a different height to each function.

The ivy-posframe-parameters can be found in the source code of the main library. Do M-x find-library RET posframe RET where RET means to hit the Return key in order to proceed. In one of the parameters I show how to define a font that is different from what would normally be used (see this document's section on fonts).

(use-package ivy-posframe
  :disabled                             ; switched to `icomplete'
  :ensure t
  :after ivy
  :delight
  :config
  (setq ivy-posframe-parameters
        '((left-fringe . 2)
          (right-fringe . 2)
          (internal-border-width . 2)
          ;; (font . "DejaVu Sans Mono-10.75:hintstyle=hintfull")
          ))
  (setq ivy-posframe-height-alist
        '((swiper . 15)
          (swiper-isearch . 15)
          (t . 10)))
  (setq ivy-posframe-display-functions-alist
        '((complete-symbol . ivy-posframe-display-at-point)
          (swiper . nil)
          (swiper-isearch . nil)
          (t . ivy-posframe-display-at-frame-center)))
  (ivy-posframe-mode 1))

8.2 DEPRECATED Reading email with MU4E

WARNING (2020-01-30): I am no longer using MU4E as I switched to Gnus. This section is only kept for reference.

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

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

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

To set up offlineimap I used the Arch Wiki entry.

8.2.1 DEPRECATED mu4e extension for org-capture

WARNING (2020-01-30): I am no longer using MU4E as I switched to Gnus. This section is only kept for reference.

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

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