A Semi-literate Emacs Configuration

Note: This configuration has been deprecated. I no longer maintain a literate configuration file, but will keep this page up to avoid link decay (20Apr20).
This doesn't mean there's anything wrong with it, just that I didn't want to go on curating it in the current form. Everything I write is still true, and can help newcomers.
Update: A newer version of my configuration can be found here (16Mar21).

1 Introductory comments

This document is my configuration for GNU Emacs, using Org Mode's Literate Programming facilities to automatically extract and evaluate all the relevant code snippets, ignoring the documenting comments.

To load this configuration, make sure you have the .org file locally on your system, and call it using org-babel-load-file. An example of how I initialise my configuration is given below.

This document, it's code and comments, are in the public domain.

The following configuration has been tested on a few different systems, with different Emacs versions, but nothing below 25, and practically assuming 26.1. Ideally don't just copy the everything but read and adopt the code snippets you think will help your configuration work better for your use case.

This configuration is more regularly updated on my website, and the source can be found in the git history.

A shortened, simplified, alternative version of this configuration can be found on my university homepage, in German.

Time of export: Sat Apr 18 18:18:15 2020.

1.1 Personal Emacs History

My first encounters with Emacs must have been somewhere around late 2013, when I was looking around to find a proper text editor. Overwhelmed by the options, I tried a lot of things out, such as Sublime Text or Geany, but all of them felt missing, faulted incomplete or simply wrong. It didn't take long for me to understand that the two main players turned out to be some of the oldest ones: vi (ie. vim) and Emacs (ie. GNU Emacs).

Knowing nothing, I trusted common opinion and went with Vi(m). And it was cool – a sin penance to say in an Emacs configuration. But while the foundations were good, it had problems with being extended, personalised, etc. Ultimately, I came to the conclusion that one shouldn't customise Vi(m), or at the very least keep it as minimal as possible (hence I use nvi and vis instead of Vim nowadays).

Yet this conclusion wasn't achieved by itself. It took Emacs to prove this, by demonstrating a properly extendable "environment" to teach me…

I remember that there was a bit of a gap between installing and using Emacs. For a while I even had issues remembering and recognising the name! A particularly vivid memory is me looking through the Gnome (or Unity?) configuration program, and encountering the "input method" option, among which "Emacs" was an option – I assumed it was related to Apple's computer brand… But when I opened Emacs for the first few times it seemed a bit overwhelming, (unnecessarily) complex and unhandy: everything was different. From names over the workflow (which I didn't even understand) to the keybindings. All I used to do, was open a file (C-x C-f), maybe move up and down (M-v and C-v) change the theme (using the menu bar) and then close Emacs (C-x C-c). That was pretty much all I knew.

But somewhere around mid-2014 (if not earlier) for some reason I can't quite remember I was drawn towards Emacs. Possibilities could be

  1. Me having have met "real" Emacs users, and drawing my attention to the Editor, making me realise that it's not just a notepad with inconvenient keybindings.
  2. Me having have engaged with Lisp, while to be fair not knowing all it's potential understanding it's beauty.

So I slowly started trying to configure Emacs. Retroactively it seems to me that the resources were far harder to find. Having no idea what to do (and lacking the patience to sit through the Emacs Manual), I copied a few things from some blogs, a few things from some dotfile, some stuff from YouTube descriptions and others from Stackoverflow questions – to put it simply, it was a mess, and stuff broke quite frequently. I kept on reading that Org mode was great, but I didn't ever get why (I did eventually). But for some reason I didn't stop using Emacs, but neither did I go too deep.

After a few years of bare survival, ups and downs, I started to find out that people were writing literate configurations.

Then I read an article, that said the following:

They all used Emacs, of course. Hell, Eric Benson was one of the authors of XEmacs1. All of the greatest engineers in the world use Emacs. The world-changer types. Not the great gal in the cube next to you. Not Fred, the amazing guy down the hall. I'm talking about the greatest software developers of our profession, the ones who changed the face of the industry. The James Goslings, the Donald Knuths, the Paul Grahams, the Jamie Zawinskis, the Eric Bensons. Real engineers use Emacs. […]

Emacs is the 100-year editor.

Yes, it is a bit cringy – but I liked the idea. So it didn't take long and I decided to rewrite my ~100-150 line configuration. My enthusiasm began to blossom somewhere around that time. And so, since February 2018 I have been maintaining this file.

PS. Here's an interesting list of famous Emacs users.

1.2 Why use org/babel?

While it might appear to be a inconvenience at first, extracting ones configuration into a format like this one has multiple advantages, of which the main ones are:

  • My actual init.el is very short. All I do, disable filename handlers, increase the garbage collection threshold, and (if necessary) call Org/Babel. If the extracted file newer than the this org file, I just load that file:

    ;; (package-initialize) - prevent package.el from adding stuff
    (require 'cl-lib)
    (require 'subr-x)
    (let ((file-name-handler-alist nil)
          (frame-inhibit-implied-resize t)
          (package-enable-at-startup nil)
          (message-log-max 16384)
          (gc-cons-threshold most-positive-fixnum)
          (gc-cons-percentage 0.6)
          (auto-window-vscroll nil))
      (org-babel-load-file (expand-file-name "conf.org" user-emacs-directory))
      (garbage-collect))
    
  • Writing text with code (as opposed to code with comments) is much more informative, and makes me justify whatever I add. This, one the one hand contributes to simplicity, but also helps people who are configuring their config get a better feeling for what to do and why.
  • After a while, every novice should entirely rewrite their configuration. In the beginning, one might just copy stuff from Manuals, Wikis and Blogs – and keep what sicks, but this results in code that's hard to maintain and keep an overview. I pushed myself to rewrite my configuration I started to write around 2014-15 and I enjoy a far better experience that had before. My potential had increased and I have used the opportunity to learn more about Emacs itself.

1.3 Inspirations for writing this configuration

Emacs is a tool you can use for years and always learn something new, which make you feel stupid for not having known. The fun part when writing a configuration like this one is that one actively learns these things, instead of accidentally pressing the wrong keybinding, and then going back to C-h l to find out what just happened.

So for my own assistance, and maybe also for other lost souls, reading this in the lookout for tricks and wizardry (note: you probably won't find any of this in my config), I list here other very interesting configurations:

All of these configurations are worth reading and re-reading from time to time, since one can always find out something new to adopt into his/her own configuration.

A more comprehensive list can be found here.

1.4 Short note on my directory structure

While this configuration aims for a certain degree of portability, this isn't universally the case, since it's my personal setup, not a cool prelude. Portability is maintained between the devices I use Emacs on, by assuming a certain standardized directory structure, as follows:

Directory Use
~/ home
~/code/ programming and sysadmin related files
~/code/{c,haskell,go,...} directories devoted to specific programming languages
~/code/etc/... various other projects (including emacs source)
~/dl/ downloads gathering directory, preferably empty
~/doc/ texts, presentations and notes
~/doc/org/ most org-mode related files
~/media/ general directory for digial media
~/media/{img,vid,music,...} specific media directories
~/etc/ various other directories
~/etc/bin/ user binaries
~/etc/{mail,news,pub} gnus related directories

When porting or copying from this configuration, these notes might help.

1.5 Software I have installed to aid Emacs

Emacs makes great use of external software, that's also installed on the same system. The following list helps me remember what I have to install on a new system, and for what purpose:

msmtp, mpop, mairix
Mail
gpg
authinfo.gpg de-/encyrption
curl
Feed Reader
git
Version Control (eg. for this file)
aspell
Spell Checking
rg
Project Managment and Source Discovery
pandoc
Markdown

Compilers and interpreters for specific programming enjoyments aren't listed here, since I don't necessarily have all of them installed, even if they are set up to work in Emacs.

2 Configurations

2.1 Base-level Setup

Before anything else, I would like to ensure that Emacs (ie. the primitive keybindings, the window, the frames, …) itself works the way I expect it to.

2.1.1 Lexical Scoping

All code written in this file, shall be "Lexically Scoped".

;;; -*- lexical-binding: t; eval: (view-mode 1) -*-

2.1.2 Initially deactivated Modes

Since I usually don't need my mouse to use Emacs, I turn off all GUI related tools, like scroll-, toolbars, etc. This is done early on to avoid redrawing during startup.

(scroll-bar-mode -1)
(menu-bar-mode -1)
(tool-bar-mode -1)
(blink-cursor-mode -1)
(tooltip-mode -1)

As an additional hack, I sometimes place the following in my .Xresources file, which further improves the startup speed slightly:

emacs.toolBar: 0
emacs.menuBar: 0
emacs.verticalScrollBars: off

2.1.3 Startup actions

In accordance to a minimalist and fast startup, I tell Emacs to not open the standard startup buffer (with a timestamp of when I opened Emacs), since I never use it anyways.

(setq inhibit-startup-screen t
      inhibit-startup-buffer-menu t
      inhibit-startup-message t
      inhibit-startup-hooks t)

2.1.4 Buffer Boundaries

Sometimes it's not obvious if you're at the top or bottom of a buffer. So I enable indicators that can tell me precisely that.

(setq-default indicate-buffer-boundaries
              '((top . right)
                (bottom . right)
                (t . nil)))

2.1.5 Frame resizing

When using graphical Emacs, this option enables more flexible resizing of the entire frame.

(setq frame-resize-pixelwise t)

2.1.6 Window resizing

This option make Emacs split windows in a more sane and visually pleasing manner, ie. proportionally.

(setq window-combination-resize t)

2.1.7 Extra-Emacs Clipboard

Having the ability to interact with the system clipboard is very welcome, especially when copying code from a (now eww) web browser.

(setq select-enable-clipboard t)

Also don't forget what it is the clipboard before text is killed, by adding it to the kill-ring.

(setq save-interprogram-paste-before-kill t)

Additionally, don't follow the mouse, but insert at the current point.

(setq mouse-yank-at-point t)

2.1.8 Disable graphical dialogues

Just don't create graphical pop-ups (especially when Emacs starts up).

(setq use-dialog-box nil)

2.1.9 Minibuffer height

I like executing commands with M-!, but I don't like new buffers and windows being created. To remedy this, I lessen Emacs general sensitivity as to what is "too much" for the Minibuffer from 25% (as of writing) to 40% of the window height.

(setq max-mini-window-height 0.40)

2.2 Core Setup

After the low level appearance and behaviour has been configured, I would like to focus on built-in but less "critical" features.

2.2.1 User information

It's helpful to tell Emacs what name and email address you like to use, so that various subsystems (most importantly Mail) know what to use.

A little peculiarity I have to do is to set my email in a bit of a roundabout way, so that web spiders don't find it while crawling my published configuration. Evaluating the format expression should though generate a regular email address.

(setq user-full-name "Philip K."
      user-mail-address (rot13-string "cuvyvc@jnecznvy.arg"))

2.2.2 Bell

Emacs will call ring-bell-function when errors occur or on various manual interrupts. I don't want it to make noise, I set the function to just do nothing.

(setq ring-bell-function #'ignore)

2.2.3 Encoding

There is almost nothing I do that doesn't use UT-8, so I tell Emacs to use UTF everywhere. If that is the case, I can manually change it later on (set-file-name-coding-system).

(setq locale-coding-system 'utf-8-unix)
(set-selection-coding-system 'utf-8-unix)
(prefer-coding-system 'utf-8-unix)

2.2.4 Pager

Prevent interactive processes from using a "regular" pager such as less, view or more, and instead just let Emacs do the job.

(setenv "PAGER" "cat")

2.2.5 Time Locale

Force Emacs (especially org-mode) to use English timestamps, instead of some other system-specific formatting.

(setq system-time-locale "C")

2.2.6 Backups

The default Emacs backup system is pretty annoying, so these are a some helpful tips I've gathered from around the internet, with a few modifications based on experience (eg. having have been saved by the backup system, more than just a few times).

(setq-default backup-directory-alist
              `(("" . ,(expand-file-name "backup/" user-emacs-directory)))
              auto-save-default nil
              backup-by-copying t
              delete-old-versions t)

Note: This is also probably one of the oldest parts on my configuration, staying mostly unchanged since mid-late 2014, when copied the code from this StackOverflow answer.

2.2.7 Disable lockfiles

Lockfiles appear when a file is opened and confuses some tools. I trust myself to not come into a situation where lockfiles are needed, and have therefore disabled them.

(setq create-lockfiles nil)

2.2.8 "Large Files"

Don't warn me about larger-but-not-actually-that-large files.

(setq large-file-warning-threshold 40000000)

2.2.9 Prefer newer Bytecode

Quite simple trick to avoid a few bugs that might arise from older bytecode being used, even though the elisp file has changed.

(setq load-prefer-newer t)

2.2.10 Disabled functions

By default Emacs disables some commands that have to be manually enabled by the user, when the keybinding is used or the function is called. This snippet (source) disables this by default, thus enabling all commands.

(setq disabled-command-function nil)

2.2.11 Help-buffers

Usually when using Emacs' online-help system, it doesn't move the active point to the new buffer, making me type C-x o every time (nearly as an instinct). Telling Emacs to do otherwise, should make life a bit easier.

(setq help-window-select nil)

2.2.12 Local or Private Configurations

I previously attempted to set custom-file to /dev/null/, but sadly I kept getting the message that the find could not be found. Therefore, to not clutter init.el, I dump all the configurations in ~/.emacs.d/custom.el.

(let ((custom-el (expand-file-name "custom.el" user-emacs-directory)))
  (setq custom-file custom-el)
  (when (file-exists-p custom-el)
    (with-eval-after-load 'use-package
      (load custom-file))))

2.2.13 Exiting Emacs

While it's not quite "appearance"-related, this will prevent Emacs from being accidentally closed when I type C-x C-c instead of C-c C-x.

(setq confirm-kill-emacs #'yes-or-no-p)

2.3 Miscellaneous Setup

What is listed here is neither really important or a real package.

2.3.1 Emacs C source

In case I have the Emacs C-source locally installed, I inform my current session about it, in case I want to inspect some low level code.

(let ((c-source (expand-file-name "~/code/etc/emacs/src")))
  (when (file-directory-p c-source)
    (setq find-function-C-source-directory c-source)))

2.3.2 Aliases

Aliases create a new function, or overwrite an existing one, with the same content as another.

I use this to replace the yes-or-no-p function, that is used throughout Emacs for simple "yes or no" queries ("Create a new file?", "Close this connection?", …) with a function that just requires a single key press.

(defalias 'yes-or-no-p 'y-or-n-p)

2.3.3 Setting the right mode

When creating new buffers, use auto-mode-alist to automatically set the major mode. Snippet from Stackoverflow.

(setq-default major-mode (lambda ()
                           (unless buffer-file-name
                             (let ((buffer-file-name (buffer-name)))
                               (set-auto-mode)))))

2.3.4 Cycle Spaces

I use cycle-spacing a lot, and most of these are to remove empty newlines, which can be done with a negative prefix argument. Hence, I wrap the function with a default negative argument, to save me a few keystrokes.

(advice-add 'cycle-spacing :around
            (lambda (old arg &rest _)
              (funcall old (if (numberp arg)
                               (- arg) arg))))

2.3.5 Registers

To quickly access certain files I tend to frequently use, I use Emacs's file registers.

(setq register-alist
      (mapcar (lambda (ent)
                (cons (car ent)
                      (cons 'file (cadr ent))))
              `((?h "~")
                (?d "~/dl/")
                (?b "~/etc/bin/")
                (?\; "~/code")
                (?w "~/code/web/www/")
                (?W "~/code/web/")
                (?c ,(expand-file-name "conf.org" user-emacs-directory))
                (?\C-c ,user-emacs-directory)
                (?C ,custom-file)
                (?o "~/doc/org/")
                (?n "~/doc/org/notes.org")
                (?p "~/doc/org/pers.org")
                (?j "~/doc/org/uni.org")
                (?r "~/doc/read/")
                (?u "~/doc/uni/")
                (?i "~/media/img/")
                (?m "~/etc/mail/")
                (?t "/tmp/")
                (?U "/scp:uni:")
                (?I "/scp:ibis:"))))

2.3.6 Helper Functions and Macros

2.3.6.1 Working with PATH

Adding a directory to the PATH environmental variable can be cumbersome at times, since it requires using getenv multiple times and it isn't pretty to check if a directory is always included every time. This is what add-to-PATH seeks to fix.

(defun add-to-PATH (dir)
  "Add `dir' to environmental variable PATH as well as
`exec-path'."
  (let ((path (split-string (getenv "PATH") ":")))
    (unless (member dir path)
      (setq path (append path (list dir))))
    (add-to-list 'exec-path dir)
    (setf (getenv "PATH") (string-join path ":"))))

And as a complement to add-to-PATH, remove-from-PATH does what one would expect it to do.

(defun remove-from-PATH (dir)
  "Remove dir from environmental variable PATH and `exec-path'."
  (let* ((path (split-string (getenv "PATH") ":"))
         (path-new (remove dir path)))
    (delete dir exec-path)
    (setf (getenv "PATH") (string-join path-new ":"))))

And I use the opportunity to add my local binary directory to Emacs' know paths.

(add-to-PATH (expand-file-name "~/etc/bin"))
2.3.6.2 Set value locally
(defmacro setl (sym val)
  "Produce a lambda expression that locally sets a value"
  `(function (lambda () (setq-local ,sym ,val))))

Some modes will have to have buffer-local variables, loaded by hooks. To make it a bit easier to work with these, this macro produces a lambda expression that just set local values to a constant.

2.3.6.3 Quickly get Point after Operation
(defmacro point-after (func &rest args)
  "Execute the passed function `func' with arguments `args' and
return the value of the point afterwards."
  `(save-excursion
     (ignore-errors (,func ,@args))
     (point)))

Sometimes I just want to know where the point will be after some operation (forward-word, backward-sexp, …), and this macro makes it easy to get this information.

2.3.6.4 Lookup Password

The auth-source library lets users store passwords in (possibly encrypted) external files. Sadly it doesn't seem to have a simple programming interface, to access these passwords.

This function, lookup-password, copied from John Wiegley's configuration, makes it easy to request them from within Emacs.

(require 'auth-source)
(require 'auth-source-pass)

(defun lookup-password (host &optional user port)
  (if-let* ((auth (auth-source-search :host host :user user :port port))
            (secretf (plist-get (car auth) :secret)))
      (funcall secretf)
    (error "No secret found")))

2.4 Packages and Modules

One of the distinctive features of Emacs is next to it's ability to be extended, the culture that surrounding this fact. The modern way of distributing these extensions is using packages, stored in repositories.

Emacs comes with a repository, named "ELPA", built in. Sadly it lacks a few packages I would like to use, so I decide to also use "MELPA", the unofficial (but defacto standard) user-repository, not maintained by the GNU project.

But since the default repository decides to build each package from a Git/Mercurial repository, and not only that but it uses whatever the last commit is! Since I find this to be too experimental, I decide to use MELPA's more reliable sibling, "MELPA Stable". This uses whatever the last tagged commit is, at the price of not having the newest version in case the maintainer forgets to tag.

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

The use-package macro by John Wiegley helps me configure the various packages I use in a cleaner way, defining dependencies, and load them more quickly by deferring loading whenever possible.

Use-package will also install packages when the feature cannot be found locally. So while calc is bundled with Emacs itself, paredit has to be installed from a repository, and then it can be configured. Thanks to this automatic installation, and various conditions (eg. only install go-mode if the go binary could be found) I can easily copy my configuration from on system to another.

Since I configure a lot of "local" (ie. non ELPA/MELPA) packages via use-package too (mainly for the :custom syntax) I do not ensure by default, but manually say when something should be downloaded from a repository.

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package t))
(require 'use-package)
(setq use-package-always-defer t)

2.4.1 Emacs Internal

2.4.1.1 Cross-session Configuration

When Emacs quits, either because of an update or a system reboot, I would usually loose everything I have entered into various minibuffers. Savehist allows me to save various variables across sessions, so that I can just continue where I left off.

Variables not saved by default, but that I still consider useful are the contents of the kill ring (ie. what I have killed ("cut" in day to day parlance)), the M-x compile history, and everything I have searched.

Variables that just accumulate too much storage without much use (yes-or-no-p-history) are disregarded.

(use-package savehist
  :demand t
  :custom ((history-delete-duplicates t)
           (savehist-save-minibuffer-history t)
           (savehist-additional-variables '(kill-ring
                                            compile-command
                                            search-ring))
           (savehist-ignored-variables '(yes-or-no-p-history)))
  :config
  (savehist-mode t))

To not loose all the buffers between sessions, desktop-save-mode keeps track of buffers before Emacs exists, but doesn't keep track of the frame layout. Furthermore, no buffers are "lazily" restored, but instead all at once, since otherwise this leads to an annoying behavior where buffers are being restored and Emacs, but because I stopped typing for a second.

(use-package desktop
  :demand t
  :custom ((desktop-restore-eager 8)
           (desktop-globals-to-save nil)
           (desktop-files-not-to-save
            (rx (or (seq bol "/" (zero-or-more (not (any "/" ":"))) ":")
                    (seq "(ftp)" eol)
                    (seq "*" (one-or-more not-newline) "*")))))
  :config
  (desktop-save-mode t))

When re-entering a file, return to that place where I was when I left it the last time.

(use-package saveplace
  :demand t
  :config
  (save-place-mode t))
2.4.1.2 Remembering Recent files

When killing a buffer, said buffer will be default be forgotten and reopening it would either require me to search the mini buffer or type in the full path again.

The global minor mode recentf-mode is used by various subsystems to remember just closed files, and with the help of these provide more intelligent suggestions.

(use-package recentf
  :demand t
  :config
  (recentf-mode t))
2.4.1.3 Automatically updating changed

Emacs supports "auto-reverting" files. This means that if a file/directory changes on the file-system, Emacs will update this buffer without the user having to do anything (as long as it's obviously uncontroversial). This is nice when a program is writing something to a file, you have already opened in Emacs.

My only issues is that by default auto-revert-mode will generate messages I don't really need in the minibuffer.

(use-package autorevert
  :custom ((auto-revert-verbose nil)))
2.4.1.4 Uniquify

In case two buffers have two different files open, that share the same name (eg. Makefile or .gitignore) addressing one or the other might turn out to be a problem. The default solution is to write the directory in angle brackets behind the buffer name, but I don't think that looks too nice. Instead I let Emacs write the different parts of it's path in the Modeline.

(use-package uniquify
  :custom((uniquify-buffer-name-style 'forward)
          (uniquify-after-kill-buffer-p t)
          (uniquify-ignore-buffers-re "^\\*")))
2.4.1.5 Automatic Timestamps

In non-versioned files I like to keep track of when I last changed something, or in other words when I last saved a file. The time-stamp function will look for files that contain a patter like

Time-stamp: <>
Time-stamp: " "

in the first few lines of a file, and insert a timestamp + username when found.

(use-package time-stamp
  :hook (before-save))
2.4.1.6 TODO Buffer overview
(use-package bs
  :bind ("C-x C-b" . bs-show))
2.4.1.7 Writable Grep

Analogously to Wdired, Wgrep allows the user to interactively edit the results of a grep buffer, and have them written back to their original files.

(use-package wgrep
  :ensure t
  :custom ((wgrep-enable-key  (kbd "C-x C-q"))))

2.4.2 Look and Feel

2.4.2.1 Highlighting Parentheses

Any structured programming language, but also sufficiently bad text has plenty of parentheses or similar syntactic constructs, that immediately recognising what matches what isn't that easy.

That's where Emacs' global minor mode show-paren-mode comes in. When activated it will highlight various matching parentheses when the point is placed on it's other pair.

The only default behaviour I don't like is that show-paren-mode will wait a bit before highlighting, probably due to historical reasons. I set this delay to zero, and have not been experiencing any issues, even with somewhat older hardware.

(use-package paren
  :demand t
  :custom ((show-paren-delay 0)
           (show-paren-mode t)))
2.4.2.2 Auto-completion

I use Ivy to extend the default find-file, switch-to-buffer, etc. commands. Compared to it's alternatives, Ivy is simpler (and faster) that Helm but more powerful than Ido.

(use-package ivy
  :demand t
  :ensure t
  :diminish
  :custom ((ivy-wrap t)
           (ivy-height 8)
           (ivy-display-style 'fancy)
           (ivy-use-virtual-buffers t)
           (ivy-case-fold-search-default t)
           (ivy-re-builders-alist '((t . ivy--regex-ignore-order)))
           (enable-recursive-minibuffers t)
           (ivy-mode t))
  :bind (:map ivy-minibuffer-map
              ("<RET>" . ivy-alt-done)))

Counsel extends this to further integrate Ivy features into default commands, such as M-x, C-x b or C-x C-f.

(use-package counsel
  :after ivy
  :demand t
  :ensure t
  :diminish
  :custom ((counsel-mode t))
  :config
  (defun zge/counsel-start-file-jump ()
    "Switch to counsel-file-jump, preserving current input."
    (interactive)
    (ivy-quit-and-run (counsel-file-jump nil ivy--directory)))
  (dolist (cmd '(yank-pop describe-bindings))
    (define-key counsel-mode-map `[remap ,cmd] nil))
  :bind (("C-x C-r" . counsel-rg)
         ("C-x C-/" . counsel-org-goto-all)
         ("C-x C-]" . counsel-file-jump)
         :map counsel-find-file-map
         ("C-]" . zge/counsel-start-file-jump)))
2.4.2.3 Modeline Hider

This package is used by many others via the :diminish keyword in use-package. It ensures that certain minor modes don't appear in the mode-line.

(use-package diminish
  :ensure t
  :config
  (diminish 'hs-minor-mode)
  (diminish 'eldoc-mode)
  (diminish 'flyspell-mode))

2.4.3 System Interaction

2.4.3.1 Open a file at point

If a file contains another file name (or more generally some reference to another file somewhere), I can use find-file-at-point or ffap to open it quickly.

If I have installed doc-rfc on my system, all RFCs can be found locally. Therefore, ffap wouldn't have to use a broken FTP mirror to parse RFC2551, but can open it directly.

(use-package ffap
  :custom ((ffap-rfc-directories
            (and (file-directory-p "/usr/share/doc/RFC/links/")
                 '("/usr/share/doc/RFC/links/"))))
  :bind (("M-#" . find-file-at-point)))
2.4.3.2 More advanced shell-command

"Bang" is a shell-command substitute that melds shell-command and shell-command-on-region by looking at the first character of the input. A < or > redirects the region in or out of the command, a | substitutes it and ! can be used to address previous commands.

I stole most of the implementation from Leah Neukirchen's .emacs (where it's just commented with "sam(1)-like M-!"), and extended it with to handle !-Prefixed commands like a regular shell.

(use-package bang
  :after dired
  :ensure t
  :bind (("M-!" . bang)
         :map dired-mode-map
         ("M-!" . bang)))
2.4.3.3 Async Shell comands

Emacs can execute shell commands with M-!, and asynchronous shell commands with M-& (does the same as M-! with a &). One annoyance with async commands is that by default a new buffer is opened and displayed. To make it's usage a bit more convenient, I make sure that buffers are automatically created, but don't change the current frame layout.

(use-package simple
  :custom ((async-shell-command-display-buffer nil)
           (async-shell-command-buffer #'new-buffer)))
2.4.3.4 Spell Checking

When just writing prose, or just comments, flyspell-mode (and flyspell-prog-mode) prove themselves to be valuable utilities, albeit a bit slow and cumbersome from time to time…

(use-package flyspell
  :custom ((ispell-program-name (executable-find "aspell"))
           (ispell-extra-args '("--sug-mode=bad-spellers"
                                "--keyboard=standard"
                                "--guess"))
           (ispell-local-dictionary-alist
            '(("german-new8" "[[:alpha:]]" "[^[:alpha:]]"
               "[']" t ("-d" "de_DE") nil utf-8)
              ("british" "[[:alpha:]]" "[^[:alpha:]]"
               "[']" t ("-d" "en_GB") nil utf-8)))
           (flyspell-issue-welcome-flag nil)
           (flyspell-issue-message-flag nil)
           (flyspell-use-meta-tab nil)))
2.4.3.5 Encryption

To use GPG in Emacs, I configure EPA. The only thing I need it to do is to query my via the minibuffer for my password (be it for my public key or a symmetrically encrypted file) and then proceed as usual.

This requires some work on GPG's side, enabled by adding this to my ~/.gnupg/gpg-agent.conf file…

allow-emacs-pinentry
allow-loopback-pinentry

… and setting this option in Emacs:

(use-package epa
  :custom ((epa-pinentry-mode 'loopback)))
2.4.3.6 OS Management and Tools
2.4.3.6.1 Directory Managment

Not much to say: For the most part, a under-customized dired configuration.

(use-package dired
  :demand t
  :custom ((dired-dwim-target t)
           (dired-no-confirm
            '(byte-compile
              chgrp chmod chown copy
              hardlink load move
              shell touch symlink))
           (dired-recursive-copies 'always)
           (dired-recursive-deletes 'top)
           (dired-ls-F-marks-symlinks t)
           (dired-ls-sorting-switches "v")
           (dired-listing-switches (if (eq system-type 'berkeley-unix)
                                       "-FAhl"
                                     "-NAhl --group-directories-first")))
  :config
  (require 'dired-x)
  (add-to-list 'dired-guess-shell-alist-user
               `(,(rx "." (or "epub" "pdf") eos) "mupdf"))
  (add-to-list 'dired-guess-shell-alist-user
               `(,(rx "." (or "png" "jpg" "jpeg") eos) "feh"))
  (add-to-list 'dired-guess-shell-alist-user
               `(,(rx "." (or "mp4" "webm" "m4a") eos) "mpv --really-quiet"))
  (add-to-list 'dired-guess-shell-alist-user
               `(,(rx "." "wav" eos) "aplay"))
  :hook ((dired-mode . auto-revert-mode)))

Wdired by default only allows one to edit file names. Setting these variables, extends the abilities of this very interesting minor mode.

(use-package wdired
  :custom ((wdired-allow-to-change-permissions t)
           (wdired-allow-to-redirect-links t)))
2.4.3.6.2 Shells and Terminal Emulation

Emacs has (at least) three built-in terminals or shells, or if one is strict two shells (eshell, shell) and one terminal emulator (term). I manly use Eshell, so I don't need a real configuration for the other two.

As I have mentioned, I generally prefer Eshell. It's the most unusual of the bunch, but precisely for that reason the most interesting too. Instead of communicating with a background process, it just does "shell stuff" (creating processes, starting programmes), when enter is pressed, and otherwise it's just a text buffer.

All my changes are regarding default appearance. I ask Emacs to not generate a banner and to shorten the prompt to a single dollar sign.

(use-package eshell
  :custom ((eshell-banner-message "")
           (eshell-prompt-function (lambda (&rest _) "$ "))
           (eshell-prompt-regexp "^$ ")))
2.4.3.6.3 Bash Completions

Bash has usually very good command completion facilities, which aren't accessible by default from Emacs (except by running M-x term). This package integrates them into regular commands such as shell-command and shell.

(use-package bash-completion
  :if (file-exists-p "/etc/bash_completion")
  :ensure t
  :init
  (bash-completion-setup))

2.4.4 Programming

All programming modes have (or should have) a mode in common: prog-mode. Here's their shared configuration:

(use-package prog-mode
  :bind (:map prog-mode-map
              ("C-c w" . whitespace-mode))
  :hook ((prog-mode . electric-indent-local-mode)
         (prog-mode . flyspell-prog-mode)))
2.4.4.1 General
2.4.4.1.1 Minibuffer Documentation

Eldoc is quite nice when programming, it shows me information about the symbol the point is currently on. All I want it for it to not wait for that long before it does that.

(use-package eldoc
  :custom ((eldoc-idle-delay 0.1)))
2.4.4.1.2 Colorful Parentheses

Especially when programming Lisp, color-matching parentheses and brackets can help readability. This feature is offered by rainbow-delemiters, that I enable in all programming modes.

(use-package rainbow-delimiters
  :after prog-mode
  :ensure t
  :hook ((prog-mode . rainbow-delimiters-mode)
         (LaTeX-mode . rainbow-delimiters-mode)))
2.4.4.1.3 Multiple Cursors

It doesn't take long to adjust to multiple-cursors-mode, and it is a feature one turns out to use surprisingly often. While it's not as native or quick, as in other editors, it's for the most part entirely sufficient for my causes.

(use-package multiple-cursors
  :ensure t
  :bind (("C-M-;" . mc/mark-all-like-this-dwim)))
2.4.4.1.4 Structural Editing

Paredit might not be easy to get used to, but after a while (and a few failed attempts) it becomes natural and one expects it.

(use-package paredit
  :after eldoc
  :ensure t
  :diminish
  :config
  (eldoc-add-command 'paredit-backward-delete)
  (eldoc-add-command 'paredit-close-round)
  :hook ((scheme-mode . paredit-mode)
         (lisp-mode . paredit-mode)
         (emacs-lisp-mode . paredit-mode)
         (slime-repl-mode . paredit-mode))
  :bind (:map paredit-mode-map
              ("M-\"" . nil)))
2.4.4.1.5 Goto Source

Source code navigation often requires a rather complex setup, either using external tools, file formats or even daemons. But more often than not, a "primitive" heuristic, such as is implemented by the dumb-jump module suffices for simple navigation. I trust this heuristic so much, that I have even decided to turn the "aggressive" mode on, which jumps even if it's not quite certain.

I have decided not to use dumb-jump-mode, that mainly just activates dumb-jump-mode-map, and instead use my own keybindings which I directly bind to prog-mode-map.

(use-package dumb-jump
  :after prog-mode
  :ensure t
  :custom (dumb-jump-aggressive t)
  :bind (:map prog-mode-map
              ("M-[" . dumb-jump-go)
              ("M-]" . dumb-jump-back)))
2.4.4.1.6 Error Checking

Emacs supports live error/warning checking, by underlining suspicious segments. The default package for this is flymake, which I have yet to properly configure (and that has allegedly improve much recently), so I use the more popular flycheck instead.

Not much has to be configured, the main annoyance I have is that it's elisp checker insists that every elisp buffer is a proper module, as in that it requires introductory comments, metadata, etc. This doesn't make much sense in buffers such as the scratch buffer or in opened org-mode source blocks. For that reason I disable the "checkdoc" checker.

Flycheck is to be enabled for all programming modes, and TeX buffers.

(use-package flycheck
  :ensure t
  :custom (flycheck-disabled-checkers '(emacs-lisp-checkdoc))
  :hook ((TeX-mode prog-mode) . flycheck-mode))
2.4.4.1.7 Snippets

While I have previously had problems with yasnippets, mainly due to snippets expanding when I don't want them to, recent experience has made me long for a snippet system again. The current system, could work: on <tab> snippets are only expanded if the last command was self-insert-command, i.e. user input. Otherwise, code will be aligned.

(use-package yasnippet
  :ensure t
  :diminish yas-minor-mode
  :functions (yas-expand)
  :custom ((yas-prompt-functions '(yas-completing-prompt))
           (yas-wrap-around-region t))
  :bind (:map yas-minor-mode-map
              ("<tab>" . nil)
              ("TAB" . nil)
              ("<backtab>" . yas-expand))
  :hook (prog-mode . yas-minor-mode))

Furthermore, make sure a few extra major modes as supported.

(use-package yasnippet-snippets
  :ensure t
  :after yasnippet)
2.4.4.1.8 Diff-Tool

Ediff is a merge-conflict resolver built into Emacs.

(use-package ediff
  :custom ((ediff-window-setup-function #'ediff-setup-windows-plain)
           (ediff-split-window-function #'split-window-horizontally)))
2.4.4.1.9 Version Control

Magit has been noted to be "a git wrapper that's better than git itself" (most definitely not sic), and from my experience, this is true, for the most part. Generally speaking, I do think it has a great user experience, and it uses Emacs potential far better than certain other modes. Another way to compliment it, would be to point out how minimal it's configuration needs to be (at least for me), without being in any sense annoying or otherwise inconvenient.

(use-package magit
  :if (executable-find "git")
  :ensure t
  :custom ((magit-diff-options "-b --patience")
           (magit-save-repository-buffers 'dontask)
           (magit-display-buffer-function
            'magit-display-buffer-same-window-except-diff-v1)
           (magit-diff-refine-hunk t)
           (git-commit-known-pseudo-headers
            '("Signed-off-by" "Acked-by" "Modified-by" "Cc"
              "Suggested-by" "Reported-by" "Tested-by"
              "Reviewed-by"))
           (git-commit-style-convention-checks
            '(non-empty-second-line
              overlong-summary-line)))
  :bind ("C-x g" . magit-status))
2.4.4.1.10 Compilation

The compile and recompile commands let the user start an asynchronous process, such as a compiler or anything like that to run in the background. recompile will re-use the last command entered into compile. When compilers print error messages or warnings, next-error and previous-error can be used to jump to where they were generated.

Binding compile and recompile to C-f2 and f2 makes it quick and easy to invoke the commands.

(use-package compile
  :custom ((compilation-scroll-output 'first-error)
           (compilation-ask-about-save nil))
  :bind (("<f2>" . recompile)
         ("C-<f2>" . compile)))
2.4.4.1.11 Code Discovery

If GNU Global is installed, I prepare the ggtags package. It will not automatically be turned on, to avoid confusion.

When ggtags-mode is enabled, I plug it into Emacs subsystems, such as completion-at-point and eldoc.

(use-package ggtags
  :if (executable-find "global")
  :config
  (add-hook 'ggtags-mode-hook
            (setl eldoc-documentation-function
                  #'ggtags-eldoc-function))
  (add-hook 'ggtags-mode-hook
            (setl completion-at-point-functions
                  '(ggtags-completion-at-point))))
2.4.4.1.12 Project Managment

Projectile is project management tool, that can automatically recognise project roots and run commands such as searching files, compile, and so on.

By default, the projectile-command-map is not set, so that everyone can choose where they would want it to be bound. The recommended suggestion, C-x p is pretty good, since by default the keybinding is not used.

(use-package projectile
  :demand t
  :ensure t
  :diminish projectile-mode
  :custom ((projectile-switch-project-action 'projectile-dired)
           (projectile-completion-system 'default)
           (projectile-mode t))
  :bind ("C-x p" . projectile-command-map))
2.4.4.1.13 Quick Jumping

Certain modes offer a simple indexing mechanism that allows the user to quickly jump in-between a list of top-level constructs (in programming languages, usually functions, global variables, global classes/structs, …). This functionality is exposed by the imenu command, that I bind to an easily accessible keybinding.

(use-package imenu
  :bind (("C-M-," . imenu)))
2.4.4.2 Programming Modes
2.4.4.2.1 C

From what one can see, it is obvious that I still have to get around to properly set up my C editing environment.

(use-package cc-mode
  :custom ((c-delete-function #'delete-char)
           (c-default-style '((java-mode . "java")
                              (awk-mode . "awk")
                              (other . "k&r")))))
2.4.4.2.1.1 Macro Expansion

When the C preprocessor expands macros, it's usually hard to read, probably because it's not primarily made for humans. This little piece of advice will try to use clang-format (if installed) to re-format the output.

(use-package cmacexp
  :if (executable-find "clang-format")
  :config
  (defun zge/clang-format (start end subst)
    (unless subst
      (let ((inhibit-read-only t))
        (shell-command-on-region (point-min) (point-max)
                                 "clang-format"
                                 nil t nil))))
  (advice-add 'c-macro-expand :after #'zge/clang-format))
2.4.4.2.1.2 Semantic

Semantic mode parses the current buffer and using it's information allows me to complete functions. All that's missing for my setup is to set the CAPF function.

(use-package semantic
  :custom ((semantic-default-submodes
            '(global-semantic-idle-scheduler-mode
              global-semantic-idle-summary-mode
              global-semanticdb-minor-mode)))
  :config
  (add-hook 'semantic-mode-hook
            (setl completion-at-point-functions '(semantic-analyze-completion-at-point-function))))
2.4.4.2.2 GDB

GBB has quite good Emacs integration, such as supporting multi-window layouts, and immediate insight into the debugee's state.

But as always, there are some defaults I don't like. Here I fix them.

(use-package gdb-mi
  :commands 'gdb
  :custom ((gdb-display-io-nopopup t)
           (gdb-show-main t)
           (gdb-many-windows t)))
2.4.4.2.3 Gnuplot

gnuplot has been my go-to plotter for a few years now. Most of the time I use it in it's REPL, but especially when working with scripts, gnuplot-mode proves itself to be helpful.

Due to the wierd package name, and the fact that I use .gp as the file extention for gnuplot files, as few things have to be re-aliased for the mode to work properly.

(use-package gnuplot
  :if (executable-find "gnuplot")
  :ensure t
  :functions (gnuplot-send-string-to-gnuplot)
  :preface
  (defun zge/gnuplot-replot ()
    (interactive)
    (gnuplot-send-string-to-gnuplot "replot\n" 'line))
  :mode ((rx ".gp" eos) . gnuplot-mode)
  :bind (:map gnuplot-mode-map
              ("C-c C-c" . zge/gnuplot-replot)))
2.4.4.2.4 Web
2.4.4.2.4.1 HTML

For most of what I do, Emacs' built in mhtml-mode. The only major annoyance is it's default insistence not to close some tags (<p>, <dl>/<dd>, …), which I here prevent.

(use-package sgml-mode
  :custom ((sgml-xml-mode t)))

Furthermore, when inserting a html tag with sgml-tag (C-c C-o), don't add unnecessary newlines.

(use-package skeleton
  :custom ((skeleton-end-newline nil)))
2.4.4.2.4.2 CSS

Emacs CSS mode has a helpful little function called css-lookup-symbol, that allows me to open a readable MDN page with a description of how to use a symbol, and so on, in EWW. Since it is initially unbound, I add a keybindings for it here.

(use-package css-mode
  :bind (:map css-mode-map ("C-c C-h" . css-lookup-symbol)))
2.4.4.2.5 Go

I tend to use Go more often than I would want to, so I have made sure to have a comfortable environment with all the things one would assume (completion, documentation, error checking, etc.). A critical component for this is go-mode.

I also automatically install all cli-tools that Go depends on with my go-install macro – but all of this is only done if the go binary is found during startup.

(use-package go-mode
  :if (executable-find "go")
  :ensure t
  :preface
  (defmacro go-install (url cmd)
    `(unless (executable-find ,cmd)
       (shell-command (concat "go get " ,url "/" ,cmd))))
  :custom ((gofmt-command "goimports"))
  :config
  (let* ((go-path  "/home/phi/code/go")
         (go-bin (expand-file-name "bin" go-path)))
    (setenv "GOPATH" go-path)
    (add-to-PATH go-bin))
  (go-install "golang.org/x/tools/cmd" "goimports")
  (go-install "golang.org/x/tools/cmd" "godoc")
  (go-install "golang.org/x/tools/cmd" "guru")
  (go-install "golang.org/x/lint" "golint")
  :hook ((go-mode . subword-mode)
         (go-mode
          . (lambda () (add-hook 'before-save-hook
                                 #'gofmt-before-save t t))))
  :bind (:map go-mode-map
              ("M-." . godef-jump)
              ("C-c ." . godoc-at-point)
              ("C-c C-r" . go-remove-unused-imports))
  :mode (rx ".go" eos))

Hovering over symbols should print documentation about said symbol in the minibuffer. godef is required for this.

(use-package go-eldoc
  :after go-mode
  :ensure t
  :config
  (go-install "github.com/rogpeppe" "godef")
  :hook (go-mode . go-eldoc-setup))

Just like with C above, I load my own gocode based completion function.

(use-package go-capf
  :ensure t
  :after go-mode
  :config
  (go-install "github.com/mdempsky" "gocode")
  (add-hook 'go-mode-hook
            (setl completion-at-point-functions (list #'go-capf))))

Delve is a debugger for go, and go-dlv is it's GUD/GDB interface.

(use-package go-dlv
  :ensure t
  :config
  (go-install "github.com/go-delve/delve/cmd" "dlv"))
2.4.4.2.6 Scheme

When properly set up, Geiser gives an pleasant development experience when working with Scheme. It's not perfect, and it sometimes drags the whole editor down, but for the amount of Scheme programming I do it's entirely sufficient.

(use-package geiser
  :if (or (executable-find "guile")
          (executable-find "scheme")
          (executable-find "mit-scheme"))
  :ensure t
  :custom ((geiser-repl-use-other-window nil))
  :config
  (when (file-exists-p "/usr/local/share/guile/site/2.2")
    (setenv "GUILE_LOAD_PATH" "/usr/local/share/guile/site/2.2:..."))
  :hook (scheme-mode . geiser-mode)
  :mode ((rx ".scm" eos) . scheme-mode))
2.4.4.2.7 Common Lisp

Emacs already comes with a major mode for working with Common Lisp, namely common-lisp-mode. It works, but I want more.

Just like Org, SLIME, the Common Lisp development environment, is one of the best and most thorough Emacs packages that has been created some ~20 years ago. This video demonstration gives a good overview of it's features, such as auto completion, debugging, documentation, etc.

My main customisation are:

  • setting up SBCL
  • using common-lisp specific indentation,
  • loading additional SLIME packages (REPL, hyperspec documentation, eldoc-like documentation, alternative completion mechanisms, …)

    (use-package slime
      :if (executable-find "sbcl")
      :ensure t
      :commands 'slime
      :custom ((common-lisp-hyperspec-root "file:///usr/share/doc/hyperspec/")
               (slime-completion-at-point-functions
                '(slime-simple-completion-at-point slime-filename-completion))
               (slime-contribs '(slime-hyperdoc
                                 slime-repl
                                 slime-autodoc
                                 slime-macrostep
                                 slime-references
                                 slime-mdot-fu
                                 slime-xref-browser
                                 slime-presentations
                                 slime-cl-indent
                                 slime-fancy-inspector
                                 slime-fontifying-fu
                                 slime-trace-dialog))
               (inferior-lisp-program (executable-find "sbcl")))
      :bind (:map slime-mode-map ("C-<return>" . slime-selector)
                  :map slime-repl-mode-map ("DEL" . nil))
      :mode ((rx (or ".lisp" ".cl") eos) . common-lisp-mode))
    
2.4.4.2.8 Haskell

To configure Haskell, one has to do a lot. This, I should say doesn't suffice, even if it might have one had. The haskell-mode gives good indentation and documentation, but completion and code discovery is a lot harder to set up. With the move HIE, I have given up on trying to properly configure Haskell.

(use-package haskell-mode
  :if (executable-find "ghc")
  :ensure t
  :custom ((haskell-completing-read-function 'completing-read))
  :hook ((haskell-mode . haskell-doc-mode)
         (haskell-mode . haskell-indentation-mode)
         (haskell-mode . interactive-haskell-mode)
         (haskell-interactive-mode . electric-pair-mode))
  :mode (rx ".hs" eos))
2.4.4.2.9 Perl

Emacs has two Perl modes, the simpler perl-mode and the more expansive cperl-mode. I have decided to use the latter, and for the most part it already works well. All I do is configure eldoc to show documentation.

(use-package cperl-mode
  :ensure t
  :config
  (add-hook 'cperl-mode-hook
            (setl eldoc-documentation-function
                  (lambda ()
                    (let (cperl-message-on-help-error)
                      (car (cperl-get-help))))))
  :interpreter ("perl" . cperl-mode)
  :mode (((rx ".pl" eos) . cperl-mode)
         ((rx "." (? "d") "cgi" eos) . cperl-mode)))
2.4.4.2.10 APL

This package isn't found in ELPA/MELPA, so I had to manual clone it to my local lisp directory.

My main changes are activating the APL input method with my preferred prefix key, as to use the necessary symbols (⍳, ⍬, ⎕, …) and changing the font to the Dyalog APL font.

(use-package gnu-apl-mode
  :if (executable-find "apl")
  :load-path "lisp/gnu-apl-mode/"
  :commands 'gnu-apl
  :custom ((gnu-apl-show-apl-welcome nil)
           (gnu-apl-key-prefix ?`)
           (gnu-apl-show-tips-on-start nil))
  :init
  (defalias 'run-apl #'gnu-apl)
  :config
  (set-face-font 'gnu-apl-default "xft:-acds-APL385 Unicode-normal-normal-normal-*-14-*-*-*-*-0-iso10646-1")
  (defun zge/gnu-apl-init ()
    (setq-local default-input-method "APL-Z")
    (toggle-input-method)
    (setq-local buffer-face-mode-face 'gnu-apl-default)
    (buffer-face-mode))
  (add-hook 'gnu-apl-interactive-mode-hook #'zge/gnu-apl-init)
  (add-hook 'gnu-apl-mode-hook 'zge/gnu-apl-init)
  :mode ((rx ".a" (? "pl") eos) . gnu-apl-mode))

2.4.5 Text Editing

2.4.5.1 Prose
2.4.5.1.1 Sentences

I dislike the standard sentence definition Emacs uses, since for me a sentence is just a publication mark, followed by white space. Optionally, non-word characters are acceptable between the punctuation and the whitespace, like when writing _No!_ in Markdown.

(use-package paragraphs
  :custom ((sentence-end-double-space nil)))
2.4.5.1.2 Text Modes

All text modes share a common parent mode: text-mode. Here I configure what they shall have in common:

(use-package text-mode
  :config
  (advice-add 'auto-fill-function :after
              (lambda (&rest _) (scroll-right most-positive-fixnum)))
  :hook ((text-mode . turn-on-auto-fill)
         (text-mode . (lambda ()
                        (unless (derived-mode-p 'org-mode)
                          (flyspell-mode t))))))
2.4.5.1.2.1 LaTeX

Installing AucTeX is a bit wierd, since it doesn't quite fit the use-package paradigm. Most changes I make, are quite standard, the only noteworthy points are:

  • Use pdf-tools instead of an external (usually Evience) viewer
  • Let electric-pair-mode delete adjacent parentheses, but don't insert any. Why? Because that will make cdlatex a lot easier to configure.
  • If AucTeX recognizes the document to be German, call my language toggle function.
  • Don't bother installing anything, if no latex compiler is found.

    (use-package latex
      :if (executable-find "pdflatex")
      :ensure auctex
      :custom ((TeX-master 'dwim)
               (LaTeX-electric-left-right-brace t)
               (LaTeX-syntactic-comments nil)
               (TeX-auto-save t)
               (TeX-parse-self t)
               (preview-auto-cache-preamble t)
               (reftex-plug-into-AUCTeX t)
               (reftex-enable-partial-scans t))
      :config
      (when (fboundp 'pdf-view-mode)
        (setf (alist-get 'output-pdf TeX-view-program-selection) '("PDF Tools")))
      (add-hook 'LaTeX-language-de-hook (lambda () (zge/toggle-dictionary "de")))
      (add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer)
      (add-hook 'LaTeX-mode-hook (setl post-self-insert-hook
                                       (remq #'electric-pair-post-self-insert-function
                                             post-self-insert-hook)))
      :hook ((LaTeX-mode . reftex-mode)
             (LaTeX-mode . TeX-fold-mode))
      :mode ((rx ".tex" eos) . TeX-latex-mode))
    

    Additionally, CDLaTeX provides a more comfortable input and intuitive automation, where possible. I extend the tables by a few commands that I like to use more often, as to make working with TeX more comfortable. Note that these changes will also take effect in my Org configuration.

    (use-package cdlatex
      :after (org latex)
      :ensure t
      :custom ((cdlatex-paired-parens "$([{|<"))
      :config
      (add-to-list 'cdlatex-math-modify-alist
                   '(?# "\\mathbb" nil t nil nil))
      (add-to-list 'cdlatex-math-modify-alist
                   '(?F "\\mathfrak" nil t nil nil))
      (add-to-list 'cdlatex-math-symbol-alist
                   '(?# ("\\equiv")))
      (add-to-list 'cdlatex-command-alist
                   '("eq" "Insert \\[ \\] pair"
                     "\\[ ? \\]" cdlatex-position-cursor nil t nil))
      (dolist (key '("(" "[" "{" "$" "|" "<" "C-c ?" "C-c {" "C-<return>"))
        (define-key cdlatex-mode-map (kbd key) nil))
      :bind (:map cdlatex-mode-map
                  ("<backtab>" . indent-according-to-mode))
      :hook (LaTeX-mode . cdlatex-mode))
    
2.4.5.1.2.2 Org

The following configuration is wrapped in a use-package macro…

(use-package org
  :defines (org-html-mathjax-options)
  :commands (org-next-link
             org-previous-link
             turn-on-org-cdlatex
             org-babel-do-load-languages)
  :config
  (require 'org-agenda)

Basic stylistic and movment options.

(setq org-use-speed-commands t
      org-yank-adjusted-subtrees t
      org-startup-folded nil
      org-return-follows-link t
      org-highlight-latex-and-related '(latex entities)
      org-M-RET-may-split-line '((default))
      org-special-ctrl-a/e t
      org-special-ctrl-k t)

Especially this document uses a lot of source blocks, so highlighting and indenting them appropriately is very convenient.

(setq org-fontify-whole-heading-line t
      org-fontify-quote-and-verse-blocks nil
      org-src-fontify-natively t
      org-src-tab-acts-natively t
      org-src-window-setup 'current-window)

Within my documents directory (~/doc/) I have an org directory just for org files, as set in org-directory. In this directory, there is another file, just called .org-agenda, where on each line one file is listed, to add to my agenda list.

(setq org-directory (expand-file-name "~/doc/org/")
      org-agenda-files (expand-file-name ".org-agenda" org-directory)
      org-agenda-inhibit-startup t
      org-agenda-window-setup 'current-window
      org-default-notes-file (expand-file-name "notes.org" org-directory))

Having special capture templates will probably help in getting used to using org-mode for taking notes.

(setq org-capture-templates
      `(("a" "Appointment" entry (file+headline "pers.org" "Appointments")
         "* %^t %?\n")
        ("p" "Plans" entry (file+headline "pers.org" "Plans")
         "* %^t %?\n")
        ("t" "Todo" entry (file+headline "pers.org" "Todo")
         "* TODO %?\n%i")
        ("c" "Note" plain (file+olp+datetree ,org-default-notes-file)
         "* %?\n  Entered on %U")
        ("l" "Link" entry (file+olp+datetree ,org-default-notes-file)
         "* %?\n\ %^L\n  Entered on %U")
        ("j" "Journal" entry (file+datetree "journal.org.gpg")
         "* Entered on %U\n %i")))

Since I don't require a complex TODO setup, I have chosen to keep the default keywords, as one often finds them recommended.

(setq org-todo-keywords '((sequence "TODO(t)" "WAIT(w)" "NEXT(n)" "DONE(d)")))

General export settings

(setq org-export-date-timestamp-format "%X"
      org-html-metadata-timestamp-format "%X"
      org-export-backends '(ascii beamer html latex md)
      org-export-dispatch-use-expert-ui t)

By default, exporting to LaTeX would produce visually unpleasing code. But by enabling minted, this issue is mitigated quite easily.

Furthermore, a few extra default packages are added, which I always enable.

(setq org-html-doctype "xhtml5"
      org-html-html5-fancy t
      org-latex-listings 'minted
      org-latex-pdf-process
      '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f")
      org-latex-packages-alist '(("" "microtype" nil)
                                 ("" "babel" nil)
                                 ("" "minted" nil)
                                 ("" "lmodern" nil)))

Default flyspell-mode complains about terms such as #+BEGIN_SRC, but flyspell-prog-mode is intelligent enough to ignore these, make sure the former is turned off, while the latter is activated (it's activated in the first place because org-mode inherits text-mode's hooks).

(dolist (hook (list #'flyspell-prog-mode
                    #'turn-on-org-cdlatex))
  (add-hook 'org-mode-hook hook))

Configure org-mode clocking and logging.

(setq org-clock-into-drawer t
      org-clock-continuously t
      org-log-into-drawer t)

Load languages for Org Babel, without the need to reconfirm.

(setq org-confirm-babel-evaluate nil)

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . nil)
   (C . t) (python . t) (scheme . t)
   (dot . t) (sqlite . t) (calc . t)
   (java . t) (awk . t) (ditaa . t)
   (haskell . t) (lisp . t)))

Setup KaTeX as compared to the default MathJax. Code from here.

(setq-default
 org-html-mathjax-template
 "<link rel=\"stylesheet\"
    href=\"https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.css\"
    integrity=\"sha384-9eLZqc9ds8eNjO3TmqPeYcDj8n+Qfa4nuSiGYa6DjLNcv9BtN69ZIulL9+8CqC9Y\"
    crossorigin=\"anonymous\"/> <script defer=\"defer\"
    src=\"https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.js\"
    integrity=\"sha384-K3vbOmF2BtaVai+Qk37uypf7VrgBubhQreNQe9aGsz9lB63dIFiQVlJbr92dw2Lx\"
    crossorigin=\"anonymous\"></script> <script defer=\"defer\"
    src=\"https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/contrib/auto-render.min.js\"
    integrity=\"sha384-kmZOZB5ObwgQnS/DuDg6TScgOiWWBiVt0plIRkZCmE6rDZGrEOQeHM5PcHi+nyqe\"
    crossorigin=\"anonymous\"
    onload=\"renderMathInElement(document.body);\"></script>")

Adding this code to org-structure-template-alist, makes it easier to maintain files like these, since expands <E to a source block with emacs-lisp automatically chosen as the language. Due to a org-mode bug, this has to be evaluated after the document has been loaded.

(add-to-list 'org-structure-template-alist
             '("el" "#+BEGIN_SRC emacs-lisp\n?\n#+END_SRC"
               "<src lang=\"emacs-lisp\">\n\n</src>"))

LaTeX previews can be a bit small and clutter the working directory, so the following options should migrate these issues.

(setq org-preview-latex-image-directory "/tmp/ltxpng/")
(plist-put org-format-latex-options :scale 1.5)

Open links in the current frame.

(setf (alist-get 'file org-link-frame-setup) #'find-file)

When jumping around a org-document (or using counsel-org-goto-all) this setting makes sure that the part of the document I just jumped to is visible, and doesn't have to be opened again manually.

(add-to-list 'org-show-context-detail
             '(org-goto . local))

Here I set a few convenient keybindings for globally interacting with my org ecosystem.

:bind (("C-c c" . org-capture)
       ("C-c a" . org-agenda)
       ("C-c l" . org-store-link)
       :map org-agenda-mode-map
       ("<tab>" . org-next-link)
       ("<S-iso-lefttab>" . org-previous-link)))

Also: Spell Checking sadly shadows org's auto-complete functionality, with an alternative I never use. When instead re-binding pcomplete, one get's a lot more out of Org, without having to look up everyhing in the manual.

2.4.5.1.2.2.1 HTML Exporter

Htmlize allows me to export files, buffers or regions as they appear to me in Emacs in HTML form. I sometimes use it myself, but for the most part it's use by Org.

(use-package htmlize :ensure t)
2.4.5.1.2.3 Markdown

Markdown is probably one of the most popular markup languages around nowadays, and tools like Pandoc really bring out it's inner potential (or rather create it in the first place). Markdown-mode offers nice support for quite a few Pandoc features, so it's usually my default choice when I have to work with shorter to medium sized documents, and web-related content generally.

I make use of pandoc as my default markdown to HTML converter.

(use-package markdown-mode
  :if (executable-find "pandoc")
  :ensure t
  :custom ((markdown-italic-underscore t)
           (markdown-command (executable-find "pandoc")))
  :custom-face (markdown-code-face ((t (:inherit fixed-pitch))))
  :mode (rx (or (seq bos "README" (opt ".md"))
                (or ".markdown" ".mkdn" ".md"))
            eos))
2.4.5.2 Movement
2.4.5.2.1 Expand Region

The expand-region utility is a helpful function that let's the user select increasingly larger semantically meaningful regions. I've bound it to the recommended default.

(use-package expand-region
  :ensure t
  :functions (er/expand-region)
  :bind ("C-=" . er/expand-region))
2.4.5.2.2 Jump in Buffer

Avy is an alternative to ace-jump, which itself is supposed to be inspired by an extension for vim. It allows me to jump to a character or word currently on the screen, by typing one or more letters. For my proposes, I most often jump to words, and occasionally to specific character – the relative ease of the two keybindings I have chosen represents this.

(use-package avy
  :ensure t
  :custom ((avy-background t))
  :bind (("C-z" . avy-goto-word-1)
         ("M-z" . avy-resume)))
2.4.5.3 Searching
2.4.5.3.1 Search Occurrences

The command occur will search the current buffer for all instances of a regular expression, and note these in a secondary buffer. These can then be search and traversed or even edited.

One thing I don't like is that the focus still remains in the initial buffer, whereas I could like it to switch to the new *Occur* buffer. This is fixed by a hook-function, that makes sure the active point is in the right buffer.

(use-package replace
  :commands occur
  :config
  (add-hook 'occur-mode-hook
            (lambda () (pop-to-buffer "*Occur*" nil t))))
2.4.5.3.2 Searching Generally

Emacs search system seems simple at first, but hides a lot if tricks up it's sleeve. I for example regularly write german texts, but only use QWERTY keyboards. By setting search-default-mode as I do below, all instances of "similar" letters such as ä to a, ö to o, … are mapped to their simpler equivalents, making searching easier.

Also, when highlighting matches, isearch will by default add a little delay. I don't need this, so it's set to wait 0 seconds.

(use-package isearch
  :custom ((lazy-highlight-initial-delay 0)
           (search-default-mode 'char-fold-to-regexp))
  :bind (("M-*" . isearch-forward-symbol-at-point)))
2.4.5.3.3 Mark Ring

The mark-ring stores previous positions, and using C-u C-<space> in a single buffer (or C-x C-<space> between all buffers) lets be jump back. As a ring, it stores up to an a priori specified number of marks, which I have raised from 16 to 32.

Also, quoting the manual

If you set set-mark-command-repeat-pop to non-nil, then immediately after you type C-u C-<SPC>, you can type C-<SPC> instead of C-u C-<SPC> to cycle through the mark ring.

makes jumping around just a bit easier.

(use-package search
  :custom ((set-mark-command-repeat-pop t)
           (mark-ring-max 32)))
2.4.5.3.4 Search

Related to Ivy, swiper offers a more graphical alternative to the standard isearch.

(use-package swiper
  :ensure t
  :bind ("C-o" . swiper))
2.4.5.4 Completion
2.4.5.4.1 Dynamic Expansion

Emacs has multiple facilities for completing partial input. completion-at-point attempts to use mode-specific algorithms to find a proper suggestion, and on the other side abbrev-mode just looks in a table for the matching expansion for any abbreviation.

Somethere in-between the two is dabbrev-expand, that tries to search buffers for longer versions of the current word, and immediately insert it into the current buffer. Press the keybinding for dabbrev-expand (usually M-/) for another try. This can be done anywhere, be it in a totally unrelated buffer or even a minibuffer prompt.

The more general version of dabbrev-expand is hippie-expand, that uses a list of functions to attempt completing a word, so that it's suggestions are not only words in this or other buffers, but also filenames or killed words. At the same time, it is more intelligent in what dabbrev-expand does well, by being able to complete visible words first, before thinking about all words in a buffer.

(use-package hippie-expand
  :custom ((hippie-expand-try-functions-list
            '(try-expand-dabbrev-visible
              try-expand-dabbrev
              try-expand-dabbrev-all-buffers
              try-expand-dabbrev-from-kill
              try-complete-file-name
              try-complete-file-name-partially)))
  :bind (("M-/" . hippie-expand)))
2.4.5.4.2 Auto-Pairing Parentheses

Automatically paring parentheses is a high priority for me. Doing this properly makes or breaks it. Emacs uses electric-pair-mode for these kinds of things, but I don't like using the global minor mode, and instead enable it where-ever appropriate as a global minor mode.

(use-package elec-pair
  :demand t
  :hook ((minibuffer-setup . electric-pair-local-mode)
         (text-mode . electric-pair-local-mode)
         (prog-mode . electric-pair-local-mode)
         (comint-mode . electric-pair-local-mode)))
2.4.5.5 Writing While Region is Active

Historically, setting the mark didn't highlight anything, and writing didn't automatically delete the region. This can still be seen in simpler or older Emacsen such as mg. To my understanding this was changed, to match the behaviour that most people have come to expect from working with "selections".

As I prefer the classic behaviour, I make sure Emacs does the right thing:

(use-package simple
  :custom ((delete-active-region nil)))
2.4.5.6 Selecting with Shift

Just like in the previous secion, modern Emacs tried to be more user friendly that I like it. Pressing shift while moving selects a region, as it does in many other editors. I don't consider this to be the correct behaviour, so I reset it.

(use-package simple
  :custom ((shift-select-mode nil)))

2.4.6 Subsystems

2.4.6.1 PDF Viewer

The default PDF viewer in Emacs, doc-view converts each page of a PDF into an image, and then displays it. This results in a rather slow and non-interactive experience. An improvement is given by the PDF-Tools package, that allows various tricks such as searching a file and zooming in and out arbitrarily, without looking clearness, all within Emacs.

(use-package pdf-tools
  :if (display-graphic-p)
  :ensure t
  :custom ((pdf-view-display-size 'fit-page)
           (pdf-view-use-unicode-ligther nil)
           (pdf-view-midnight-colors '("#ffffff" . "#000000")))
  :config
  (pdf-tools-install t)
  :mode ((rx ".pdf" eos) . pdf-view-mode)
  :magic ("%PDF-" . pdf-view-mode)
  :bind (:map pdf-view-mode-map
              ("i" . pdf-view-midnight-minor-mode)
              ("c" . pdf-annot-add-text-annotation)))
2.4.6.2 Dictionary

The A Dictionary Server Protocol specified a way for client to ask dictionary servers for definitions. This package implements the protocol, and is set up to listen to a local server. It will only be loaded when one is found.

(use-package dictionary
  :if (file-exists-p "/etc/dictd")
  :ensure t
  :custom ((dictionary-server "localhost"))
  :bind (("<f8>". dictionary-search)
         ("C-<f8>". dictionary-match-words)))
2.4.6.3 Mail

Managing Email from within Emacs is certainly doable, thought certainly not for everyone. I played around with it for quite a while, trying different clients (mu4e, notmuch, Gnus) using IMAP, POP3, Maildir, but have now found a quite nice setup using the packages and tools I am about to introduce.

2.4.6.3.1 Reading Mail

Reading Email in Emacs can be hard. Usually people consider Gnus, as it's a quite advanced mailreader, with an extensive abstraction mechanism.

I used Gnus for a while (~2-3 years), but I never really mastered it. Part of the reason were the confusing keybindings, but also its general complexity. There is a seemingly innumerable amount of variables, hooks, documentation and related sub-modules – and that's not considering the changes between versions.

Eventually (<2020-01-20 Mon>) I decided to try out Rmail, the "official" but commonly dismissed mail reader for Emacs. It's a lot simpler than Gnus, but as I have realised, it's simple enough.

My setup requires two mail sources, and coincidentally involves two programmes: mpop and movemail. The first is used to fetch mail from my mail servers and store it in local mbox files (via a regular cron job), the later is used by Rmail to move and merge these into one "main" inbox (inbox.mbox). The benefit to this approach is that display-mail-mode can easily be configured to check new mail, as explained below.

Mpop requires a simple configuration, just so to know where and how to fetch it's mail from. In my case it looks like this:

defaults
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
port 587

account pers
host smtp.fastmail.com
user philip@warpmail.net
from philip@warpmail.net
passwordeval authinfo login=warp

account uni
host smtp-auth.fau.de
user philip.kaludercic@fau.de
from philip.kaludercic@fau.de
passwordeval authinfo login=fau

account default: pers

This uses my authinfo script to extract my password from ~/.authinfo.gpg.

All in all, reading and displaying mail works as it should. I just ask for the summary buffer to be a bit smaller, list my email addresses, prioritise non-HTML mail and restrict the number of headers to be shown by default.

The last thing I have to do is to configure my "secondary mailboxes", ie. where I move mail from the inbox into. I choose a sub-directory (out, where all files end with .mbox) and make sure that Rmail will not copy, but actually "move" the messages.

(use-package rmail
  :if (executable-find "movemail")
  :custom ((rmail-primary-inbox-list
            '("mbox://~/etc/mail/in/pers.mbox"
              "mbox://~/etc/mail/in/uni.mbox"))
           (rmail-file-name "~/etc/mail/inbox.mbox")
           (rmail-user-mail-address-regexp
            (rot13-string (rx (or "cuvyvc.xnyhqrepvp@snh.qr"
                                  "cuvyvc@jnecznvy.arg"))))
           (rmail-mime-prefer-html nil)
           (rmail-mime-attachment-dirs-alist '(("" "~/dl")))
           (rmail-displayed-headers
            (rx bol (or "To" "Cc" "From" "Date" "Subject") ":"))
           (rmail-secondary-file-directory "~/etc/mail/old/")
           (rmail-secondary-file-regexp "\\.mbox\\'")
           (rmail-delete-after-output t)
           (rmail-default-file "~/etc/mail/old/")
           (mail-dont-reply-to-names rmail-user-mail-address-regexp))
  :hook ((rmail-mode . visual-line-mode))
  :mode ((rx ".mbox" eos) . rmail-mode)
  :bind (("C-x x m" . rmail)))
2.4.6.3.2 Sending Mail

Emacs can send mail without external dependencies, but currently this means that you would only have one SMTP server with one account. A simple solution to this problem is to use msmtp, that simply resends messages and is a bit faster than Emacs' SMTP implementation.

For this to work, I require a simple configuration, found in ~/.msmtprc:

defaults
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
port 587

account pers
host smtp.fastmail.com
user philip@warpmail.net
from philip@warpmail.net
passwordeval authinfo login=warp

account uni
host smtp-auth.fau.de
user philip.kaludercic@fau.de
from philip.kaludercic@fau.de
passwordeval authinfo login=fau

account default: pers

Again, my authinfo script is being used.

Emacs now has to be set up to work with msmtp. The trick is that msmtp implements sendmail's CLI interface, that is supported by Emacs (See message-send-mail-function). A few more options are required to make msmtp work as expected, but otherwise it's nothing extraordinary.

One extra trick is to add a "FCC" flag. This will send a "File Carbon Copy" to the file returned by the function saved in message-default-headers. This is how I save my outgoing messages.

(use-package message
  :if (executable-find "msmtp")
  :config
  (defun zge/message-add-fcc ()
    (format "FCC: ~/etc/mail/out/%s.mbox"
            (downcase (format-time-string "%Y-%h") )))
  :custom ((sendmail-program (executable-find "msmtp"))
           (message-send-mail-function 'message-send-mail-with-sendmail)
           (message-sendmail-extra-arguments '("--read-envelope-from"))
           (message-sendmail-envelope-from 'header)
           (message-kill-buffer-on-exit t)
           (message-sendmail-f-is-evil t)
           (message-forward-as-mime t)
           (message-interactive nil)
           (message-default-headers #'zge/message-add-fcc)))
2.4.6.3.3 Searching Mail

Rmail has a simple filtering mechanism, but more complex queries, over multiple mail boxes aren't supported. For this use-case I use mairix. It's a quick external tool that regularly re-scans my mailboxes (again, using cron). It requires some, but not too much configuration. In my case it looks like this

base=~/etc/mail
mbox=inbox.mbox:out/...:old/...
mformat=mbox
database=~/etc/mail/.mairix.db

Read mairixrc(1) for more details.

The Emacs configuration only has to be told to respect my directory preferences, and keep everything mail related in ~/etc/mail.

(use-package mairix
  :if (executable-find "mairix")
  :custom ((mairix-file-path "~/etc/mail/")
           (mairix-search-file "search.mbox"))
  :bind (("C-x x s" . mairix-search)))
2.4.6.3.4 Contacts

When sending messages to someone you know, it's helpful to have a manageable list of contacts. This is provided by the Insidious Big Brother Database, BBDB.

It stores all contacts in a file (~/etc/mail/contacts), but this file is never edited manually. Instead the database buffer is opened, viewed and manipulated by running the command bbdb.

Most of the configuration is changing appearance and some basic behaviour. The important lines are in the :config section, where BBDB is initiated for reading mail, sending messages (autocompletion) and encryption.

(use-package bbdb
  :ensure t
  :custom ((bbdb-complete-mail-allow-cycling t)
           (bbdb-completion-display-record nil)
           (bbdb-message-all-addresses t)
           (bbdb-layout 'one-line)
           (bbdb-file "~/etc/mail/contacts"))
  :config
  (bbdb-initialize 'rmail 'message 'pgp))
2.4.6.3.5 Notification

Since my mail is fetched outside of Emacs, I don't (usually) have to worry about receiving mail. The only thing that's missing is a system to notify me that there is new mail.

This is built into Emacs, but a bit hidden behind minor mode display-time-mode. When the variable display-time-mail-directory is set, the minor mode will check if there is a non-empty file in the described path. If yes, a little "Mail" notification pops up in every mode line.

And while I'm at it, increase the update-frequency from once a minute to three times a minute, set the time to 24h-mode and hide the load average.

(use-package time
  :demand t
  :custom ((display-time-mail-directory "~/etc/mail/in")
           (display-time-24hr-format t)
           (display-time-default-load-average nil)
           (display-time-interval 20))
  :config
  (display-time-mode t))
2.4.6.4 IRC

I am usually connected to a few IRC servers via a bouncer. To connect to the bouncer (ZNC), I use ERC, one of the two bundled IRC clients in Emacs (the other one is rcirc). My bouncer only hosts one connection, so this might not work for more complex configurations.

It is worth noting that I overwrite the irc function, that usually is an alias to rcirc. This is because I used to use rcirc, and I am used to starting IRC with that command. Internally it uses the lookup-password function, defined above.

(use-package erc
  :custom ((erc-email-userid "zge")
           (erc-nick "zge")
           (erc-server "zge.us.to")
           (erc-port 25331)
           (erc-join-buffer 'bury)
           (erc-server-auto-reconnect t)
           (erc-server-reconnect-timeout 30)
           (erc-hide-list '("PART" "MODE" "QUIT"))
           (erc-track-enable-keybindings t)
           (erc-track-shorten-aggressively t)
           (erc-track-exclude-server-buffer t)
           (erc-track-exclude-types
            (append erc-hide-list
                    '("JOIN" "NICK" "324" "329"
                      "332" "333" "353" "477"
                      "305" "306"))))
  :init
  (defun zge/irc-init ()
    (interactive)
    (erc-spelling-mode t)
    (erc-tls :password (lookup-password erc-server)))
  (defalias 'irc #'zge/irc-init)
  :hook ((erc-mode . electric-pair-local-mode))
  :bind (:map erc-mode-map
              ("M-<tab>" . completion-at-point)))
2.4.6.5 Feed Reader

A popular solution to reading RSS/Atom feeds in Emacs is Elfeed. Compared to bundeled alternatives (newsticker, Gnus nnrss) it feels faster and more natural to use.

Elfeed's defaults work quite well, so there's no need to change much usability-wise. I just tell elfeed not to store it's metadata in my home directory.

(use-package elfeed
  :ensure t
  :custom ((elfeed-db-directory "~/.local/share/elfeed"))
  :bind ("C-x x f" . elfeed))
2.4.6.6 Browser

Use whatever is set as the default browser on the current system, when opening http:// links. (But still let eww be properly configured.) Additionally, the contrast is increased to make webpages (and HTML emails) with peculiar background colors render better.

(use-package eww
  :custom ((browse-url-browser-function
            '(("^file:///usr/share/doc/" . eww-browse-url)
              ("" . browse-url-default-browser)))
           (eww-download-directory (expand-file-name "~/dl"))
           (shr-use-colors nil)))
2.4.6.7 Calendar

The default Emacs calendar configuration is a bit simplistic and peculiar. I've always been used to weeks starting on Monday and prefer ISO over the American date format, so I set calendar to work accordingly. Furthermore, I request holidays and diary entries to be highlighted.

Based on the EmacsWiki Calendar Localization Article, I list inform Emacs German/Bavarian holidays, since these are relevant to me.

Setting calendar-mark-holidays-flag highlights these holidays, but I would have to press another key (h) for it to be described. An auxiliary function zge/calendar-print-holiday will do this automatically, when the cursor is on a holiday date.

(use-package calendar
  :custom ((calendar-week-start-day 1)
           (calendar-longitude 10.9887)
           (calendar-latitude 49.4771)
           (calendar-date-style 'iso)
           (calendar-christian-all-holidays-flag t)
           (calendar-mark-holidays-flag t)
           (calendar-mark-diary-entries-flag t)
           (holiday-general-holidays
            '((holiday-fixed 1 1 "New Year")
              (holiday-fixed 5 1 "1st Mai")
              (holiday-fixed 10 3 "Tag der Deutschen Einheit")
              (holiday-fixed 12 31 "Sylvester")))
           (holiday-christian-holidays
            '((holiday-fixed 1 6 "Heilige Drei Könige")
              (holiday-easter-etc -48 "Rosenmontag")
              (holiday-easter-etc  -2 "Karfreitag")
              (holiday-easter-etc   0 "Ostersonntag")
              (holiday-easter-etc  +1 "Ostermontag")
              (holiday-easter-etc +39 "Christi Himmelfahrt")
              (holiday-easter-etc +49 "Pfingstsonntag")
              (holiday-easter-etc +50 "Pfingstmontag")
              (holiday-easter-etc +60 "Fronleichnam")
              (holiday-fixed 8 15 "Mariae Himmelfahrt")
              (holiday-fixed 11 1 "Allerheiligen")
              (holiday-float 11 0 1 "Totensonntag" 20)
              (holiday-float 12 0 -4 "1. Advent" 24)
              (holiday-float 12 0 -3 "2. Advent" 24)
              (holiday-float 12 0 -2 "3. Advent" 24)
              (holiday-float 12 0 -1 "4. Advent" 24)
              (holiday-fixed 12 25 "1. Weihnachtstag")
              (holiday-fixed 12 26 "2. Weihnachtstag"))))
  :config
  (defun zge/calendar-print-holiday ()
    (when (calendar-check-holidays (calendar-cursor-to-date t nil))
      (calendar-cursor-holidays)))
  :hook ((calendar-move . zge/calendar-print-holiday)
         (calendar-mode . toggle-truncate-lines))
  :bind (("C-x x d" . calendar)
         :map calendar-mode-map
         ("f" . calendar-forward-day)
         ("n" . calendar-forward-week)
         ("b" . calendar-backward-day)
         ("p" . calendar-backward-week)))
2.4.6.8 Window Management

The winner-mode global mode lets it's user easily recreate previous window configurations, similarly to regular undo'ing in buffers.

(use-package winner
  :demand t
  :config
  (winner-mode))

Furthermore, windmove lets one switch from one window to another. This is useful when one wants to avoid C-x o'ing around the screen all the time. Windmove provides no minor mode, but instead wants a special function to be called, that globally defines "shift-arrow key" keybindings, to match the direction one wants to move in.

(use-package windmove
  :defer nil
  :config
  (windmove-default-keybindings))
2.4.6.9 RPN Calculator

Emacs has two calculators, of which calc is the better one. It offers a full RPN experience, with linear algebra, symbolic algebra, complex arithmetic, and some simple analysis. For this it has been called "Poor Mann's Mathematica", though for me this has not been an issue.

My configuration mainly consists of storing my preferences (such as how to display vectors/matrices, complex numbers, etc.).

(use-package calc
  :config
  (setq calc-angle-mode 'rad
        calc-shift-prefix t
        calc-infinite-mode t
        calc-vector-brackets nil
        calc-vector-commas nil
        calc-matrix-just 'right
        calc-matrix-brackets '(R O)
        calc-complex-format 'i))
2.4.6.10 Remote System Interaction

TRAMP is for transparently accessing remote files from within Emacs. It's pretty cool, and allows me to work on remote systems within my current Emacs session. Kind of a bit like namespaces on Plan 9. Various backends (ssh, sftp, adb, …) allow me to specialise on whatever kind of work I want to do.

My configuration just consists of telling TRAMP to use my local backup directory for backups, instead of the remote system. This prevents sudden hick-ups while working on a remote system.

(use-package tramp
  :demand t
  :custom ((tramp-backup-directory-alist backup-directory-alist)
           (enable-remote-dir-locals t))
  :config
  (add-to-list 'backup-directory-alist
               (cons tramp-file-name-regexp nil)))
2.4.6.11 Kamoji

Kamojis are eastern emoticons (something like "(b ᵔ▽ᵔ)b"), that are usually quite creative but hard to write. I've collected a few functions to easily insert these into the current buffer, where the main one, kamoji-insert let's me choose between multiple different categories.

(use-package insert-kaomoji
  :ensure t
  :bind ("C-x 8 k" . insert-kaomoji))

3 Interactive Functions

All private functions and variables are be prefixed with zge/, but sometimes are aliases to simpler names.

3.1 Toggle dictionary

Since I regularly have to switch between English and German, and I am a horrible speller, having a quick function to toggle between just the two (using Spell Checking) had been very nice. Additionally, my input method is changed based on zge/input-alist.

(defconst zge/input-alist '(("british" . nil)
                            ("german-new8" . "german-postfix")))
(defconst zge/toggle-dict-modes '(text-mode erc-mode))
(defvar-local zge/dict-ring nil)

(defun zge/toggle-dictionary (&optional lang no-check)
  "Toggle the Ispell dictionary from English to German and vice versa."
  (interactive (list nil current-prefix-arg))
  (unless zge/dict-ring
    (setq zge/dict-ring (ring-convert-sequence-to-ring
                         (mapcar #'car zge/input-alist))))
  (unless lang
    (setq lang (ring-remove zge/dict-ring))
    (ring-insert zge/dict-ring lang))
  (ispell-change-dictionary lang)
  (when (delq nil (mapcar (lambda (m) (derived-mode-p m))
                          zge/toggle-dict-modes))
    (let* ((next-im (assoc lang zge/input-alist)))
      (when next-im
        (set-input-method (cdr next-im)))))
  (unless (or no-check (> (- (point-max) (point-min)) 10000))
    (save-mark-and-excursion
      (flyspell-large-region (point-min) (point-max)))))

(global-set-key (kbd "<f5>") #'zge/toggle-dictionary)

Most of the time, empirical studies have found, I use zge/toggle-dictionary in org-mode since half of what I write is in English and the other half in German. To make life even easier, this function looks for a #+LANGUAGE tag (that's usually just used for exporting) and turns on that language for this buffer, similarly to what LaTeX-language-de-hook does.

(defun zge/org-check-lang ()
  "Look for LANGUAGE tag and turn on the language specified"
  (save-excursion
    (goto-char (point-min))
    (save-match-data
      (when (re-search-forward (rx bol "#+LANGUAGE: " (group (one-or-more word))) nil t)
        (let ((lang (pcase (match-string-no-properties 1)
                      ("en" "british")
                      ("de" "german-new8")
                      (_ nil))))
          (when lang
            (zge/toggle-dictionary lang)
            (message "Toggled language to \"%s\"" lang)))))))

(with-eval-after-load 'org
  (add-hook 'org-mode-hook #'zge/org-check-lang))

3.2 Swap keybindings

First mentioned here, to argue for elisp v.s. vim script, I don't need this function that often, nevertheless I keep in here, just in case.

(defun zge/swap-keys (kb1 kb2 &optional map)
  "Swap the functions behind KB1 and KB2 in MAP"
  (interactive "kFirst key: \nkSecond key: ")
  (let* ((m (or map (current-global-map)))
         (f1 (lookup-key m kb1))
         (f2 (lookup-key m kb2)))
    (define-key m kb1 f2)
    (define-key m kb2 f1)))

As examples, I default isearch to use regular expressions before "regular" searching.

(zge/swap-keys (kbd "M-%") (kbd "C-M-%"))

3.3 Replace Region with Hex Charachter References

When fuzzing Email addresses, replacing ASCII text with Hexadecimal XML Charachter References can make it a bit harder for spiders and bots to find and spam you.

(defun zge/xml-hexify (start end)
  "Convert region into XML hex code"
  (interactive "rRegion:")
  (save-excursion
    (save-restriction
      (narrow-to-region start end)
      (goto-char (point-min))
      (while (not (eobp))
        (let ((char (following-char)))
          (delete-char 1)
          (insert-before-markers-and-inherit
           (format "&#x%X;" char)))))))

(defalias 'xml-hexify #'zge/xml-hexify)

Idea taken from the Discount Markdown parser.

3.4 Recreate *scratch* buffer

To prevent myself from accidentally killing my *scratch* buffer, I locally override what the C-x k keybinding does, emulating the kill-buffer function, without actually killing anything.

(defvar zge/scratch-buf nil
  "Currently active scratch buffer")

(defun new-scratch (&optional keep)
  "Generate a new *scratch* buffer"
  (interactive "P")
  (setq zge/scratch-buf (get-buffer-create "*scratch*"))
  (with-current-buffer zge/scratch-buf
    (lisp-interaction-mode)
    (goto-char (point-min))
    (unless keep
      (save-mark-and-excursion
        (delete-region (point-min) (point-max)))
      (insert (substitute-command-keys initial-scratch-message)))
    (set-buffer-modified-p nil)
    (local-set-key (kbd "<f12>") #'bury-buffer))
  (when (called-interactively-p 'any)
    (switch-to-buffer "*scratch*")))

(add-hook 'after-init-hook #'new-scratch)

Contrary to what some people say, I think the scratch buffer is useful, and I often want to use it quickly. For that reason, F12 is my scratch-key: Pressing it from anywhere outside the brings me there, and (as implemented above) pressing it again, buries it in the buffer stack.

(defun zge/switch-to-scratch ()
  "Switch to active scratch buffer."
  (interactive)
  (unless (buffer-live-p zge/scratch-buf)
    (new-scratch nil))
  (switch-to-buffer zge/scratch-buf))

(global-set-key (kbd "<f12>") #'zge/switch-to-scratch)

3.5 Eval and Replace

My version of a common function one sees used in different configurations. The main difference is that I don't use the kill-ring, and employ my above defined macro.

(defun zge/eval-and-replace ()
  (interactive)
  (save-excursion
    (backward-sexp)
    (let* ((end (point-after forward-sexp))
           (raw (buffer-substring (point) end))
           (sexp (eval (read raw))))
      (delete-region (point) end)
      (insert-before-markers
       (prin1-to-string sexp)))))

(defalias 'eval-and-replace 'zge/eval-and-replace)
(global-set-key (kbd "C-x x e") #'zge/eval-and-replace)

3.6 Move from Downloads directory

Sometimes I have downloaded a file I would like to move to the current directory I'm currently working in, but since this happens quite a lot, this function makes it easier, by suggesting the right files, right away.

(defun zge/move-from-dl ()
  (interactive)
  (let* ((file (read-file-name "Move: " "~/dl" nil t))
         (nfile (expand-file-name
                 (file-name-nondirectory file)
                 (or dired-directory default-directory))))
    (rename-file file nfile)))

(global-set-key (kbd "C-x x .") #'zge/move-from-dl)

3.7 Open Mailto Links

On SIGUSR1, Emacs checks a specified file for a mailto link, and opens it if it exists. An external script constructs this file and sends Emacs the signal. This is done to avoid having to run the Emacs server.

(defvar zge/mailto-location "/tmp/emacs.mailto"
  "Path to `zge/load-mailto' file.")

(defun zge/load-mailto ()
  (interactive)
  (when (file-exists-p zge/mailto-location)
    (with-temp-buffer
      (insert-file-contents zge/mailto-location)
      (browse-url-mail (buffer-string)))
    (delete-file zge/mailto-location)))

(define-key special-event-map [sigusr1] #'zge/load-mailto)

The shell script I have to invoke this special event is just the following:

#!/bin/sh
echo "$@" > /tmp/emacs.mailto
pkill -USR1 emacs

3.8 Change Mail Sender

Since I use two different Email addresses, and Emacs defaults to my personal one, I use this (ugly) function to allow me easy switching between the two, when a message buffer is already opened.

(defvar zge/mail-accounts
  (let* ((accounts '("cuvyvc@jnecznvy.arg"
                     "cuvyvc.xnyhqrepvp@snh.qr"))
         (ring (make-ring (length accounts))))
    (dolist (acc accounts ring)
      (ring-insert ring (rot13-string acc)))))

(defun zge/mail-toggle-account ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (save-match-data
      (re-search-forward (rx bol "From:" (*? nonl)
                             (group (* (any "-._" alnum)) ?@
                                    (* (any "-._" alnum)))
                             (*? nonl) eol))
      (replace-match (ring-next zge/mail-accounts
                                (match-string 1))
                     t t nil 1))))

(with-eval-after-load 'message
  (define-key message-mode-map (kbd "C-<return>") #'zge/mail-toggle-account))

3.9 Indent Defun

To indent a just a function, I have stolen a keybinding from Paredit. This "redefines" the regular fill-paragraph, use in code to format a comment, to indent-defun, that just selects a top-level construct and indents just it. This function does basically the same, and I bind it to all programming modes.

(defun zge/indent-defun ()
  (interactive)
  (if (nth 4 (syntax-ppss))
      (fill-paragraph)
    (save-restriction
      (narrow-to-defun)
      (indent-region-line-by-line (point-min) (point-max)))))

(define-key prog-mode-map (kbd "M-q") #'zge/indent-defun)

3.10 Dark Mode

A cheep dark mode for my configuration. Calculates and applies the RGB complements for a list of faces.

(defun zge/toggle-dark-mode ()
  (interactive)
  (dolist (face '(mode-line default))
    (zge/reverse-face face)))

(global-set-key (kbd "<f9>") #'zge/toggle-dark-mode)

The actual logic behind reverting, is done by reverse-face, a lightly altered version of invert-face, that instead of swapping fore- and background colors, replaced the fore- and background of a face with it's RGB complement.

(defun zge/reverse-face (face &optional frame)
  (interactive (list (read-face-name "Reverse face" (face-at-point t))))
  (let* ((fg (face-attribute face :foreground frame))
         (bg (face-attribute face :background frame)))
    (set-face-attribute
     face frame
     :foreground
     (color-complement-hex
      (if (eq fg 'unspecified)
          (face-attribute 'default :foreground frame)
        fg))
     :background
     (color-complement-hex
      (if (eq bg 'unspecified)
          (face-attribute 'default :background frame)
        bg))))
  face)

3.11 Empty Lines

This option makes Emacs populate the left-hand fringe with little lines indicating space the frame uses, but the buffer doesn't. This only makes sense for buffers I edit manually, like text or programs (less so in Eshell and Magit) so I enable it using a hook.

(defun zge/handle-whitespaces ()
  (setq indicate-empty-lines t))

(add-hook 'prog-mode-hook #'zge/handle-whitespaces)
(add-hook 'text-mode-hook #'zge/handle-whitespaces)
(add-hook 'dired-mode-hook #'zge/handle-whitespaces)
(add-hook 'comint-mode-hook #'zge/handle-whitespaces)

4 Global Keybindings

These are juts a few self-explanatory global, personal keybindings, I find useful. All of this is done using the dolist macro, to keep everything cleaner and easier to read.

(dolist (bind '(("M-\"" . eshell)
                ("C-(" . delete-pair)
                ("<f6>" . toggle-truncate-lines)
                ("C-c <C-return>" . man)
                ("C-x C-z" . bury-buffer)
                ("C-x M-k" . kill-buffer-and-window)
                ("C-x j" . jump-to-register)
                ("C-<" . zap-up-to-char)
                ("C->" . zap-to-char)
                ("M-F" . forward-to-word)
                ("M-B" . backward-to-word)
                ("C-M-<return>" . window-swap-states)
                ("C-c <backspace>" . delete-region)))
  (global-set-key (kbd (car bind)) (cdr bind)))

These on the other hand, overrride existing, default keybindings with different, or better (eg. M-SPC to cycle-spacing) commands.

(dolist (bind '(("M-l" . downcase-dwim)
                ("M-c" . capitalize-dwim)
                ("M-u" . upcase-dwim)
                ("M-k" . kill-paragraph)
                ("<prior>" . previous-buffer)
                ("<next>" . next-buffer)
                ("<XF86WLAN>" . ignore)
                ("<XF86Sleep>" . ignore)
                ("M-SPC" . cycle-spacing)
                ("<insert>" . open-line)
                ("C-x u" . undo-only)))
  (global-set-key (kbd (car bind)) (cdr bind)))

Author: Philip K.

Created: 18:18:19

Validate