Protesilaos Stavrou
Philosopher. Polymath.

Dotemacs

Personal Emacs customisations

Created: 2019-09-15, 09:08 EET

Table of Contents

1 Overview

1.1 Canonical links to this document

1.2 What is this

If you check my init.el (inside the git repo that contains this file), you will see that all it does is prepare package.el, add melpa, and setup use-package. As such, this file covers the actual configurations, package definitions and their concomitant settings.

The present document is an example of "literate programming": a combination of ordinary language and inline code blocks. Emacs knows how to parse this file properly so as to evaluate only the elisp ("Emacs Lisp") included herein. The rest is for humans to make sense of my additions and their underlying rationale.

I find this paradigm particularly helpful for sharing Emacs configurations with a wider audience that includes new or potential users (I am still very new myself as of the summer of 2019).

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

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

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

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

1.3 Where I run Emacs

My OS is Debian 10 'Buster' (GNU/Linux). I run the latest tagged release of Emacs (26.3), which I have compiled from source, based on these instructions from the EmacsWiki. I do not optimise for portability, as I have no means of ever testing it.

1.4 Note about my methodology

I choose not to use many external packages until I familiarise myself with the defaults and/or with functionality I carefully introduce. The idea is to take things slowly and learn along the way while consulting the official manual and relevant documentation. This is necessary to make an informed decision about what is actually missing and what could be improved further.

External packages that I do use are either a clear upgrade over the defaults or otherwise extend the functionality of what is already available. For example, Magit is the superior choice for working with git, while amx nicely complements the built-in completion mechanism when running the M-x prompt.

Though a former Vim user, I choose not to use evil-mode or similar implementations from the start. I want to do things differently in order to ultimately set on the best approach for my use case. This topic is covered at greater length in my codelog entry on Why I switched to Emacs (2019-08-11).

Bear in mind that I have decided to omit Emacs-related configurations from my dotfiles. My Emacs usage might evolve to encompass workflows that were once covered by other programs (email, RSS…). I do not want to break the setup of my dotfiles in the meantime. Furthermore, Emacs has the potential to become the epicentre of a custom computing environment, meaning that it might become the "dotfiles".

1.5 Note about the use of the Super key

Some sections of this document assign functions to s-KEY. These are alternative ways of invoking common commands that are bound to otherwise awkward key chords. The original keys will continue to function as intended (for example, C-x o is also s-o).

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

Similarly, a tiling window manager that binds practically all of its motions to Super, will cause you trouble. Personally, I switched to Xfce: solid by default and, more importantly, easy to delete all its key bindings. Perhaps something like Openbox would also make for a good fit, but I have no time to tinker with that.

1.6 Note about use-package

You most likely are familiar with use-package. This is a tool that streamlines the configuration of packages. Though this might not be readily apparent, a "package" in Emacs parlance is any elisp file that is evaluated by Emacs. This includes things that are shipped with the upstream distribution.

As such I have two ways of implementing use-package:

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

Refer to my init.el file (part of this file's git repo) in order to see how I bootstrap use-package.

1.7 COPYING

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

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

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

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

2 Base settings

2.1 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.
  • Silence the messages that appear in the echo area during start up.
  • The pair of key bindings that involve z minimise the Emacs frame. I 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, as I experienced similar issues on various terminal emulators.
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq use-dialog-box nil)
(setq inhibit-splash-screen t)
(setq inhibit-startup-echo-area-message t)
(global-unset-key (kbd "C-z"))
(global-unset-key (kbd "C-x C-z"))
(global-unset-key (kbd "C-h h"))

2.2 Default typeface

2.2.1 About my choice of font

The primary objective of my font definitions is to cover my writing needs for both Latin and Greek scripts. Secondarily, the typefaces should ideally cover both roman and italic slants and their bold equivalents.

My favourite fonts are "Hack" and its ancestor "DejaVu Sans Mono". They offer wide glyph coverage, good support at small point sizes, fairly pronounced glyphs at the regular weight. Especially the latter is important for improving colour distinctiveness, particularly when using a light theme (see my Modus themes for Emacs).

I also like, though to a lesser extent, "Source Code Pro", "Iosevka", and "Fira Mono". However SCP has relatively light glyphs, Iosevka does not disambiguate hyphens from em dashes (critical for prose), while FM lacks italics.

I do not enjoy everything in the standard Hack distribution. The lack of a slab in the letter "i", the exaggerated shape of "1", the disproportionately thick dot/spot inside the "0". Thankfully, upstream provides a repository with alternatives glyphs, for those who wish to build a variant themselves. I used this to do the following:

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

This is the git repo of my custom Hack font. It is available under the same terms as "Hack" itself (MIT License). Note that you should better remove any other build of the original typeface before using my mod. On Debian per-user fonts are read from ~/.local/share/fonts/ (probably true for many other distros).

2.2.2 Technical considerations on GNU/Linux

On the implementation front, it is worth knowing that things are governed by the fontconfig library. This is true for Debian and others.

The font definition can thus accept several values other than the font family and size. Each value is separated by the colon sign. Have a look at this spec for the available parameters.

My fontconfig rules establish global slight hinting and RGB anti-aliasing. These can be overridden in Emacs with a definition that passes rules to the font, as documented in the aforementioned specification, such as Hack-13:hinting=true:hintstyle=hintfull.

2.2.3 Choose font based on availability

The conditions below set an order of priority for my desired typeface. The first match from the top is the one that is used. The structure of this snippet can be expanded to use as many typeface preferences as needed.

(cond
 ((member "Hack" (font-family-list))
  (set-frame-font "Hack-13" t t))
 ((member "DejaVu Sans Mono" (font-family-list))
  (set-frame-font "DejaVu Sans Mono-13" t t))
 ((member "Source Code Pro" (font-family-list))
  (set-frame-font "Source Code Pro-13" t t))
 ((member "Fira Mono" (font-family-list))
  (set-frame-font "Fira Mono-13" t t))
 ((member "Iosevka" (font-family-list))
  (set-frame-font "Iosevka-14.5" t t)))

(setq x-underline-at-descent-line nil)
(setq underline-minimum-offset 1)

;; Typeface suitability: can you discern the character at once?

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

;; Sample character set to check for monospacing and support for Greek.

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

2.3 Persistent state

2.3.1 Emacs server

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

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

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

2.3.2 Emacs "desktop" (state of buffers)

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

Overview of my settings:

  • Enable the mode that saves the "desktop", instructing it to load a small number of buffers at launch (desktop-restore-eager). The remainder of the buffer list will be loaded lazily. Lastly, I configure it to ignore frames because I seldom use those.
  • 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 that the documentation has two variables for the desktop path, so I just use both…
  • Finally, 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 pose 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.

(use-package desktop
  :config
  (desktop-save-mode 1)
  (setq desktop-restore-eager 3)
  (setq desktop-restore-frames nil)
  (setq desktop-path '("~/.emacs.d/"))
  (setq desktop-dirname "~/.emacs.d/")
  (setq desktop-base-file-name "desktop")
  (setq desktop-auto-save-timeout 300)
  (setq desktop-load-locked-desktop t))

2.3.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 Record history

2.4.1 Recent files

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 Ido (see its definition elsewhere in this doc) through its "virtual buffers" interface.

By default, the key chord C-x C-r is assigned to a function that opens files as read-only. I have no use for that, so I am repurposing it to the function that opens a dedicated file with the recent files.

(use-package recentf
  :config
  (recentf-mode 1)
  (setq recentf-save-file "~/.emacs.d/recentf")
  (setq recentf-max-menu-items 50)
  (setq recentf-max-saved-items 50)
  :bind ("C-x C-r" . recentf-open-files))

2.4.2 Minibuffer

Keeps a record of actions involving the minibuffer. Used by icomplete (see its definition elsewhere in this document).

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

2.4.3 Point (cursor position)

Just remember where the point is in any given file.

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

2.4.4 Backups

This section is subject to review.

And here are some settings pertaining to backups.

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

3 Interface and interactions

3.1 Theme and colours (Modus themes)

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

I call this project "Modus themes". It currently consists of "Modus Operandi" (light theme) and "Modus Vivendi" (dark). The source code is available here: gitlab.com/protesilaos/modus-themes.

There still is no MELPA package for these, as they remain under active development. That will change soon enough, once I am confident that most popular packages are styled appropriately. In the meantime, the theme files are placed in ~/.emacs.d, following this naming convention: THEME-NAME-theme.el.

(setq custom-safe-themes t) ;; stop asking if custom themes are safe
(load-theme 'modus-operandi 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).  The inverse applies when Vivendi
is in use."
  (interactive)
  (if (eq (car custom-enabled-themes) 'modus-operandi)
      (load-theme 'modus-vivendi t)
    (load-theme 'modus-operandi t)))

(global-set-key (kbd "s-t") 'prot/modus-themes-toggle)

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

3.2 Feedback

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

  • Answer with just the initials when dealing with "yes/no" questions.
  • Follow symlinks without asking.
  • Faster feedback for key chords (keys appear in the echo area).
  • Be quiet about auto-revert messages. They interrupt the minibuffer.
  • Indicate the end of a buffer, by adding marks in the fringe.
(fset 'yes-or-no-p 'y-or-n-p)
(setq vc-follow-symlinks t)
(setq frame-title-format '("%b %& GNU Emacs"))
(setq echo-keystrokes 0.25)
(setq auto-revert-verbose nil)
(setq indicate-empty-lines t)

3.3 Parentheses

Configure the mode that highlights matching 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 parenthesis if on screen, else highlight the expression enclosed by it.
  • Highlight parentheses even if the point is in their vicinity. This means the beginning or end of the line, with space in between.
  • Do not highlight a match when the point is on the inside of the parenthesis.
(use-package paren
  :config
  (show-paren-mode 1)
  (setq show-paren-style 'mixed)
  (setq show-paren-when-point-in-periphery t)
  (setq show-paren-when-point-inside-paren nil))

3.4 Configure 'electric' behaviour

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

  • Insert quotes and brackets in pairs. Only do so if there is no alphabetic character after the cursor.
  • Indent automatically.
  • The cryptic numbers in the pairs set, correspond to standard double quotes, their fancy (curly) equivalents, and these «». The contents of this set as always inserted in pairs, regardless of major mode. I do not add straight single quotes or backticks because of their importance in elisp.
  • When inputting a pair, inserting the closing character will just skip over the existing one, rather than add a new one. So typing ( will insert () and then typing ) will just be the same as moving forward one character C-f.
  • The rest concern the conditions for transforming quotes into their curly equivalents. I keep this disabled, because curly quotes are distinct characters. It is difficult to search for them. Just note that on GNU/Linux you can type them directly by hitting the "compose" key and then <" , >", <', >'.
(use-package electric
  :config
  (electric-pair-mode 1)
  (setq electric-pair-inhibit-predicate 'electric-pair-conservative-inhibit)
  (electric-indent-mode 1)
  (setq electric-pair-pairs '((34 . 34)
                              (8216 . 8217)
                              (8220 . 8221)
                              (171 . 187)))
  (setq electric-pair-skip-self 'electric-pair-default-skip-self)
  (electric-quote-mode -1)
  (setq electric-quote-context-sensitive t)
  (setq electric-quote-paragraph t)
  (setq electric-quote-string nil))

3.5 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. I understand that elisp uses its own approach, which I do not want to interfere with. Still I define the following for those files where Emacs is not particularly opinionated on the matter.

I guess this is subject to revision once I am more familiar with the various major/minor modes that influence the relevant indentation settings.

(setq-default tab-always-indent t)
(setq-default tab-width 4)

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

(setq-default cursor-type 'box)
(setq-default cursor-in-non-selected-windows 'hollow)
(setq x-stretch-cursor nil)

3.7 Mode line customisation

This package groups minor modes in a menu. This is great, because the more I keep expanding my Emacs usage, the higher the number of indicators on the modeline—too many distractions for information I do not readily need. The lighter consists of two mid dots (on GNU/Linux hit the "compose" key and then type . ^).

(use-package minions
  :ensure t
  :config
  (minions-mode 1)
  (setq minions-mode-line-lighter "··")
  (setq minions-direct '(flyspell-mode projectile-mode)))

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

(setq fill-column 72)
(column-number-mode 1)

3.9 Delete trailing whitespace

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

(add-hook 'before-save-hook 'delete-trailing-whitespace)

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

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

3.11 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)
  (mouse-wheel-mode 1)
  (setq mouse-wheel-progressive-speed t))

3.12 Selection

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

(use-package delsel
  :config
  (delete-selection-mode t))

3.13 Browse the kill ring

This neat package is for those cases where you have killed something a while ago and need to find it quickly. Use Super-y instead of M-y to access it (the latter works as before). A temporary buffer will pop up, allowing you move around using standard motions or just n and p. Confirm your choice with RET or exit with q.

(use-package browse-kill-ring
  :ensure t
  :config
  (setq browse-kill-ring-highlight-current-entry t)
  (setq browse-kill-ring-separator "——————")
  (setq browse-kill-ring-separator-face nil)
  :bind ("s-y" . browse-kill-ring))

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

(setq scroll-preserve-screen-position t)

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

(setq scroll-conservatively 1)
(setq scroll-margin 0)

3.15 TODO tool tips

3.16 Allow inputting Greek while preserving Emacs keys

The input option for Greek allows me to type in that language but still use all Emacs keys. It is not flawless, as some characters cannot be inserted via their regular keyboard combinations, but is still usable for most common needs. Toggle it with C-\.

(setq default-input-method "greek")

3.17 Enable actions like narrowing, capitalisation

The deactivated functions concern such things as manipulating the casing or capitalisation of the word/region, or narrowing down a region. I want them enabled by default.

(setq disabled-command-function nil)

3.18 Altered zap and easier repeat

Some minor conveniences:

Zap
I do not like the default behaviour of M-z (zap-to-char): it deletes the character you provide it with. Fortunately, there is a built-in replacement that deletes everything up to the character. Let's just rebind the key stroke.
Repeat
With this you can repeat the last command with C-x z and then just press z to run it over and over… Quite useful! IF you find yourself in need of something more complex, use keyboard macros.
(global-set-key (kbd "M-z") 'zap-up-to-char)
(setq repeat-on-final-keystroke t)

3.19 Toggle visual elements

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

(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)))
(global-set-key (kbd "C-c l") 'prot/toggle-line-numbers)

3.19.2 Display invisible characters (whitespace)

Viewing invisible characters (whitespace) can be very helpful under certain circumstances.

(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)))
(global-set-key (kbd "C-c i") 'prot/toggle-invisibles)

3.20 Multiple cursors

This package is very useful for operating on multiple positions within the same viewport (use occur, macros, regexp replacement etc. for more demanding tasks). Here I only include the actions that I find useful for my workflow.

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

3.21 More movements and motions

3.21.1 Move up/down more lines

Faster line movement next/prev. By default, the keys I assign these to start a selection and move it incrementally. I find no use for that.

(defun prot/multi-line-next ()
  "Moves the point 15 lines down."
  (interactive)
  (next-line 15))
(global-set-key (kbd "C-S-n") 'prot/multi-line-next)

(defun prot/multi-line-prev ()
  "Moves the point 15 lines up."
  (interactive)
  (previous-line 15))
(global-set-key (kbd "C-S-p") 'prot/multi-line-prev)

3.21.2 Move to next/prev line

Move directly to the next or the previous line (rather than C-a RET or C-e RET). The source for those two is this blog post, with minor tweaks by me.

(defun contrib/open-line-below ()
  "Opens a new line below the current one, regardless of the
point's position."
  (interactive)
  (end-of-line)
  (newline)
  (indent-for-tab-command))
(global-set-key (kbd "<C-return>") 'contrib/open-line-below)

(defun contrib/open-line-above ()
  "Opens a new line above the current one, regardless of the
point's position."
  (interactive)
  (beginning-of-line)
  (newline)
  (forward-line -1)
  (indent-for-tab-command))
(global-set-key (kbd "<C-S-return>") 'contrib/open-line-above)

3.21.3 More copy and kill motions

Some other ways of copying and killing.

(defun prot/kill-line-backward ()
  "Kill line backwards from the position of the point to the
beginning of the line.  This will not remove the line."
  (interactive)
  (kill-line 0))
(global-set-key (kbd "C-S-k") 'prot/kill-line-backward)

(defun prot/copy-line ()
  "Copies the current line."
  (interactive)
  (kill-ring-save (point-at-bol) (point-at-eol))
  (message "Current line copied"))
(global-set-key (kbd "C-c w") 'prot/copy-line)

3.21.4 Comment to end of line

Just turn everything from point to end of line into a comment (the whole line is done with C-x C-;). Trying this twice over the same region will un-comment it.

(defun prot/comment-eol ()
  "Comments out the line from the position of the points to the
end.  The behaviour is not a toggle, meaning that subsequent
invocations will keep adding the comment mark."
  (interactive)
  (comment-region (point) (point-at-eol)))
(global-set-key (kbd "C-c C-;") 'prot/comment-eol)

4 Selection candidates and search methods

4.1 Completion framework and enhancements

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.

I have made some changes since that video was published. Namely, reverting to the built-in ido-mode instead of Ivy and friends.

4.1.1 Ido (interactively do things)

I have a video demo about IDO.

This is the built-in framework for interactively narrowing down the list of matching candidates when performing a relevant search. Ido underpins functions such as those that change buffers, navigate the filesystem, query for help… I used to be an Ivy user, but have found that Ido is just as good for my case. Plus, I prefer its default horizontal layout. Simple and effective.

Here is an overview of my configurations:

  • Use ido-mode and make sure it runs everywhere it can.
  • Enable "flexible matching". If there is no matching string of adjacent characters, Ido will instead search for any item containing the characters in their given sequence even if they are not positioned directly next to each other. Their sequence is all that matters.
  • Also disable regexp and prefix matching by default. These can be toggled on at any moment with C-t or C-p respectively (read below for more key bindings).
  • Only consider the current frame. I seldom use more than one (recall that what Emacs calls "frames" is what window managers call "windows").
  • Create a buffer with completion candidates (manually invoke it with "?" after having typed a search), but do not place all completions there—just the current list of matches.
  • No need to confirm anything when there is a unique match.
  • Do not create a buffer when there is no match for the given search.
  • By default open matching buffers and files in the selected window. I use separate commands for doing the same for the "other window". By default, these are always accessed via C-x 4.
  • Keep track of selected directories (recall that navigating history is done with M-n and M-p).
  • Do not try to guess whether the symbol at point is a file name. This gives many false positives and consequently hampers the commands for filesystem navigation.
  • Same for URLs.
  • Use "virtual buffers" (e.g. recent files without a current buffer). I also have a separate key binding for that (see my use-package declaration for recentf).
  • Allow the theme's styles for Ido. I have configured those in my Modus themes (defined elsewhere in this document).
  • Keep the prompt to a single line. I find that a horizontal layout that spans multiple lines is counter-productive. One line is nice and simple. We are anyhow going to narrow down the list of candidates by typing a search.
  • As for ido-decorations it is better you search for its help buffer with C-h v ido-decorations RET. Basically, I tweak it to have less visual noise.

Then I just bind some common commands to the Super key. Where you see a capital letter, it means Super-Shift-KEY. For a complete overview of some useful key bindings, type C-h f ido-find-file RET. The bindings I define in ido-common-completion-map are for consistency with their equivalents in isearch.

(use-package ido
  :config
  (ido-mode 1)
  (setq ido-everywhere t)
  (setq ido-enable-flex-matching t)
  (setq ido-enable-regexp nil)
  (setq ido-enable-prefix nil)
  (setq ido-all-frames nil)
  (setq ido-buffer-disable-smart-matches t)
  (setq ido-completion-buffer "*Ido Completions*")
  (setq ido-completion-buffer-all-completions nil)
  (setq ido-confirm-unique-completion nil)
  (setq ido-create-new-buffer nil)
  (setq ido-default-buffer-method 'selected-window)
  (setq ido-default-file-method 'selected-window)
  (setq ido-enable-last-directory-history t)
  (setq ido-use-filename-at-point nil)
  (setq ido-use-url-at-point nil)
  (setq ido-use-virtual-buffers t)
  (setq ido-use-faces t)
  (setq ido-max-window-height 1)
  (setq ido-decorations
        '(" "
          "   "
          " | "
          " | …"
          "["
          "]"
          " [No match]"
          " [Matched]"
          " [Not readable]"
          " [Too big]"
          " [Confirm]"
          " "
          " "))
  :bind (("s-f" . ido-find-file)
         ("s-F" . ido-find-file-other-window)
         ("s-d" . ido-dired)
         ("s-D" . ido-dired-other-window)
         ("s-b" . ido-switch-buffer)
         ("s-B" . ido-switch-buffer-other-window)
         :map ido-common-completion-map
         ("M-e" . ido-edit-input)
         ("M-r" . ido-toggle-regexp)))

The following ensures that Ido mode is implemented in as many places as possible. I am not sure what is not covered by it, but so far every relevant interface provides Ido-style matching.

(use-package ido-completing-read+
  :ensure t
  :config
  (ido-ubiquitous-mode 1))

4.1.2 amx (minibuffer support for ido)

This package implements Ido-style completion for the M-x prompt, plus a nice scoring algorithm as well as a history of recent commands. Surprisingly simple and powerful.

Note that amx can show the key bindings that are associated with a given function. I keep it disabled because it (a) clutters the interface and (b) can cause a noticeable delay, according to its documentation.

(use-package amx
  :ensure t
  :after ido-completing-read+
  :config
  (amx-mode 1)
  (setq amx-backend 'ido)
  (setq amx-save-file "~/.emacs.d/amx-items")
  (setq amx-history-length 10)
  (setq amx-show-key-bindings nil))

4.1.3 Icomplete (disabled fallback option)

There is another built-in tool, which is older and less capable (as far as I can tell), which can be used as a fallback for those cases where both ido and amx cannot get the job done. Not sure what those cases would be, so I just keep this here disabled until I have a better understanding of it.

In my initial tests, icomplete will populate things that do not need Ido completion, such as renaming and copying files in Dired. It also creates visual inconsistencies, as it does not inherit the custom value I pass to ido-decorations.

(use-package icomplete
  :disabled
  :config
  (icomplete-mode 1)
  (setq icomplete-show-matches-on-no-input t)
  (setq icomplete-prospects-height 1))

4.2 isearch enhancements

I use isearch all the time for quick navigation, either to a visible part of the buffer or to some specific string I am aware of.

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

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

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

Mark match
Replaces the default mark command following a successful search. I prefer to mark the match. This can be then used to insert multiple cursors, kill the region, etc. Besides, it is always possible to mark a region from point to search string by running C-x C-x following a successful search.
Find region
Populate the search prompt with the contents of the region. Select a word or a phrase that would be harder to otherwise type out and run a search. Got this from a Reddit post on r/emacs.
DWIM delete
The isearch+.el library provides a ton of additions to the search function. I do not need any of them, except the following snippet, which deletes the entire failed match or just the last character (whatever is appropriate). This removes the entirety of a mismatch, just by hitting backspace. For valid searches, backspace functions exactly as expected, deleting one character at a time. Note, though, that it is no longer possible to delete part of a failed search, just by hitting backspace. For that you should anyway be doing a proper edit with M-e.
(use-package isearch
  :config
  (defun prot/isearch-mark-and-exit ()
    "Marks the current search string.  Can be used as a building
block for a more complex chain, such as to kill a region, or
place multiple cursors."
    (interactive)
    (push-mark isearch-other-end t 'activate)
    (setq deactivate-mark nil)
    (isearch-done))

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

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

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

5 Directory, project, buffer, window management

5.1 Configure dired (directory editor)

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

5.1.1 Base settings

The options here are meant to do the following:

  • Copy and delete recursively. Do not ask about it.
  • Search only file names while point is there, else the rest (the latter is 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). There are also options for tweaking the behaviour of find-name-dired, in the same spirit.
  • Hide all the verbose details by default (permissions, size, etc.). These can easily be toggled on using the left parenthesis ( inside a dired buffer.
  • 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's path as a prefix when performing such actions.
  • Enable asynchronous mode. This is subject to change, as I need to test it a bit more.
(use-package dired
  :config
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq dired-isearch-filenames 'dwim)
  (setq delete-by-moving-to-trash t)
  (setq dired-listing-switches "-AFlv --group-directories-first") ;; M-x man RET ls
  (setq find-ls-option ;; applies to `find-name-dired'
        '("-print0 | xargs -0 ls -AFlv --group-directories-first" . "-AFlv --group-directories-first"))
;;; activate this for case-insensitive names:
;; (setq find-name-arg "-iname")
  (setq dired-dwim-target t)
  :hook
  (dired-mode . dired-hide-details-mode)
  :bind (("s-d" . dired)
         ("s-D" . dired-other-window)))

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

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

  1. TODO find-name-arg instead of find-ls-option?

5.1.2 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
  :config
  (setq wdired-allow-to-change-permissions t)
  (setq wdired-create-parent-directories t))

5.1.3 File previews (including images)

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

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

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

5.1.4 Tree-style view

This is great. Tree-style view of subdirectories. The tab key will expand a directory right below its parent. Shift+Tab will contract it.

This does not override the action of inserting a subdirectory listing in the current dired buffer (with i over the target dir).

(use-package dired-subtree
  :ensure t
  :after dired
  :bind (:map dired-mode-map
              ("TAB" . dired-subtree-insert)
              ("<S-iso-lefttab>" . dired-subtree-remove)
              ("DEL" . dired-subtree-remove)))

5.1.5 dired-x

Some additional features that are shipped with Emacs.

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

5.2 Git repositories

5.2.1 Git front-end (Magit)

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

(use-package magit
  :ensure t
  :config
  (setq magit-completing-read-function 'magit-ido-completing-read)
  :bind (("C-c g s" . magit-status)
         ("C-c g d" . magit-dispatch)
         ("s-g" . magit-status)
         ("s-G" . magit-dispatch)))

5.2.2 Git projects (Projectile)

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

In terms of integration, I am adding an extension to it which leverages the ido framework I am already using (see elsewhere in this document).

Make sure you check the Projectile manual.

(use-package projectile
  :ensure t
  :config
  (projectile-mode 1)
  (setq projectile-project-search-path '("~/Projects/"))
  (setq projectile-indexing-method 'hybrid)
  (setq projectile-sort-order 'default)
  (setq projectile-enable-caching t)
  (setq projectile-completion-system 'ido)
  :bind (("s-SPC" . projectile-find-file)
         ("S-s-SPC" . projectile-switch-project)))

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

5.3 Working with buffers

These settings make it easier to work with multiple buffers. When two buffers have the same name, Emacs will try to disambiguate them by first displaying their path inside angled brackets, while stripping the part they have in common.

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

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

5.3.1 Configure ibuffer

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

Some tweaks to the default behaviour and presentation:

  • Prompt for confirmation only when deleting a modified buffer.
  • Hide the summary.
  • Do not open on the other window (not focused window).
  • Remap default key to launch ibuffer instead of list-buffers.
(use-package ibuffer
  :config
  (setq ibuffer-expert t)
  (setq ibuffer-display-summary nil)
  (setq ibuffer-use-other-window nil)
  :bind ("C-x C-b" . ibuffer))

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

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

5.4 Window configuration

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

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

5.4.1 Basic tweaks

These key bindings are complementary to the standard ones. They all involve the Super key.

tear-off-window allows us to place the current window in its own frame. Remember, what Emacs calls "windows" is the splits of the viewport. Whereas a "frame" is the rectangle your window manager controls.

(use-package window
  :bind (("s-n" . next-buffer)
         ("s-p" . previous-buffer)
         ("s-o" . other-window)
         ("s-2" . split-window-below)
         ("s-3" . split-window-right)
         ("s-0" . delete-window)
         ("s-1" . delete-other-windows)
         ("s-T" . tear-off-window)))

5.4.2 winner-mode

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

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

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

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

6 Applications and utilities

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

6.1 Configure Org-mode

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

Overview of the following settings:

Disable opinionated Org bindings
I want Emacs to be my editor and this includes the choice of key chords. Org is very opinionated with its decisions and maps all sorts of—often specialised—actions to keys that follow the C-c KEYS sequence. This breaks my setup. I prefer to disable all of the default org-mode keys that conflict with my own defititions and then choose to implement the ones I might need for my own workflow. These are the offending keys I have identified thus far.
Style code blocks
I want .org files to use the native settings for styling code blocks. The first variable concerns C-c ' (run it inside of a code block). That opens a buffer with just the contents of the code block, with the major mode configured appropriately.
(use-package org
  :config
  ;; disable keys I rely on
  (define-key org-mode-map (kbd "<C-return>") nil)
  (define-key org-mode-map (kbd "<C-S-return>") nil)
  (define-key org-mode-map (kbd "<M-return>") nil)
  (define-key org-mode-map (kbd "C-c >") nil)
  (define-key org-mode-map (kbd "C-c <") nil)
  (define-key org-mode-map (kbd "C-c C-;") nil)
  (define-key org-mode-map (kbd "C-c M-f") nil)
  ;; code blocks
  (setq org-src-window-setup 'current-window)
  (setq org-src-fontify-natively t)
  (setq org-src-tab-acts-natively t)
  (setq org-confirm-babel-evaluate nil)
  (setq org-edit-src-content-indentation 0))

6.1.1 Export Org to HTML

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

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

6.2 Shells and terminal emulators

I seldom need a terminal emulator (or, in this case, a shell). Emacs covers practically everything a terminal does. But there are some cases where running a shell is necessary, such as when updating Debian (through apt).

For one-off actions or certain custom scripts of mine, I get what I need from shell-command (default key is M-!). Otherwise, I launch a dedicated terminal.

6.2.1 Shell

This is a shell that runs inside of Emacs. Unlike term (see below), this one can use standard Emacs keys and behaves like an ordinary buffer.

(use-package shell
  :bind ("<s-return>" . shell))

6.2.2 TODO Eshell

I am well aware of eshell and the potential value of running elisp functions, but I have yet to see a compelling workflow that would justify it. Still need to read all the relevant documents…

6.2.3 Terminal emulator

term and ansi-term are terminal emulators (like Xterm). 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 a 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 the two is the ability to run one or multiple buffers simultaneously. Better check the documentation for this point.

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

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

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

(use-package proced
  :config
  (setq proced-toggle-auto-update t)
  (setq proced-auto-update-interval 1)
  (setq proced-descend t)
  (setq proced-filter (quote user)))

6.4 Pass interface (password-store)

pass is a password manager that uses GPG and standand UNIX tools to handle passwords. Encrypted files are stored in a plain directory structure. Very simple, very nice. The following provides an Emacs interface.

(use-package password-store
  :ensure t
  :bind ("C-c M-p" . password-store-copy))

6.5 Elfeed (feed reader for RSS/Atom)

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

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

(use-package elfeed
  :ensure t
  :config
  (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-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))))

  :bind (("C-c M-f" . elfeed)
         :map elfeed-search-mode-map
         ("g" . elfeed-update)
         ("G" . elfeed-search-update--force)
         :map elfeed-show-mode-map
         ("v" . ambrevar/elfeed-play-with-mpv))
  :hook (after-init . prot/feeds))

6.6 Emacs web browser and HTML renderer

As far as I can tell, the following shr-* variables concern an HTML renderer 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
  :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)))

6.7 Bongo (Music player)

I already tried EMMS and various other options but did not stick with them. I felt I was missing something. 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).

(use-package bongo
  :ensure t
  :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-header-icons nil)
  (setq bongo-display-playback-mode-indicator nil)
  (setq bongo-display-inline-playback-progress nil)
  (setq bongo-header-line-mode nil)
  (setq bongo-mode-line-indicator-mode nil)

  (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
        ;; Is this always safe or can there be more than
        ;; one Bongo buffer?
        (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 becomes 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)))
  :hook
  (dired-mode . prot/bongo-dired-library)
  :bind (("s-m m" . bongo-playlist)
         ("s-m s" . bongo-pause/resume) ;; start/stop
         ("s-m n" . bongo-next)
         ("s-m p" . bongo-previous)
         :map bongo-dired-library-mode-map
         ("SPC" . (lambda ()
                    (interactive)
                    (contrib/bongo-add-dired-files)
                    (bongo-play-random)
                    (bongo-random-playback-mode 1)))))

6.7.1 TODO start bongo in the background when emacs launches

6.7.2 TODO save/load playlists

6.7.3 TODO read metadata

6.7.4 TODO browse/search by metadata

6.8 Email settings

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

  • The built-in SMTP capabilities to send email.
  • The mu4e front-end to the mu mail indexer. This is for finding and reading email. Both of those tools are available for Debian 10 'buster': apt install mu4e.
  • The offlineimap utility to store a local copy of my email (from Debian: apt install offlineimap). This is necessary for mu to actually do its work.

6.8.1 Sending email (SMTP)

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

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

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

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

6.8.2 Reading email (MUA)

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

  • Include mu4e in the load path. This is necessary because we are using the Debian package.
  • Use mu4e as the default MUA in Emacs. This concerns actions such as C-x m (compose-mail).
  • Do not run anything to get new mail. The task is handled by systemd, so that email syncing does not mess up with Emacs (or vice versa). I am not sure why the docs suggest setting this to "true" rather than nil… Will eventually test things with the latter.
  • Do not provide verbose output about indexing operations.
  • Update manually, because I anyway interact with email only when I have time to check it.
  • Use my selected completion framework (Ido, Ivy…) where relevant.
  • Define my signature and include it in new messages.
  • Specify the directory where mail is stored. This is where offlineimap is configured to place its findings. Each email account has its own subdirectory therein.
  • Careful with this: Store sent messages in their appropriate place (defined in the "contexts" file—see further below). The docs suggest that IMAP accounts should opt for either moving messages to the trash directory or outright deleting them. The idea is that IMAP is supposed to handle this stuff automatically—my initial tests with my configs do not confirm this, which is why I just tell it to place them in the "sent" directory.
  • Do not kill message buffer upon exit from it. It can always be useful to quickly check something.
  • Always show email addresses (the default is to display just the name).
  • The variables that concern mu4e contexts are relevant because of prot/mu4e-contexts. It loads the mu4e-contexts with all the information about my account setup. I set this in a private and encrypted file. Do check the example in the official docs. I based my work off of it.
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e")
(use-package mu4e
  :after (smtpmail smtpmail-async)
  :config
  (setq mail-user-agent 'mu4e-user-agent)
  (setq mu4e-get-mail-command "true")
  (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)

  (defun prot/mu4e-contexts ()
    "Loads a file with the specifics of my email account info."
    (let ((mails "~/.emacs.d/mu4e-contexts.el.gpg"))
      (when (file-exists-p mails)
        (load-file mails))))

  :hook (after-init . prot/mu4e-contexts))

To set up offlineimap I used the Arch Wiki entry.

7 Language settings

7.1 Recognise subwords

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

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

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

(add-hook 'text-mode-hook (lambda ()
                            (turn-on-auto-fill)
                            (setq adaptive-fill-mode t)))

7.3 Markdown support

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

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

7.4 Spell check

I need spell checking for both English and Greek. Perhaps I will add French and Spanish in the future, though it has been a while since I typed anything in those languages…

Anyhow, these settings are meant to check for mixed language content, so there is no need to changed dictionaries. It also runs continuously, highlighting any errors on the spot.

Note that hunspell is not part of Emacs. I install the relevant packages from the Debian archive with apt install hunspell{,-el}.

The value of ispell-local-dictionary-alist is adapted from Chen Bin's blog.

(use-package flyspell
  :init
  (setq ispell-program-name "hunspell")
  (setq ispell-local-dictionary "en_GB")
  (setq flyspell-issue-message-flag nil)
  (setq flyspell-issue-welcome-flag nil)
  (setq ispell-local-dictionary-alist
        '(("en_GB"
           "[[:alpha:]]"
           "[^[:alpha:]]"
           "[']"
           nil
           ("-d" "en_GB,el_GR")
           nil
           utf-8)))
  :hook
  (text-mode . turn-on-flyspell)
  (prog-mode . turn-off-flyspell))

IMPORTANT: I experience a weird, but critical issue, when I use :config instead of :init, where each indentation inside of an Org code block relaunches a Flyspell process. Probably this is due to something wrong on my end. Will investigate.

7.5 Word completion (built-in tools)

I tried the company package for a while, but it has a few issues I did not like. this inspired me to reconsider Emacs' own approach to the matter: "dynamic abbreviation expansion". I then realised that hippie-expand (also shipped with Emacs) can be configured to behave in a more predictable way.

I keep the default key bindings for dabbrev and use the Super key for hippie-expand. Here is my workflow:

  • Use dabbrev-expand (bound to M-/) to complete a target. This can also include hyphens and slashes that are common in elisp.
  • Use hippie-expand (assigned to s-/) to complete something longer, such as a sentence, statement, expression.
  • Use dabbrev-completion (=C-M-/) to produce a list of completion candidates.

This does not give me autocompletion, but I am okay with that.

(use-package dabbrev
  :config
  (setq dabbrev-abbrev-char-regexp nil)
  (setq dabbrev-backward-only nil)
  (setq dabbrev-eliminate-newlines nil)
  (setq dabbrev-ignored-buffer-names (quote ("^ \\*.*\\*$" "dired-mode")))
  (setq hippie-expand-ignore-buffers (quote ("^ \\*.*\\*$" "dired-mode")))
  (setq dabbrev-upcase-means-case-search t)
  (setq hippie-expand-try-functions-list '(try-expand-line
                                           try-expand-list
                                           try-expand-dabbrev
                                           try-expand-dabbrev-all-buffers
                                           try-expand-all-abbrevs
                                           try-expand-dabbrev-from-kill
                                           try-complete-lisp-symbol-partially
                                           try-complete-lisp-symbol
                                           try-complete-file-name-partially
                                           try-complete-file-name))
  :bind ("s-/" . hippie-expand))

7.6 TODO code linting