Protesilaos Stavrou
Philosopher. Polymath.

Dotemacs

Personal Emacs customisations

Created: 2019-08-23, 09:54 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, what is covered in this file is the actual package definitions.

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 configurations 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, 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.

;; Source: https://news.ycombinator.com/item?id=20156004 with minor
;; tweaks by me.
(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 < s e TAB will give you a block with emacs-lisp.

1.3 Where I run Emacs

My OS is Debian 10 'Buster' (GNU/Linux). What I have here works with the version of Emacs from Debian's archive. I do not optimise for portability, as I have no means of ever testing it.

1.4 Note about my methodology

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

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. This is necessary to make an informed decision about what is actually missing and what could be improved further.

In this light, and as 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.

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.

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

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.

(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq use-dialog-box nil)
(setq inhibit-splash-screen t)

The following is a set of keys to 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.

(global-unset-key (kbd "C-z"))
(global-unset-key (kbd "C-x C-z"))

Also disable this "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.

(global-unset-key (kbd "C-h h"))

2.2 Default typeface

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

I then prioritise typefaces that are available as Debian packages, but would not hesitate to include fonts from other sources, if they truly are superior to the ones I use.

Bitmap fonts are ruled out for two main reasons:

  • Poor colour contrast against light backgrounds, courtesy of their thin letter forms.
  • Limited flexibility for typographic needs (italics and/or bold are missing or sub-par, sizes are limited…).

Based on my tests, only Terminus is a truly excellent typeface in terms of design, but still suffers from being a bitmap font…

On the implementation front, it is worth knowing that things are governed by fontconfig. This is true for Debian and probably every other GNU/Linux distro. The font definition can 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 more options.

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.

;; Priority is from top to bottom
(cond ((member "Hack" (font-family-list))
       (set-default-font "Hack-11.5" t t)
       (set-frame-font "Hack-11.5" t t)
       (setq x-underline-at-descent-line nil)
       (setq underline-minimum-offset 1))
      ((member "DejaVu Sans Mono" (font-family-list))
       (set-default-font "DejaVu Sans Mono-11.5" t t)
       (set-frame-font "DejaVu Sans Mono-11.5" t t)
       (setq x-underline-at-descent-line nil)
       (setq underline-minimum-offset 1)))

;; Change font if you cannot clearly discern the character at first
;; glance…

;; ()[]{}<>«»‹›
;; 6bB8&0ODdo
;; 1iIl|
;; !ij
;; 5$Ss
;; 7Zz
;; gq
;; nmM
;; vvwWuuw
;; x×X
;; .,·*^
;; :;
;; `'
;; ''"
;; '
;; "
;; —–-~≈=_.…

;; Sample character set

;; ABCDEFGHIJKLMNOPQRSTUVWXYZ 12345
;; abcdefghijklmnopqrstuvwxyz 67890
;; {}[]()<>$*-+=/#_%^@\&|~?'"`!,.;:
;; Illegal1i = oO0          
;; The quick brown fox, (..) Hello,
;; jumps over lazy dog. /__\ World!
;;       
;;
;; ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
;; αβγδεζηθικλμνξοπρστυφχψω

This is introduced in Emacs 26.2. Prevents coloured fonts from being loaded (emoji stuff).

(let ((emacs-minver "26.2"))
  (when (version< emacs-version emacs-minver)
    (setq xft-ignore-color-fonts t)))

2.3 Custom files generated by Emacs

This section contains settings for the various backup files Emacs creates, as well as the values it appends to the "custom*" function.

;;; Place the variables created by Emacs (custom*) in another file.
;;; Do not interfere with my init.el
(setq custom-file "~/.emacs.d/custom.el")
(unless (file-exists-p custom-file)
  (write-region "" nil custom-file))
(load custom-file)

;;; Do not add backups everywhere
(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) ; no lockfiles

2.4 Transient

This fixes an issue in Emacs 26.1 where the package refresh fails to fetch the "gnu" archive. Should be patched soon.

(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3") ;; temporary fix

2.5 Persistent state

2.5.1 Emacs server

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

(server-start)

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

2.5.2 Emacs "desktop" (state of buffers)

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

First we set the history to a reasonable length (adjust according to your needs):

(setq history-length 250)

Next, we enable the mode that saves the "desktop", instructing it to load a small number of buffers at startup (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.

(desktop-save-mode 1)
(setq desktop-restore-eager 10)
(setq desktop-restore-frames nil)

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…

(setq desktop-path '("~/.emacs.d/"))
(setq desktop-dirname "~/.emacs.d/")
(setq desktop-base-file-name "desktop")
(setq desktop-auto-save-timeout 300)

And here comes a setting that needs to be explained in greater detail.

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.

(setq desktop-load-locked-desktop t)

2.6 Record history

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

(recentf-mode 1)
(setq recentf-save-file "~/.emacs.d/recentf")
(setq recentf-max-menu-items 50)
(setq recentf-max-saved-items 50)

2.6.2 Minibuffer

Keeps a record of actions involving the minibuffer. Not sure whether this is useful in conjunction with counsel. I am keeping it here, but disabling it.

(savehist-mode -1)
(setq savehist-file "~/.emacs.d/savehist")
(setq history-length 100)

2.6.3 Point (cursor position)

Just remember where the point is in any given file.

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

2.6.4 TODO Bookmarks

I have had a cursory look at bookmarks, but still need to assess whether they would fit into my workflow and how.

(setq bookmark-default-file "~/.emacs.d/bookmarks")

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

;; Source of this theme toggle (adaptations by me):
;; https://emacs.stackexchange.com/questions/24088/make-a-function-to-toggle-themes
(defvar modus-theme-dark 'modus-vivendi)
(defvar modus-theme-light 'modus-operandi)
(defvar modus-theme-current modus-theme-light)

;; disable other themes before loading new one
(defadvice contrib/load-theme (before theme-dont-propagate activate)
  "Disable theme before loading new one."
  (mapcar #'disable-theme custom-enabled-themes))

(defun contrib/next-theme (theme)
  (if (eq theme 'default)
      (disable-theme 'default)
    (progn
      (load-theme theme t)))
  (setq modus-theme-current theme))

(defun contrib/toggle-theme ()
  (interactive)
  (cond ((eq modus-theme-current modus-theme-dark) (contrib/next-theme modus-theme-light))
        ((eq modus-theme-current modus-theme-light) (contrib/next-theme modus-theme-dark))))

(global-set-key (kbd "C-c M-t") 'contrib/toggle-theme)

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

  • Insert quotes and brackets in pairs.
  • Indicate matching parenthesis (essential for elisp code).
  • Answer with just the initials when dealing with "yes/no" questions.
  • Follow symlinks without asking.
  • Kill the marked region when yanking over it (replace selection).
  • 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.

    (electric-pair-mode 1)
    (show-paren-mode 1)
    (fset 'yes-or-no-p 'y-or-n-p)
    (setq vc-follow-symlinks t)
    (delete-selection-mode t)
    (setq x-stretch-cursor nil)
    (setq-default frame-title-format '("%b %& GNU Emacs"))
    (setq echo-keystrokes 0.25)
    (setq auto-revert-verbose nil)
    (setq-default indicate-empty-lines t)
    

The package which-key opens up an interface with all possible completion targets for a key chord chain. By default, it brings up an interface after a second of idleness when a key chord has been initiated. I personally prefer it to come up a bit later, but can call it earlier with C-h.

(use-package which-key
  :ensure t
  :config
  (setq which-key-show-early-on-C-h t)
  (setq which-key-idle-delay 2)
  (setq which-key-idle-secondary-delay nil)
  (which-key-setup-side-window-bottom)
  (which-key-mode))

3.3 Cursor appearance and tweaks

beacon is a package that can improve accessibility without being distracting. It highlights the point under certain conditions. This makes it easier to identify the cursor. I configure it to be less intrusive than its default configuration. You can learn more about each of these variables by searching for them via C-h v.

(use-package beacon
  :ensure t
  :config
  (beacon-mode 1)
  (setq beacon-lighter "")
  (setq beacon-blink-delay 0.1)
  (setq bracon-blink-duration 0.50)
  (setq beacon-size 35)
  (setq beacon-blink-when-point-moves-vertically nil)
  (setq beacon-blink-when-point-moves-horizontally nil)
  (setq beacon-blink-when-window-scrolls nil)
  (setq beacon-blink-when-buffer-changes nil)
  (setq beacon-blink-when-focused nil)
  (setq beacon-blink-when-window-changes t)
  :bind ("s-`" . beacon-blink))

Were it not for beacon, I would set the cursor to a box. 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 focused window has a vertical bar that is 2/8 the size of a box. Unfocused windows have a hollow box.

(set-default 'cursor-type '(bar . 2))
(set-default 'cursor-in-non-selected-windows 'hollow)

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

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

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

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

3.6 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)
(electric-indent-mode 1)

3.7 Delete trailing whitespace

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

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

3.8 Preserve contents of system clipboard

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

(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.9 Mouse selection to clipboard

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

(setq mouse-drag-copy-region t)

3.10 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.11 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.12 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.13 Undo tree and change history navigation

undo-tree is shipped with the version of Emacs that is in the Debian 10 stable repos. Enabling it provides key bindings for undo and redo actions, while it converts the history of actions from linear to a tree design (branching paths).

(use-package undo-tree
  :init (global-undo-tree-mode))
;;; C-/   Undo
;;; C-?   Redo

This is the package that handles the cycling through the history of changes. It simply jumps to the position of the last change. Repeated invocations are possible.

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

3.14 Altered zap behaviour

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.

(global-set-key (kbd "M-z") 'zap-up-to-char)

3.15 Easier `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.

(setq repeat-on-final-keystroke t)

3.16 Toggle visual elements

3.16.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.16.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.17 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. Note that visual-regexp (defined elsewhere in this document) provides an interface for multiple cursors.

(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.18 More movements and motions

3.18.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.18.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.18.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.18.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 Directory, project, buffer, window management

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

4.1.1 Base settings

The options here are meant to do the following:

  • Copy and delete recursively. Do not ask about it.
  • Search only file names while point is there, else the rest (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.
  • Enable asynchronous mode. This is subject to change, as I need to test it a bit more.
(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")
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
(autoload 'dired-async-mode "dired-async.el" nil t)
(dired-async-mode 1)

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

4.1.2 wdired (writable dired)

This is the editable state of a dired buffer. Access it with C-x C-q. Write changes, 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 automatically.
(setq wdired-allow-to-change-permissions t)
(setq wdired-create-parent-directories t)

4.1.3 Two-pane dired is smart

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.

(setq dired-dwim-target t)

4.1.4 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
  :bind (:map dired-mode-map
              ("P" . peep-dired))
  :config
  (setq peep-dired-cleanup-on-disable t)
  (setq peep-dired-ignored-extensions '("mkv" "webm" "mp4" "mp3" "ogg" "iso")))

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

4.1.5 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
  :config
  :bind (:map dired-mode-map
              ("TAB" . dired-subtree-insert)
              ("<S-iso-lefttab>" . dired-subtree-remove)
              ("DEL" . dired-subtree-remove)))

4.1.6 dired-x

Some additional features that are shipped with Emacs (at least on Debian 10).

;; Source: https://www.gnu.org/software/emacs/manual/html_mono/dired-x.html
(add-hook 'dired-load-hook
          (lambda ()
            (load "dired-x")
            ;; Set dired-x global variables here.
            (setq dired-clean-confirm-killing-deleted-buffers t)
            ))
(add-hook 'dired-mode-hook
          (lambda ()
            ;; Set dired-x buffer-local variables here.  For example:
            ;; (dired-omit-mode 1)
            ))

(autoload 'dired-jump "dired-x"
  "Jump to Dired buffer corresponding to current buffer." t)

(autoload 'dired-jump-other-window "dired-x"
  "Like \\[dired-jump] (dired-jump) but in other window." t)

(define-key global-map "\C-x\C-j" 'dired-jump)
(define-key global-map "\C-x4\C-j" 'dired-jump-other-window)

4.1.7 Dired motions bound to Super

Just assigning some common commands to the super key (the one with the Windows logo on most commercial keyboards). Those with a capital letter also involve the shift key. Basically, super+KEY means the current window, else the other one.

(global-set-key (kbd "s-d") 'dired)
(global-set-key (kbd "s-j") 'dired-jump)
(global-set-key (kbd "s-D") 'dired-other-window)
(global-set-key (kbd "s-J") 'dired-jump-other-window)

4.2 Git repositories

4.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
  :bind (("C-c M-g" . magit-status)
         ("s-g" . magit-status)))

4.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 ivy 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-sort-order 'recently-active)
  (setq projectile-enable-caching t)
  (setq projectile-completion-system 'ivy))

(use-package counsel-projectile
  :ensure t
  :after (ivy counsel)
  :bind (("C-c p SPC" . counsel-projectile)
         ("C-c p p" . counsel-projectile-switch-project)
         ("C-c p f" . counsel-projectile-find-file-dwim)
         ("C-c p d" . counsel-projectile-find-dir)
         ("C-c p b" . counsel-projectile-switch-to-buffer)))

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

4.3 Working with buffers

These settings make it easier to work with multiple buffers. The value of uniquify renames buffers with the same name by appending to them their parent directory's name. While the second option ensure that they revert back to their standard name when unification is no longer needed.

(setq uniquify 'post-forward)
(setq uniquify-after-kill-buffer-p t)

4.3.1 Configure ibuffer

ibuffer is a replacement for buffer-list that allows for fine-grained control of 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.
  • Always open on the other window (not focused window).
  • Remap default key to launch ibuffer instead of list-buffers.
(setq ibuffer-expert t)
(setq ibuffer-display-summary nil)
(setq ibuffer-use-other-window t)
(global-set-key (kbd "C-x C-b") 'ibuffer)

4.3.2 Integrate with Git

This 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 projectile
  :hook ibuffer . (lambda ()
                    (ibuffer-projectile-set-filter-groups)
                    (unless (eq ibuffer-sorting-mode 'recency)
                      (ibuffer-do-sort-by-recency))))

And we also want to be able to access magit directly from inside of ibuffer (integration intensifies!). This function is provided by Manuel Uberti.

(defun contrib/ibuffer-magit ()
  "Open `magit-status' for the current buffer."
  (interactive)
  (let ((buf (ibuffer-current-buffer t)))
    (magit-status (cdr (ibuffer-projectile-root buf)))))
(define-key ibuffer-mode-map (kbd "G") 'contrib/ibuffer-magit)

4.3.3 Bind buffer actions to Super

Just some alternative key chords for the most common commands. Where it ends with a capital letter, you also need to hold down shift. Same as with S-s-KEY.

(global-set-key (kbd "s-f") 'find-file)
(global-set-key (kbd "s-b") 'switch-to-buffer)
(global-set-key (kbd "s-SPC") 'counsel-projectile)
(global-set-key (kbd "s-F") 'find-file-other-window)
(global-set-key (kbd "s-B") 'switch-to-buffer-other-window)
(global-set-key (kbd "S-s-SPC") 'projectile-dired-other-window)
(global-set-key (kbd "s-n") 'next-buffer)
(global-set-key (kbd "s-p") 'previous-buffer)

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

4.4.1 Basic tweaks

Tear off the window by holding down Ctrl and pressing left click on the mode line. This allows us to place the 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.

(global-set-key [mode-line C-mouse-1] 'tear-off-window)

4.4.2 winner-mode

This will enable winner-mode. What that does is keep a record of the various window layouts, allowing us to cycle it through it. An extremely useful piece of functionality!

(winner-mode 1)

Bind winner history to super and the right/left arrow keys. Reflects next-buffer (C-x <right>) and previous-buffer (C-x <left>).

(global-set-key (kbd "<s-right>") 'winner-redo)
(global-set-key (kbd "<s-left>") 'winner-undo)

I do not care about switching to windows in any of the cardinal directions. I seldom have more than two open at any one time. A third one would still be fiarly easy to access. Besides, the keys that would be used for such multiplexing can be used to greater effect elsewhere.

4.4.3 Bind common motions to Super

The regular key chords will work as expected. These are some additional definitions that speed things up. Recall that super is the "Windows key" on most of the keyboards out there.

(global-set-key (kbd "s-o") 'other-window)
(global-set-key (kbd "s-2") 'split-window-below)
(global-set-key (kbd "s-3") 'split-window-right)
(global-set-key (kbd "s-0") 'delete-window)
(global-set-key (kbd "s-1") 'delete-other-windows)

5 Selection candidates and search methods

5.1 Completion framework and enhancements

As discussed in 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.

5.1.1 Ivy (the underlying mechanism)

After trying ido-mode and smex for a while, I feel there are a few areas that could be improved further. This is where ivy and its companion utilities come in. They offer improved selection algorithms, while still staying close to the paradigms of the default experience.

(use-package ivy
  :ensure t
  :config
  (ivy-mode)
  (setq ivy-display-style 'fancy)
  (setq ivy-use-virtual-buffers t)
  (setq enable-recursive-minibuffers t)
  (setq ivy-use-selectable-prompt t))

5.1.2 Swiper (search tool)

swiper performs searches using the ivy backend. I still use isearch for quick navigation to a character/word, so swiper is used for more demanding searches or for when I am not sure whether I want to go up or down the buffer.

Note that this tool offers a lot of functions that I still need to explore, so this is just it for the time being.

(use-package swiper
  :ensure t
  :after ivy
  :bind (("s-s" . swiper)) ;; Super-s
  :config
  (setq swiper-include-line-number-in-search t))

5.1.3 Enhanced filesystem navigation (and more)

counsel is a suite of functions that replace standard file and buffer navigation commands with ivy-powered ones. Note the C-r. This is the same key you would use in the terminal to search through your commands (only works inside of counsel).

(use-package counsel
  :ensure t
  :after ivy
  :bind (("M-x" . counsel-M-x)
         ("C-x C-f" . counsel-find-file)
         ("C-x b" . counsel-ibuffer)
         ("C-x B" . counsel-switch-buffer-other-window)
         ("C-x d" . counsel-dired)
         ("C-x C-r" . counsel-buffer-or-recentf)
         :map minibuffer-local-map
         ("C-r" . counsel-minibuffer-history)))

5.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. I let swiper handle the more complicated searches where it can match lines flexibly (which can of course be done with C-M-s or C-s M-r followed by a simple regexp—but swiper is more convenient and has better visual feedback).

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.

5.2.1 Mark match

The following 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.

(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))
(define-key isearch-mode-map (kbd "C-SPC") 'prot/isearch-mark-and-exit)

5.2.2 Search marked

The following will populate the search prompt with the contents of the marked region. So 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.

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

5.2.3 Smarter backspace

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 delete failed match or last character. 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.

(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)))
(define-key isearch-mode-map (kbd "DEL") 'contrib/isearchp-remove-failed-part-or-last-char)

5.3 Visualise regular expression search/replace

This package highlights the terms affected by a search/replace operation. I find it helpful when constructing a complex regular expression. Note that vr/mc-mark is an interface for multiple-cursors, placing a cursor at the position of each matching term.

(use-package visual-regexp
  :ensure t
  :bind (("C-c r" . vr/replace)
         ("C-c q" . vr/query-replace)
         ("C-c m" . vr/mc-mark)))

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

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.

(eval-after-load 'org
  (progn
    (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)))

6.1.1 Improve heading presentation

This converts the multiple asterisks that denote the level of the heading into unicode characters. However, the true value of this lies in the next code block…

(use-package org-bullets
  :ensure t
  :hook org-mode . (lambda ()
                     (org-bullets-mode 1)))

Now that we have the ground set, we can use this awesome minor mode to render each heading level as an encircled number. Precision and simplicity of appearance.

;; Source: https://emacs.stackexchange.com/a/35374
(require 'nxml-mode)
(define-minor-mode org-my-bullets-level-mode
  "UTF-8 bullets for `org-mode' indicating declarative sort of headings."
  nil nil nil
  (let* ((keyword
          `((,org-outline-regexp-bol
             (0 (progn
                  ;; Set the bullet display character
                  (put-text-property (- (match-end 0) 2)
                                     (- (match-end 0) 1)
                                     'display
                                     (circled-number-unicode-char (- (match-end 0) (match-beginning 0) 1)))
                  (when (facep org-bullets-face-name)
                    ;; Set bullet face
                    (put-text-property (- (match-end 0) 2)
                                       (- (match-end 0) 1)
                                       'face
                                       org-bullets-face-name))
                  ;; "Hide" leading '*'s
                  (put-text-property (match-beginning 0)
                                     (- (match-end 0) 2)
                                     'face (list :foreground
                                                 (face-attribute
                                                  'default :background)))
                  ;; Attach the keymap to the entire segment
                  (put-text-property (match-beginning 0)
                                     (match-end 0)
                                     'keymap
                                     org-bullets-bullet-map)
                  nil))))))
    (if org-my-bullets-level-mode
        (progn (font-lock-add-keywords nil keyword)
               (font-lock-fontify-buffer))
      (save-excursion
        (goto-char (point-min))
        (font-lock-remove-keywords nil keyword)))))

(defun circled-number-unicode-char (num)
  "Return the corresponding circled digit unicode character for NUM.  Number range: -20 to 50."
  (let ((charcode (cond
                   ((and (>= num -20)
                         (<= num -11))
                    (+ (abs num) 9440))
                   ((and (>= num -10)
                         (<= num -1))
                    (+ (abs num) 10101))
                   ((= num 0)
                    9450)
                   ((and (>= num 1)
                         (<= num 20))
                    (+ 9311 num))
                   ((and (>= num 21)
                         (<= num 35))
                    (+ 12860 num))
                   ((and (>= num 36)
                         (<= num 50))
                    (+ 12941 num)))))
    (char-to-string (xmltok-unicode-to-char charcode))))
(add-hook 'org-mode-hook 'org-my-bullets-level-mode)

6.1.2 Org code blocks

I want .org files to use the native settings for styling code blocks.

(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.3 Org exporting

  1. Export to HTML

    Use this package to output to HTML. Type C-b to only produce the HTML body (useful when embedding to an existing template/website).

    (use-package htmlize
      :ensure t)
    
  2. TODO export to pdf

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 shell, bound to M-RET. If the *shell* buffer is available, this command will just switch to it.

(global-set-key (kbd "<M-return>") 'shell)

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.

6.2.1 Fix apt progress bar

The apt package manager on Debian displays a progress for some of its operations. This relies on escape sequences that are not properly interpreted by Emacs. This piece of code, provide by the developer of ivy (and many others), fixes the problem. The progress bar appears in the echo area. Source.

(use-package bash-completion
  :ensure t
  :config
  (bash-completion-setup))

;;;###autoload
(defun ora-shell-hook ())

(advice-add 'ansi-color-apply-on-region :before 'ora-ansi-color-apply-on-region)

(defun ora-ansi-color-apply-on-region (begin end)
  "Fix progress bars for e.g. apt(8).
Display progress in the mode line instead."
  (let ((end-marker (copy-marker end))
        mb)
    (save-excursion
      (goto-char (copy-marker begin))
      (while (re-search-forward "\0337" end-marker t)
        (setq mb (match-beginning 0))
        (when (re-search-forward "\0338" end-marker t)
          (let ((progress (buffer-substring-no-properties
                           (+ mb 2) (- (point) 2))))
            (delete-region mb (point))
            (ora-apt-progress-message progress)))))))

(defun ora-apt-progress-message (progress)
  ;; (setq mode-line-process
  ;;       (if (string-match "Progress: \\[ *\\([0-9]+\\)%\\]" progress)
  ;;           (list (concat ":%s " (match-string 1 progress) "%%%% "))
  ;;         '(":%s")))
  ;; (force-mode-line-update)
  (message
   (replace-regexp-in-string
    "%" "%%"
    (ansi-color-apply progress))))

(provide 'ora-shell)
(require 'ora-shell)

6.3 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.4 Feed reader (RSS/Atom)

Settings for the feed reader package. The source code on Gitlab for ambrevar/elfeed-play-with-mpv.

(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"))
    (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 "Max height resolution (0 for unlimited): " '("0" "480" "720") nil nil)))
    (setq quality-val (string-to-number quality-val))
    (message "Opening %s with height≤%s with mpv..." (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))))

(use-package elfeed
  :ensure t
  :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))
  :init (prot/feeds))

6.5 EWW (emacs web browser)

Open links in a new window, instead of the system's browser.

(setq browse-url-browser-function 'eww-browse-url)

Use monospaced fonts.

(setq shr-use-fonts nil)

Do not use HTML colours.

(setq shr-use-colors nil)

6.6 Guix (package manager)

This section is highly experimental.

An Emacs interface to Guix (the "Emacs of package managers"). Recall that I am using Debian 10. Just experimenting with Guix. The idea is that it might prove useful in those instances where I absolutely need a different package or version than those stored in Debian's archive or when I have to employ some of the more advanced features of Guix.

(use-package guix
  :ensure t)

7 Per-language settings

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

;; taken directly from the official manual
(add-hook 'text-mode-hook 'turn-on-auto-fill)
(setq adaptive-fill-mode t)

7.2 Markdown support

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

(use-package markdown-mode
  :ensure t)

7.3 TODO Spell check and language linting