Emacs configuration

Table of Contents

Basics

Lexical binding

By default, Emacs uses dynamic binding. In order to use closures, I enable lexical binding.

;; -*- lexical-binding: t -*-

Color theme

I prefer a dark color theme. It's much easier on the eyes, and I like working in the night often in dark rooms without any other lighting.

(load-theme 'manoj-dark)

Font size

Set the font size to something reasonable. The font size is in units of 1/10 pt. So, by setting :height to 120, I have set the font size to 12 pt. The default font face is what all other font faces base on.

Package manager and repositories

First things first, set up the package manager. Define the list of repos and initialize installed packages. More and more, I only use Emacs packages from my distro, GNU GuixSD. But, from time to time, I still need MELPA.

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

bind-keys

bind-keys is a useful macro from use-package1 that makes binding keys easy. I make extensive use of it in my Emacs configuration.

Autoloading

Autoloading lets you speed up Emacs startup and reduce memory usage by postponing loading files until the last moment when some command requiring that file is invoked.

Wherever necessary, I have wrapped up forms in the built-in with-eval-after-load macro so that they run only after some file is loaded.

No tabs

Indent using spaces instead of tabs. Since indent-tabs-mode is a buffer-local variable, set its default value. If we set its value using setq, it only applies to the current buffer.

(setq-default indent-tabs-mode nil)

Emacs as a window manager

I totally live in Emacs, so much so that I run exwm on Emacs to use it as my window manager. This is, at least at the moment, not the easiest or the most reliable approach to window management, but there are benefits. For example, with Emacs as my window manager, I can bind any key sequence on my keyboard in Emacs. Other window managers tend to block some key sequences. I can switch to and from external programs as though they were just Emacs buffers. I don't need an external program such as xbindkeys to handle keybindings to media keys, function keys, etc. I can instead use the excellent programmability of Emacs Lisp combined with clean integration to the exwm window manager. Not only does this centralize more of my system configuration in one place, it also lets me write more complex and cleanly integrated functions for my keybindings than would be possible with a bunch of shell scripts.

Display the battery and average system load

In the absence of a full fledged desktop environment, it is still nice to see the current battery and average system load (but not the time—see Clock-free life). Let's activate display-battery-mode and display-time.

(display-battery-mode)
(display-time)

bind-exwm-keys

In order to get keybindings to work across all buffers (both external program buffers managed by exwm and other Emacs buffers), I need to bind them using exwm-input-set-key. To make this convenient, I have a macro bind-exwm-keys similar in spirit to bind-keys described earlier.

bind-exwm-keys might have to bind key sequences to not just Emacs commands, but also to external programs. For this, the external program needs to be wrapped up in an Emacs command. This wrapping is handled by make-external-command. make-external-command returns a command that invokes the external program if it isn't already running, switches to the buffer of the external program if it is already running, and switches to the last used buffer2 if the external program is already running and is the current buffer. This makes it easy to start an external program, switch to it, and switch back from it all using the same keybinding.

(require 'exwm)

(defun switch-to-last-used-buffer ()
  (interactive)
  (switch-to-buffer (other-buffer (current-buffer) t)))

(defun make-external-command (command)
  (lambda ()
    (interactive)
    (let ((buffer-name (car (split-string command))))
      (cond
       ((equal buffer-name (buffer-name))
        (switch-to-last-used-buffer))
       ((get-buffer buffer-name)
        (switch-to-buffer (get-buffer buffer-name)))
       (t (start-process-shell-command buffer-name nil command))))))

(defmacro bind-exwm-keys (&rest keybindings)
  `(mapc (cl-function
          (lambda ((keybinding . command))
            (exwm-input-set-key (kbd keybinding)
                                (if (stringp command)
                                    (make-external-command command)
                                  command))))
         ',keybindings))

Binding function keys and media keys

I then bind a lot of external programs to the function and media keys.

(bind-exwm-keys ("<f2>" . "icecat")
                ("<f4>" . "qterminal")
                ("<f9>" . "gajim")
                ("<f10>" . "mpv")
                ("<f11>" . "xbacklight -dec 5")
                ("<f12>" . "xbacklight -inc 5")
                ("<XF86AudioMute>" . "amixer set Master toggle")
                ("<XF86AudioLowerVolume>" . "amixer set Master 5%-")
                ("<XF86AudioRaiseVolume>" . "amixer set Master 5%+")
                ("<XF86HomePage>" . "icecat")
                ("<XF86Sleep>" . "loginctl suspend")
                ("<S-XF86Sleep>" . "loginctl suspend; slock"))

Running external programs

I might still need to run external programs not covered above with the function and media keys. For these, I have a command run-external-program that prompts for the program to run and starts it using start-process-shell-command. The prompt provides all executables in PATH as completions. When building a list of completions, care is taken to filter out non-existent directories in PATH.

(let ((executables (seq-mapcat (lambda (bin)
                                 (when (file-exists-p bin)
                                   (directory-files bin nil (rx string-start (not ?.)))))
                               exec-path)))
  (defun run-external-program (command)
    (interactive (list (completing-read "$ " executables)))
    (start-process-shell-command command nil command)))

(bind-exwm-keys ("s-r" . run-external-program))

Rename exwm buffers

I want exwm buffers to be named according to the programs they are running.

(add-hook 'exwm-update-class-hook
          (lambda ()
            (exwm-workspace-rename-buffer
             (downcase exwm-class-name))))

Enable exwm

Finally, I enable exwm.

(exwm-enable)

Customization of built in Emacs features

Backup files

I don't want backup files littering my folders. I use version control systems a lot anyway.

(setq make-backup-files nil)

Menu and tool bars

Coming from a Vim background, I prefer to not have menu bars and tool bars clutter my limited screen real estate. I like all of the screen devoted to just text. Menu bars are very useful to discover new features, but I can always enable them when I need to, instead of keeping them permanently enabled.

(menu-bar-mode 0)
(tool-bar-mode 0)

Visual line mode

A lot of my work in emacs involves writing using org mode and latex. Word wrapping and the use of "visual lines" is absolutely important for this.

(global-visual-line-mode t)

Highlighting parentheses

Highlighting matching parentheses is really important when coding. I don't know why this is not enabled by default in emacs.

(show-paren-mode)

Windows

Moving between windows

The standard way of moving between windows using C-x o involves too many keystrokes. I prefer the more spatially intuitive s-{left,right,up,down} keybindings of the Windmove package with wrapping around the edge of the frame enabled. The Windmove default "Shift" modifier conflicts with org mode's default keybindings. So, I'm using the "Super" modifier instead.

(setq windmove-wrap-around t)
(windmove-default-keybindings 's)

Window manipulation keybindings

I bind several window and buffer manipulation commands to keybindings in the spirit of the Super key windmove keybindings. The keybindings I have set up are better than the default keybindings for these commands because they only need one keypress instead of two.

I hardly use the window enlargement/shrinking commands. Nevertheless, I keep them just in case I need them.

(bind-exwm-keys ("<s-tab>" . switch-to-last-used-buffer)
                ("s-1" . delete-other-windows)
                ("s-_" . split-window-below)
                ("s-|" . split-window-right)
                ("s--" . shrink-window-horizontally)
                ("<s-kp-subtract>" . shrink-window-horizontally)
                ("s-+" . enlarge-window-horizontally)
                ("<s-kp-add>" . enlarge-window-horizontally)
                ("s-*" . enlarge-window)
                ("<s-kp-multiply>" . enlarge-window)
                ("s-/" . shrink-window)
                ("<s-kp-divide>" . shrink-window))

Major modes

Lisps

For the lisps, after trying many structured editing packages, I have returned to the humble paredit and Emacs' own S-expression editing commands.

(mapc (lambda (hook)
        (add-hook hook 'paredit-mode))
      (list 'emacs-lisp-mode-hook 'lisp-interaction-mode-hook
            'lisp-mode-hook 'lisp-data-mode-hook 'scheme-mode-hook))

Ledger

I keep accounts with ledger. Here, I tell Emacs to automatically open .ledger files in ledger-mode.

(add-to-list 'auto-mode-alist '("\\.ledger" . ledger-mode))

Gnuplot mode

Gnuplot mode provides syntax highlighting, indentation and a few other convenient functions for editing gnuplot scripts. Unfortunately, by default, gnuplot mode only recognizes file extensions .gp and .gnuplot as gnuplot scripts, while Vim recognizes the .gpi extension. I don't want to have to rename all my gnuplot scripts. So, I just tell gnuplot mode to recognize the .gpi extension as well.

(add-to-list 'auto-mode-alist '("\\.gpi" . gnuplot-mode))

Octave mode

Octave mode is a major mode for editing Octave programs. I set Octave mode to recognize .m files as Octave source files. Without this, .m files are opened in Objective C mode, which I have no need for.

(map-put auto-mode-alist "\\.m$" 'octave-mode)

Completion

For completion, I use the minimalistic completion packages—vertico and corfu—that integrate tightly with Emacs' default completion system. Once upon a time, I was using Helm, but have since given it up to reduce complexity. With vertico and corfu, I only configure Emacs' default completion system, and it gets used everywhere.

(vertico-mode)
(global-corfu-mode)

I use the orderless completion style.

(setq completion-styles '(orderless))

I don't want Guile's object files cluttering my find-file completions. So, I added the .go extension to completion-ignored-extension.

(add-to-list 'completion-ignored-extensions ".go")

marginalia provides short annotations for completions. Not only is this pretty, it is quite useful as well. For example, when looking up elisp variables or functions with describe-variable and describe-function, it is nice to see their docstring summary before selecting them. Likewise, when browsing through commands with M-x, it's nice to a short description.

(marginalia-mode)

Consult

I use consult-buffer and consult-line everyday to jump around my buffers.

(global-set-key (kbd "s-b") 'consult-buffer)
(global-set-key (kbd "C-s") 'consult-line)

consult-ripgrep is incredibly useful to search around a large number of files. The live previews are invaluable. I alias it to rg for short.

(defalias 'rg 'consult-ripgrep)

Email

Encryption

Email encryption can sure do with some assistance from automation. Without automation, it is easy to forget to encrypt.

When encrypting outgoing emails, I also encrypt to myself so that I can read my own archives later. If I didn't, I would be left with opaque encrypted blobs in my archives.

(setq mml-secure-openpgp-encrypt-to-self t)

Configure Emacs to automatically encrypt when both the following are true:

  • I have a private key for the email address I am sending from
  • I have the public keys of all my correspondents
(defun encrypt-when-possible ()
  (when (and (secret-key-available-p
              (canonical-email-address (message-fetch-field "From")))
             (message-all-epg-keys-available-p))
    (mml-secure-message-sign-encrypt)))

(add-hook 'message-send-hook 'encrypt-when-possible)

And, here's the implementation of secret-key-available-p.

(defun canonical-email-address (address)
  "Return the canonical address part of email ADDRESS.

For example, (canonical-email-address \"Foo <foo@example.com>\")
=> \"foo@example.com\""
  (pcase (mail-extract-address-components address)
    (`(,_ ,canonical-address) canonical-address)))

(defun secret-key-available-p (email-address)
  "Check if secret key is available for EMAIL-ADDRESS.

Return non-nil if a valid secret key is available for
EMAIL-ADDRESS. EMAIL-ADDRESS is a canonical email address without
the name."
  (seq-some (lambda (key)
              (seq-some (lambda (user-id)
                          (and (string= (canonical-email-address
                                         (epg-user-id-string user-id))
                                        email-address)
                               (not (eq (epg-user-id-validity user-id)
                                        'revoked))))
                        (epg-key-user-id-list key)))
            (epg-list-keys (epg-make-context epa-protocol)
                           email-address t)))

Earlier, I used to automatically sign all outgoing emails regardless of whether my correspondents knew about PGP. This ended up confusing and maybe even annoying some people since they would mistake the signatures for attachments. So, I have stopped doing this. Nevertheless, here is the function if anyone needs it.

(defun sign-or-encrypt-when-possible ()
  (when (secret-key-available-p
         (canonical-email-address (message-fetch-field "From")))
    (if (message-all-epg-keys-available-p)
        (mml-secure-message-sign-encrypt)
      (mml-secure-message-sign))))

Guix

Guix store paths have long ugly hashes, and we don't usually want to see them. So, hide them with guix-prettify-mode.

(dolist (hook '(shell-mode-hook vterm-mode-hook process-menu-mode-hook))
  (add-hook hook 'guix-prettify-mode))

Dired

With its convenient emacsy interface and good Emacs integration, Dired is the text based file manager that finally convinced me to quit the GUI file managers I was using earlier. I spend so much time in Emacs (programming, mail, browsing, blogging, getting things done with org mode, etc.) that an Emacs based file manager is just the natural thing to have!

In Dired's file listings, I prefer seeing approximate human readable file sizes (such as 1K, 234M, 2G, etc.) instead of the number of bytes which are usually too large to be meaningfully interpreted by a human being. Hence I've added the -h switch to Dired's ls calls.

(with-eval-after-load 'dired
  (setq dired-listing-switches "-alh")
  (add-hook 'dired-mode-hook 'dired-async-mode))

Reading EPUB books using nov-el

I prefer paper and don't read EPUB books much. But, when I have to, I use nov.el. It's much easier than having a separate heavy GUI program for it. Here, I instruct Emacs to automatically open .epub files with nov-mode.

(map-put auto-mode-alist "\\.epub$" 'nov-mode)

Emacs Multimedia System

The Emacs Multimedia System (EMMS) lets you play audio/video from Emacs. The actual playing is delegated to external players. EMMS merely holds the controls, handles the media player user interface, etc. I use mpg321 for mp3, ogg123 for ogg.

(with-eval-after-load 'emms
  (require 'emms-source-file)
  (require 'emms-player-simple)

  (setq emms-player-list '(emms-player-mpg321 emms-player-ogg123)))

Useful minor modes

which-function-mode

(setq which-func-modes
      (list 'emacs-lisp-mode 'latex-mode 'scheme-mode))
(which-function-mode)

Clock-free life

I'm experimenting with a clock-free life to live outside the tyranny of the clock. I don't want to know the precise hour and minute of every moment of the day. It is much less stressful to live by imprecise natural time afforded by the movement of the Sun. Unfortunately, clocks are present in surprisingly many places and can trip you up and reveal the current time. So, we find and disable each one of them.

Often, there are no easy variables you can customize to disable the time. Programmers assume you want to always have the most precise time. Such is our culture. So, we need to resort to Emacs' advice system.

display-time-mode

I obviously don't want to have a clock on my modeline, but I'd still like to see the average system load. So, set the time format of display-time-mode to the empty string.

(setq display-time-format "")

org clocking

org clocking very helpfully prints out the current time on org-clock-in and org-clock-out. Silence it.

(defun silence (old-function &rest args)
  "Call OLD-FUNCTION with ARGS silencing its messages."
  (let ((message-log-max nil)
        (inhibit-message t))
    (apply old-function args)))

(advice-add 'org-clock-in :around 'silence)
(advice-add 'org-clock-out :around 'silence)

notmuch

The notmuch email client naturally prints out dates and times of emails. For relatively close times, the displayed times are relative. Relative times are not a big deal. But, if you happen to have emails of just the right age, notmuch will print out their absolute date/times and you might be able to deduce the current time pretty precisely. This is no good. Advise notmuch to show only the date, and never the time. We use the remove-time utility that we define later.

(defun remove-time-from-notmuch-tree-date (args)
  (pcase args
    (`(,field ,format-string ,msg)
     (list field
           format-string
           (map-insert msg
                       :date_relative
                       (remove-time (plist-get msg :date_relative)))))))

(advice-add 'notmuch-tree-format-field :filter-args 'remove-time-from-notmuch-tree-date)

(defun remove-time-from-notmuch-search-date (args)
  (pcase args
    (`(,field ,format-string ,result)
     (list field
           format-string
           (map-insert result
                       :date_relative
                       (remove-time (plist-get result :date_relative)))))))

(advice-add 'notmuch-search-insert-field :filter-args 'remove-time-from-notmuch-search-date)

(defun remove-time-from-notmuch-show-date (args)
  (pcase args
    (`(,headers ,date ,tags ,depth)
     (list headers
           (remove-time date)
           tags
           depth))))

(advice-add 'notmuch-show-insert-headerline :filter-args 'remove-time-from-notmuch-show-date)

marginalia file modification time annotations

marginalia annotates files with their modification times. If you happen to see a recently modified file, you just told yourself the time. Avoid this by overriding the absolute time formatting function to not show the hour and minute. Relative times are fine as they are.

(advice-add 'marginalia--time-absolute :filter-return 'remove-time)

Utilities

remove-time

remove-time is a function that can remove the time part of a wide range of datetime strings.

(defun remove-time (date)
  "Remove time from DATE string."
  (save-match-data
    (let ((date-end
           (string-match (rx " " (= 2 digit) ":" (= 2 digit) string-end)
                         date)))
      (if date-end
          (substring date 0 date-end)
        date))))

The remove-time function assumes something about the structure of the date string. And, as our use of it evolves, we need to be sure that it still works for all our date strings. So, we write a few test cases just to make sure.

(require 'ert)

(ert-deftest remove-notmuch-time-test ()
  (should (equal (remove-time "Mon. 11:47")
                 "Mon.")))

(ert-deftest remove-marginalia-time-test ()
  (should (equal (remove-time "May 24 07:16")
                 "May 24")))

Footnotes:

1

use-package also provides the use-package macro that helps you organize your various customizations in a performance oriented and tidy way. While I have outgrown it, I would still recommend it to beginners trying to grok Emacs configuaration best practices.

2

The switch-to-last-used-buffer function that I use for this, I credit to http://emacsredux.com/blog/2013/04/28/switch-to-previous-buffer/