Git hosting

dotemacs: Literate Emacs configuration

Clone

git clone https://depp.brause.cc/dotemacs.git

Files

Size Path
etc/
theme/
unpublished/
225 INSTALL
66622 TODO.org
1210 UNLICENSE
2808 init.el
132915 init.org
1721 install.el

README.org

Table of Contents

1 Preface

This is an experiment to find out whether literate configurations in a single monolithic org-mode file actually have a perceivable benefit.

I'm using this configuration on a system running Arch Linux and Emacs 27.1, that's why this configuration will be using the latest and greatest features and not check for the system it's running on. While I do use emacsclient for most of my editing needs, I occasionally open emacs instances for things like IRC or testing purposes.

To debug it, I've added a properties drawer to the following subtree that exports the code blocks to .emacs with M-x org-babel-tangle. Why .emacs? This file is kickstarted by init.el, so the other init file location was chosen.

There's some more files of interest:

Please don't use this Emacs configuration as is. I suggest you to study it instead and inspect the many variables involved with the built-in help system by hitting F1 v.

Here's a list of other literate Emacs configurations I've drawn inspiration from:

2 Init

2.1 User Interface

Emacs comes with a set of pretty wonky defaults, since the UI is what we'll see first, we'll deal with it swiftly. Part of its configuration can be set up in ~/.Xresources and has the effect to be used before any frame is displayed for the price of less flexibility. For experimentation, reload such configuration with xrdb ~/.Xresources. Keep in mind that this hack might not do anything for you on a sufficiently fast machine that loads themes faster than you can notice.

2.1.1 Superfluous UI elements

I prefer not dealing with menu bars, tool bars and scroll bars and deactivate them in ~/.Xresources:

Emacs.menuBar: off
Emacs.toolBar: off
Emacs.verticalScrollBars: off

2.1.2 Stop cursor blinking

This used to give me nightmares, especially with the 24.4 addition that made the cursor blink ten times, then stops blinking until moving it again.

Emacs.cursorBlink: off

2.1.3 Less jarring background change

The default background color chosen is white, however I'm using a dark theme. Changing the background color after frame creation results in flashing, therefore I modify the background color to equal the one of the theme I'm going to load later.

Emacs.background: #002b36

The only other UI element left that stays visible throughout the entire init time would be the mode line. It took me a bit of puzzling to figure out the right format for making it look the same as in my theme, but I eventually figured out from reading the last paragraph of the (emacs) Table of Resources info node that it does read in a list from a string by studying the sources of faces.el and xfaces.c. While this sounds kind of obvious, it means one can debug errors for more complex values like the one for the box by simply invoking read-from-string on them.

Emacs.mode-line.attributeForeground: #93a1a1
Emacs.mode-line.attributeBackground: #002b36
Emacs.mode-line.attributeBox: (:line-width 1 :color "#073642")

2.1.4 Load theme

I wrote a custom theme and want it to apply on each created frame:

(add-to-list 'custom-theme-load-path "~/code/elisp/punpun-theme")
(add-to-list 'load-path "~/code/elisp/punpun-theme")
(defun my-load-theme (&optional frame)
  (with-selected-frame (or frame (selected-frame))
    (load-theme 'punpun-light t)))
(my-load-theme)
(add-hook 'after-make-frame-functions 'my-load-theme)

Let's disable questions about theme loading while we're at it.

(setq custom-safe-themes t)

Tooltips can be themed as well.

(setq x-gtk-use-system-tooltips nil)

2.1.5 Improve the mode line

The mode line is essentially a huge nested list of mode line items you can customize to achieve your prefered look. The simplest way of getting a more useful mode line is using the smart-mode-line package which comes with more useful and color-coded items and a few interesting features like shortening file names and minor modes.

(setq sml/theme 'respectful
      sml/mode-width 'full
      sml/name-width '(0 . 20)
      sml/replacer-regexp-list
      '(("^~/org/" ":O:")
        ("^~/\\.emacs\\.d/" ":ED:")))

However I prefer explicitly whitelisting minor modes.

(setq rm-whitelist "FlyC:.*")

2.1.6 Disable advertisements

The first obvious thing one notices upon launching an uncustomized Emacs is a rather fancy splash screen that informs you about the usage and advertises for the GNU project. I did eventually grow annoyed by it.

(setq inhibit-startup-screen t)

A less obvious one is the advertisement message displayed after successful startup in the echo area. The culprit behind it is display-startup-echo-area-message and goes great lengths to make sure it's seen by first checking whether the inhibit-startup-echo-area-message has been set by the customize system to your user name, then scanning your init file with a regular expression for it. Considering I dislike using the customize system, don't have a conventional init file and find this pretty silly, I disable this behaviour entirely by redefining the function to display a bit more encouraging message instead.

(defun display-startup-echo-area-message ()
  (message "Let the hacking begin!"))

2.1.7 Adjust keystroke echo timeout

This is a built-in feature I didn't expect to be useful. If you type part of keybind, Emacs will display this part in the echo area after a timeout. One second is a bit too long though for my taste.

(setq echo-keystrokes 0.5)

2.1.8 Customize font-specific faces

I can't put these into every theme I use, so it's time to change the special user theme:

(custom-theme-set-faces 'user
                        '(default ((t :font "DejaVu Sans Mono:pixelsize=14")))
                        '(variable-pitch ((t :family "Cambria" :height 1.3))))

2.2 Emacs annoyances

Every file stating "This file is part of GNU Emacs." is more often than not a source of code that may be crufty, nausea-inducing or just having weird defaults that I need to correct.

2.2.1 Memory Management

800 KiB heap size makes for major pauses during init, so we'll temporarily increase it, then reset it after init is done to minimize GC pause times.

(setq gc-cons-threshold 10000000)

2.2.2 Fix line-number-mode

line-number-mode displays the current line number in the mode line, however it stops doing that in buffers when encountering at least one overly long line and displays two question marks instead. This is pretty unhelpful, the only workaround I've been able to find was to increase line-number-display-limit-width to a substantially higher value.

(setq line-number-display-limit-width 2000000)

See also this question on the Emacs SE.

2.2.3 GnuTLS

I have no idea why, but apparently you get nasty warnings by the GnuTLS library when using https with the default settings. Increasing the minimum prime bits size to something safer alleviates that.

Another fun thing is that it allows legacy algorithms like 3DES by default, so let's disable them as well.

(setq gnutls-min-prime-bits 2048)
(setq gnutls-algorithm-priority "SECURE128")

2.2.4 Scratch

Since the *scratch* buffer is pretty hard-wired into Emacs (see buffer.c), the least we could do is getting rid of its initial message. No, it's using its own mode instead of emacs-lisp-mode for the questionable benefit of having a function inserting evaluation values after a newline.

(setq initial-scratch-message "")
(setq initial-major-mode 'emacs-lisp-mode)

2.2.5 Initial buffer

However I don't want to see the scratch buffer, let's display our notes file instead as daily reminder what's left to do.

(setq remember-notes-bury-on-kill nil)
(setq remember-notes-initial-major-mode 'org-mode)
(setq initial-buffer-choice 'remember-notes)

There is a bit of mismatch between the keybindings of remember-notes-mode and org-mode, so let's fix that:

(with-eval-after-load 'remember
  (define-key remember-notes-mode-map (kbd "C-c C-c") nil))

2.2.6 Find C functions

There's a fair number of Emacs functions that aren't written in Emacs Lisp (see these statistics). To be able to locate them, it's necessary to grab a tarball of the sources at http://ftp.gnu.org/gnu/emacs/ and put it into your prefered location.

(setq find-function-C-source-directory "~/code/emacsen/emacs-27.1/src")

2.2.7 Shorten Yes/No prompts

Per default you're required to type out a full "yes" or "no" whenever the function yes-or-no-p is invoked, let's substitute its function definition to allow a "y" or "n" without even requiring confirmation.

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

2.2.8 Open URLs with xdg-open

I've set up xdg-open to use my prefered browser for HTTP and HTTPS URLs. Emacs claims to detect whether my system can use it, however this fails because I don't have a popular DE up and running (I kid you not, look at browse-url-can-use-xdg and how it replicates that part from the xdg-open script).

(setq browse-url-browser-function 'browse-url-xdg-open)

2.2.9 Zero out default splitting tresholds

I have no idea how this actually works, but it seems to make Emacs prefer doing a horizontal split over a vertical split on wide screens.

(setq split-height-threshold 0
      split-width-threshold 0)

2.2.10 Unique buffer names

This shouldn't be necessary since I'm already using smart-mode-line, however it's better to use a less confusing style than the default that puts brackets around the buffer names shared in Emacs.

(setq uniquify-buffer-name-style 'forward)

2.2.11 Inhibit custom littering my init file

For whatever reason the customization system will write into your init file which is especially annoying if you have it in version control like I do. It's reasonably simple to deactivate this behaviour by customizing customize into using a dedicated file, however you'll need to both delete the lines it wrote and load it later to make it aware it has already been loaded successfully.

(setq custom-file "~/.config/emacs/etc/custom.el")

2.2.12 Display .nfo files with appropriate code page

Since Emacs auto-detection of encodings is quite good, but not omniscient, we'll give it a nudge to display these files the way they're supposed to be.

(add-to-list 'auto-coding-alist '("\\.nfo\\'" . ibm437))

2.2.13 Fix scrolling

Half-page scrolling is great at reducing bandwidth, but is very jarring when done automatically. The following settings will make Emacs scroll line by line, without scrolloff and try to keep point at the same visual place when scrolling by page.

I used to have scrolloff enabled here with the scroll-margin variable, but it introduced pretty nasty scrolling behaviour for large files, so I no longer do.

(setq scroll-conservatively 10000
      scroll-preserve-screen-position t)

2.2.14 Indent with spaces by default

Most programming languages I work with prefer spaces over tabs.

Note how this is not a mode, but a buffer-local variable.

(setq-default indent-tabs-mode nil)

2.2.15 Manage Backup and autosave files

Backup files are created on save in the same directory as the file and end in ~. They can be numbered which makes most sense combined with a different save location and automatic pruning.

(setq backup-directory-alist '(("." . "~/.config/emacs/backup")))
(setq version-control t)
(setq delete-old-versions t)

Autosave files are created between saves after a sufficient timeout in the current directory for crash detection, they begin and end with #. Let's change their save location as well.

(setq auto-save-list-file-prefix "~/.config/emacs/autosave/")
(setq auto-save-file-name-transforms '((".*" "~/.config/emacs/autosave/" t)))

Keep in mind that there is nothing you can do regarding lock files except deactivating them completely (which robs you of the ability to detect session clashes). They are symlinks that are created upon modification of the file in question in its directory and are prefixed by .#. Saving the file makes them disappear (unlike autosave files).

2.2.16 Allow for multiple Emacs daemons

Although I'm pretty sure I won't make use of this, I prefer using local TCP connections over socket files. Another benefit of this setting is that it would allow me to make use of emacsclient to access a remote Emacs daemon.

(setq server-use-tcp t)

2.2.17 Stop pasting at the mouse click point

Middle-clicking is nice to paste, however it should not adjust point and paste at the then adjusted point.

(setq mouse-yank-at-point t)

2.2.18 Display buffer name in frame titles

The default is to display the invocation name and host. Changing that to use a different separator and the buffer name is trivial, however there's still an annoying space in front when using M-:. Regular expressions to the rescue!

(setq frame-title-format
      '("" invocation-name ": " (:eval (replace-regexp-in-string
                                        "^ +" "" (buffer-name)))))

2.2.19 Disable parentheses blinking on entering a match

This will be done by a different package anyways, therefore we don't need it.

(setq blink-matching-paren nil)

2.2.20 Display fringe indicators and fix line movement in visual-line-mode

It's pretty nice to have the option to display words in a buffer as if they were hardwrapped around the word boundaries, however it's confusing to not have any fringe indicators.

(setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow))

I don't like the remappings done to operate on visual lines (for C-a, C-e and C-k), so I'm just undefining them.

(setcdr visual-line-mode-map nil)

2.2.21 Enable every deactivated command

The rationale for this default seems to be to avoid confusion for beginners, I personally find it kind of annoying that Emacs of all editors does this kind of thing and doesn't offer a straightforward option to disable it even.

(setq disabled-command-function nil)

2.2.22 Save clipboard data of other programs in the kill ring when possible

I hope the necessity of this will be gone once Wayland is a viable option for me.

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

2.2.23 Make recentering behave more similiar to other programs

Try it out yourself by hitting C-l, it will start with the top instead of the middle row.

(setq recenter-positions '(top middle bottom))

2.2.24 Make kill -USR1 do something useful

A lesser known fact is that sending the USR2 signal to an Emacs process makes it proceed as soon as possible to a debug window. USR1 is ignored however, so let's bind it to an alternative desirable function that can be used on an Emacs instance that has locked up.

(defun my-quit-emacs-unconditionally ()
  (interactive)
  (my-quit-emacs '(4)))

(define-key special-event-map (kbd "<sigusr1>") 'my-quit-emacs-unconditionally)

2.2.25 Don't use dialog boxes

Clicking on an install button for instance makes Emacs spawn dialog boxes from that point on.

(setq use-dialog-box nil)

2.2.26 Don't print integers in multiple formats

I'm not sure why this is handled this way in the first place. Turns out that eval-expression-print-format is responsible and signals a non-integer by returning nil.

(fset 'eval-expression-print-format 'ignore)

2.2.27 Print backtraces in a lispier form

I wrote a patch to fix the oversight of function calls in a backtrace not being a list. The following will only work in Emacs 26:

(setq debugger-stack-frame-as-list t)

2.2.28 Display raw bytes as hex

Another patch of mine that will have effect in Emacs 26. Name says it all.

(setq display-raw-bytes-as-hex t)

2.2.29 Disable signal handler insanity

Emacs tries saving your buffers if it receives a fatal signal (including module segfaults). This is batshit insane, I prefer a clean exit over silent corruption. The following setting is supposed to make it so:

(setq attempt-stack-overflow-recovery nil)
(setq attempt-orderly-shutdown-on-fatal-signal nil)

2.2.30 comment-dwim isn't DWIM enough

All IDEs I've tried have a comment toggle command (typically mapped to C-/), however it only does operate on full lines instead of adding an in-line comment if no region is selected. While Emacs already has the comment-line command, it doesn't work correctly with Evil, so I wrote my own version of it:

(defun my-comment-dwim ()
  (interactive)
  (if (use-region-p)
      (comment-or-uncomment-region (region-beginning) (region-end))
    (comment-or-uncomment-region (line-beginning-position)
                                 (line-end-position))))

(global-set-key (kbd "M-;") 'my-comment-dwim)

2.2.31 Unconditionally kill subprocesses at exit

(setq confirm-kill-processes nil)

2.2.32 Sentence spacing

RIP double-spacing sentences.

(setq sentence-end-double-space nil)

2.3 Packages bundled with Emacs

This includes stuff that is bundled with Emacs and can be obtained from a more recent source as well, such as org-mode. I'm mostly refering to smaller packages though.

2.3.1 recentf

recentf-mode allows you to access the list of recent files which can be used by ido and helm. Let's save its file somewhere else and change the size of its history while we're at it.

(setq recentf-save-file "~/.config/emacs/etc/recentf"
      recentf-max-saved-items 50)

Editing with emacsclient records files I'd rather not have there:

(setq recentf-exclude '("^/dev/shm/.*$"))

2.3.2 savehist

The history of prompts like M-: can be saved, but let's change its save file and history length first.

(setq savehist-file "~/.config/emacs/etc/savehist"
      history-length 150)

2.3.3 save-place

I didn't expect to like this functionality, but it's pretty neat to start from the last place you were in a file the next time you visit it. Asides from putting the save file somewhere else, I have to enable this behaviour for every buffer since it's buffer-local.

(setq save-place-file "~/.config/emacs/etc/saveplace")

2.3.4 windmove

The windmove provides useful commands for moving window focus by direction, I prefer having wraparound instead of getting errors though.

(setq windmove-wrap-around t)

2.3.5 bookmark

Yet another file that I prefer being saved somewhere else.

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

2.3.6 ediff

Anything else than emacsclient spawning frames is pretty much useless for me with i3. I assume the vertical split is not done because I've customized horizontal splits to be prefered. The name of the alternative splitting function is not a mistake, what Emacs calls "horizontal" in window.el is called vertical in anything else.

(setq ediff-window-setup-function 'ediff-setup-windows-plain
      ediff-split-window-function 'split-window-horizontally)

2.3.7 dired

For the few times I'm using Dired, I prefer it not spawning an endless amount of buffers. In fact, I'd prefer it using one buffer unless another one is explicitly created, but you can't have everything.

(with-eval-after-load 'dired
  (define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file))

2.3.8 tramp

If TRAMP makes backup files, they should better be kept locally than remote.

(setq tramp-backup-directory-alist backup-directory-alist)

As usual I want to fix up the file it's storing its history in.

(with-eval-after-load 'tramp-cache
  (setq tramp-persistency-file-name "~/.config/emacs/etc/tramp"))

But to be honest, I prefer it not automatically interfering with everything. Unloading it entirely causes packages to break that assume it's enabled, therefore I'm going for its main entry point and dike it out.

(defun my-disable-tramp-file-handlers ()
  (setq file-name-handler-alist
        (--remove (string-match-p "^tramp" (symbol-name (cdr it)))
                  file-name-handler-alist)))

It discovers SSH options by launching a process which is rather slow, so customize it to the discovered value to disable auto-detection.

(setq tramp-ssh-controlmaster-options "")

2.3.9 FFAP

A great idea, but it's rather annoying in practice. At times it can happen that it pings something looking like a domain name.

(setq ffap-machine-p-known 'accept)

2.3.10 Calendar

General functionality for calendars inside Emacs, split up in a lot of files. Customizing it will affect other packages, including calfw and M-x calendar. The following customizations add German holidays (since I happen to live in Germany, d'uh).

(setq calendar-week-start-day 1)

(setq holiday-general-holidays
      '((holiday-fixed 1 1 "Neujahr")
        (holiday-fixed 5 1 "1. Mai")
        (holiday-fixed 10 3 "Tag der Deutschen Einheit")))

(setq holiday-christian-holidays
      '((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 24 "Weihnachten")
        (holiday-fixed 12 25 "1. Weihnachtstag")
        (holiday-fixed 12 26 "2. Weihnachtstag")
        (holiday-fixed 1 6 "Heilige Drei Könige")
        (holiday-easter-etc -48 "Rosenmontag")
        (holiday-easter-etc -3 "Gründonnerstag")
        (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)))

(setq holiday-oriental-holidays nil
      holiday-bahai-holidays nil
      holiday-islamic-holidays nil
      holiday-hebrew-holidays nil)

2.3.11 org-mode

First some UI and editing tweaks.

(setq org-catch-invisible-edits 'error
      org-startup-indented t
      org-cycle-include-plain-lists 'integrate
      org-return-follows-link t
      org-M-RET-may-split-line nil
      org-src-fontify-natively t
      org-src-preserve-indentation t
      org-enforce-todo-dependencies t
      org-enforce-todo-checkbox-dependencies t
      org-link-frame-setup '((file . find-file)))

I like taking notes.

(setq org-directory "~/org/"
      org-default-notes-file "~/org/notes.org"
      org-capture-templates
      '(("j" "Journal" entry (file+olp+datetree "~/org/journal.org.gpg")
         "* %<%H:%M>\n\n%?" :empty-lines 1)))

An Org patch from an upcoming release that makes datetree journal entries less annoying:

(with-eval-after-load 'org-datetree
  (defun org-datetree-insert-line (year &optional month day text)
    (delete-region (save-excursion (skip-chars-backward " \t\n") (point)) (point))
    (org-insert-heading)
    (while (> (org-current-level) 1)
      (org-do-promote))
    (when month (org-do-demote))
    (when day (org-do-demote))
    (if text
        (insert text)
      (insert (format "%d" year))
      (when month
        (insert
         (if day
             (format-time-string "-%m-%d %A" (encode-time 0 0 0 day month year))
           (format-time-string "-%m %B" (encode-time 0 0 0 1 month year))))))
    (when (and day org-datetree-add-timestamp)
      (save-excursion
        (insert "\n")
        (org-indent-line)
        (org-insert-time-stamp
         (encode-time 0 0 0 day month year)
         nil
        (eq org-datetree-add-timestamp 'inactive))))
    (beginning-of-line)))

The export functionality is very handy, but some of the stuff I like using is deactivated by default :<

(setq org-export-backends '(ascii beamer html latex md))

It's a bit tricky to color code listings and permit more flexible tables:

(setq org-latex-listings 'minted
      org-latex-packages-alist '(("" "tabu") ("" "minted") ("english" "babel"))
      org-latex-pdf-process
      '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

Using the HTML exporter I've discovered that underscores are interpreted as subscripts. Considering I only use these in LaTeX formulae, I'll just deactivate that markup completely:

(setq org-export-with-sub-superscripts '{})

When clocking in changes, the default accuracy of five minutes is a bit too fine for me. I don't know why, but Org has a customizable that adjusts this and rounding when adding clocking entries…

(setq org-time-stamp-rounding-minutes '(0 15))

Org's exporter has a rather weird way of figuring out how to open an exported file, so we'll give it a nudge. Note that since Emacs handles PDFs with docview and its mailcap implementation prefers Lisp viewers over system viewers, it ends up using docview in both cases, so we'll just tell it not to by placing the PDF entry at the top while still defining that we'd rather want to use xdg-open instead of that piece of rubbish.

(setq org-file-apps '(("pdf" . system)
                      (auto-mode . emacs)
                      (system . "xdg-open %s")
                      (t . system)))

Org changed a default about clocking and drawers which has the annoying side effect of always putting clocks into a drawer and surrounding the existing clocks with one if necessary. This can take a significant amount of time if there are many clocking entries present.

(setq org-clock-into-drawer nil)

2.3.12 comint

Here comes another particularly interesting Emacs package. It allows one to define major modes interacting with a REPL-style process. In other words, it gives you all kinds of shell and interpreter interaction with common keybindings, be it for SQL, your favourite programming language or your shell. Even Emacs itself can be used, try out M-x ielm.

It's trivial to clear the entire comint buffer by temporarily binding comint-buffer-maximum-size to zero and calling comint-truncate-buffer, however that's not what I really want. Usually it's just the output of the last expression that's been faulty and needs to be cleared by replacing it with a comment. The idea itself is taken from CIDER.

(defun my-comint-last-output-beg ()
  (save-excursion
    (comint-goto-process-mark)
    (while (not (or (eq (get-char-property (point) 'field) 'boundary)
                    (= (point) (point-min))))
      (goto-char (previous-char-property-change (point) (point-min))))
    (if (= (point) (point-min))
        (point)
      (1+ (point)))))

(defun my-comint-last-output-end ()
  (save-excursion
    (comint-goto-process-mark)
    (while (not (or (eq (get-char-property (point) 'font-lock-face)
                        'comint-highlight-prompt)
                    (= (point) (point-min))))
      (goto-char (previous-char-property-change (point) (point-min))))
    (let ((overlay (car (overlays-at (point)))))
      (when (and overlay (eq (overlay-get overlay 'font-lock-face)
                             'comint-highlight-prompt))
        (goto-char (overlay-start overlay))))
    (1- (point))))

(defun my-comint-clear-last-output ()
  (interactive)
  (let ((start (my-comint-last-output-beg))
        (end (my-comint-last-output-end)))
    (let ((inhibit-read-only t))
      (delete-region start end)
      (save-excursion
        (goto-char start)
        (insert (propertize "output cleared"
                            'font-lock-face 'font-lock-comment-face))))))

Killed comint processes tend to leave an useless buffer around. Let's kill it after noticing such an event with a process sentinel.

(defun my-shell-kill-buffer-sentinel (process event)
  (when (and (memq (process-status process) '(exit signal))
             (buffer-live-p (process-buffer process)))
    (kill-buffer)))

(defun my-kill-process-buffer-on-exit ()
  (set-process-sentinel (get-buffer-process (current-buffer))
                        #'my-shell-kill-buffer-sentinel))

(dolist (hook '(ielm-mode-hook term-exec-hook comint-exec-hook))
  (add-hook hook 'my-kill-process-buffer-on-exit))

Recentering feels a bit unintuitive since it goes by the middle first. I only need top and bottom commands, for that I'll define my own command and bind it later.

(defun my-recenter-top-bottom ()
  (interactive)
  (goto-char (point-max))
  (let ((recenter-positions '(top bottom)))
    (recenter-top-bottom)))

Another thing annoying me in comint buffers is that when text is read-only, both cursor movement and appending to kill ring still happen. This is less useful since if you keep holding the keys to delete words, you end up traversing the entire buffer instead of stopping at the read-only boundaries and pollute the kill ring. To remedy that I'll write my own word killing commands in the typical Emacs user fashion, however I'll not advise the built-ins since who knows what might possibly be relying on this default behaviour.

(defun my-kill-word (arg)
  (interactive "p")
  (unless buffer-read-only
    (let ((beg (point))
          (end (save-excursion (forward-word arg) (point)))
          (point (save-excursion (goto-char
                                  (if (> arg 0)
                                      (next-single-char-property-change
                                       (point) 'read-only)
                                    (previous-single-char-property-change
                                     (point) 'read-only)))
                                 (point))))
      (unless (get-char-property (point) 'read-only)
        (if (if (> arg 0) (< point end) (> point end))
            (kill-region beg point)
          (kill-region beg end))))))

(defun my-backward-kill-word (arg)
  (interactive "p")
  (my-kill-word (- arg)))

The new functionality introduced has to be bound to keys for convenient use. Note the remapping of commands.

(with-eval-after-load 'comint
  (define-key comint-mode-map (kbd "<remap> <kill-word>") 'my-kill-word)
  (define-key comint-mode-map (kbd "<remap> <backward-kill-word>") 'my-backward-kill-word)
  (define-key comint-mode-map (kbd "C-S-l") 'my-comint-clear-last-output)
  (define-key comint-mode-map (kbd "C-l") 'my-recenter-top-bottom))

2.3.13 shell

For unknown reasons I get my input echoed back to me. In other words, sending ls to shell echoes my input twice, then the output. comint has a setting that can filter these echoes.

(defun my-shell-turn-echo-off ()
  (setq comint-process-echoes t))

(add-hook 'shell-mode-hook 'my-shell-turn-echo-off)

2.3.14 eshell

I want C-d to not unconditionally delete the character, but to quit on an empty prompt, too.

(defun my-eshell-quit-or-delete-char (arg)
  (interactive "p")
  (if (and (eolp) (looking-back eshell-prompt-regexp))
      (eshell-life-is-too-much) ;; http://emacshorrors.com/post/life-is-too-much
    (delete-forward-char arg)))

(defun my-eshell-setup ()
  (define-key eshell-mode-map (kbd "C-d") 'my-eshell-quit-or-delete-char))

(add-hook 'eshell-mode-hook 'my-eshell-setup)

For silly reasons I like having a rainbow-colored prompt.

(add-hook 'eshell-load-hook 'nyan-prompt-enable)

2.3.15 CC-Mode

In their ingenuity the Emacs developers decided to make the GNU style the default style for C code written with it. While this is a decision that helps making contribution to GNU projects still adhering to this style (including Emacs itself) a fair bit easier, I'd hate using it for anything else. I don't know my exact preferences yet, but for the time being the "user" style is good enough and can still be customized into something more sophisticated.

(setq c-default-style '((java-mode . "java")
                        (awk-mode . "awk")
                        (c-mode . "user")
                        (c++-mode . "user")))

Electric indentation of things like opening braces on their own line is linked to electric-indent-mode, but that can be worked around easily:

(setq-default c-electric-flag t)

Debugging indentation issues is hard enough as it is, this setting echoes what construct you've just indented.

;; (setq c-echo-syntactic-information-p t)

Backspace can untabify tabs which is yucky. Hungry delete is overkill, so I'll just revert to normal behavior there:

(setq c-backspace-function 'delete-backward-char)

Smartparens screws up the handling of single quote literals and escapes them for some reason. Let's work around this by disabling its delimiter escaping in cc-mode:

(defun my-disable-sp-escape-quotes ()
  (setq-local sp-escape-quotes-after-insert nil))
(add-hook 'c-mode-common-hook 'my-disable-sp-escape-quotes)

2.3.16 eldoc-mode

The default idle delay is way too long. Also, avoid displaying overly long function signatures.

(setq eldoc-idle-delay 0.1
      eldoc-echo-area-use-multiline-p nil)

2.3.17 Emacs Lisp

Cask files are just Emacs Lisp.

(add-to-list 'auto-mode-alist '("Cask\\'" . emacs-lisp-mode))

Additionally to the F1 keybindings I'd like to have two extra keybinds for evaluation and a REPL.

(defun my-eval-region-or-buffer ()
  (interactive)
  (if (region-active-p)
      (eval-region (region-beginning) (region-end))
    (eval-buffer)))

(with-eval-after-load 'lisp-mode
  (define-key emacs-lisp-mode-map (kbd "C-c C-c") 'my-eval-region-or-buffer)
  (define-key emacs-lisp-mode-map (kbd "C-c C-z") 'ielm))

eldoc is a nice helper to avoid looking up function signatures in function documentation.

(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'ielm-mode-hook 'turn-on-eldoc-mode)

I quite like CIDER-style display of evaluation results, so I'm glad there's a package providing this for Emacs Lisp code.

(add-hook 'emacs-lisp-mode-hook 'eros-mode)

Experiment: My biggest gripe with writing Emacs Lisp is having to type out a package prefix every time. This hack will lessen the pain by only having to hit C-. for inserting the prefix at point. The prefix is guessed if not set by searching for a (defgroup <prefix> ...) form, but can be set manually as well by using the prefix argument.

(defvar-local my-current-package-prefix nil)

(defun my-ensure-trailing-dash (string)
  (if (and (not (zerop (length string)))
           (not (= (aref string (1- (length string))) ?-)))
      (concat string "-")
    string))

(defun my-guess-current-package-prefix (arg)
  (save-excursion
    (goto-char (point-min))
    (if (and (not arg)
             (re-search-forward "^(defgroup " nil t))
        (let ((form (read (thing-at-point 'defun)))
              rest)
          (if (setq rest (memq :prefix form))
              (setq my-current-package-prefix (cadr rest))
            (setq my-current-package-prefix (format "%s-" (cadr form)))))
      (setq my-current-package-prefix
            (my-ensure-trailing-dash
             (read-string "Package prefix: "
                          my-current-package-prefix))))))

(defun my-insert-current-package-prefix (arg)
  (interactive "P")
  (when (or (not my-current-package-prefix) arg)
    (my-guess-current-package-prefix arg))
  (insert my-current-package-prefix))

(with-eval-after-load 'elisp-mode
  (define-key emacs-lisp-mode-map (kbd "C-.")
              'my-insert-current-package-prefix))

2.3.18 Scheme

I like CHICKEN.

(setq scheme-program-name "csi")
(add-to-list 'interpreter-mode-alist '("chicken-scheme" . scheme-mode))

To avoid typing M-x run-scheme, I define another useful keybinding.

(with-eval-after-load 'scheme
  (define-key scheme-mode-map (kbd "C-c C-z") 'run-scheme))

The binding is replaced though after launching the REPL, I should eventually fix this. Perhaps with my very own major mode.

I wrote an integration for chicken-doc:

(with-eval-after-load 'scheme
  (define-key scheme-mode-map (kbd "C-c C-d") 'chicken-doc-describe))

Indentation hints fortunately seem to work for other languages than Emacs Lisp.

(put 'match 'scheme-indent-function 1)
(put 'match-let 'scheme-indent-function 1)
(put 'match-let* 'scheme-indent-function 1)
(put 'when 'scheme-indent-function 1)
(put 'and-let* 'scheme-indent-function 1)
(put 'if-let 'scheme-indent-function 1)
(put 'let-location 'scheme-indent-function 1)
(put 'select 'scheme-indent-function 1)
(put 'bitmatch 'scheme-indent-function 1)
(put 'bitpacket 'scheme-indent-function 1)
(put 'with-transaction 'scheme-indent-function 1)
(put 'foreign-lambda* 'scheme-indent-function 2)
(put 'test-group 'scheme-indent-function 1)
(put 'call-with-database 'scheme-indent-function 1)
(put 'call-with-input-string 'scheme-indent-function 1)
(put 'parameterize 'scheme-indent-function 1)
(put 'hash-table-for-each 'scheme-indent-function 1)
(put 'module 'scheme-indent-function (lambda (&rest args) 0))

There's a few schemey file formats I'd like to automatically recognize:

(add-to-list 'auto-mode-alist '("\\.sld\\'" . scheme-mode))
(add-to-list 'auto-mode-alist '("\\.sxml\\'" . scheme-mode))
(add-to-list 'auto-mode-alist '("\\.scss\\'" . scheme-mode))
(add-to-list 'auto-mode-alist '("\\.setup\\'" . scheme-mode))
(add-to-list 'auto-mode-alist '("\\.meta\\'" . scheme-mode))
(add-to-list 'auto-mode-alist '("\\.release-info\\'" . scheme-mode))
(add-to-list 'auto-mode-alist '("\\.egg\\'" . scheme-mode))

CHICKEN has special syntax for embedded C code and heredocs that messes up syntax highlighting:

(with-eval-after-load 'scheme
  (defun my-scheme-region-extend-function ()
    (when (not (get-text-property (point) 'font-lock-multiline))
      (let* ((heredoc nil)
             (new-beg
              (save-excursion
                (when (and (re-search-backward "#>\\|<#\\|#<[<#]\\(.*\\)$" nil t)
                           (not (get-text-property (point) 'font-lock-multiline)))
                  (let ((match (match-string 0))
                        (tag (match-string 1)))
                    (cond
                     ((equal match "#>") (point))
                     ((string-match-p "^#<[<#]" match) (setq heredoc tag) (point)))))))
             (new-end
              (save-excursion
                (if heredoc
                    (when (and (re-search-forward (concat "^" (regexp-quote heredoc) "$") nil t)
                               (not (get-text-property (point) 'font-lock-multiline)))
                      (point))
                  (when (and (re-search-forward "#>\\|<#" nil t)
                             (not (get-text-property (point) 'font-lock-multiline))
                             (equal (match-string 0) "<#"))
                    (point))))))
        (when (and new-beg new-end)
          (setq font-lock-beg new-beg)
          (setq font-lock-end new-end)
          (with-silent-modifications
            (put-text-property new-beg new-end 'font-lock-multiline t))
          (cons new-beg new-end)))))

  (defun my-scheme-syntax-propertize-foreign (_ end)
    (save-match-data
      (when (search-forward "<#" end t)
        (with-silent-modifications
          (put-text-property (1- (point)) (point)
                             'syntax-table (string-to-syntax "> cn"))))))

  (defun my-scheme-syntax-propertize-heredoc (_ end)
    (save-match-data
      (let ((tag (match-string 2)))
        (when (and tag (re-search-forward (concat "^" (regexp-quote tag) "$") nil t))
          (with-silent-modifications
            (put-text-property (1- (point)) (point)
                               'syntax-table (string-to-syntax "> cn")))))))

  (defun scheme-syntax-propertize (beg end)
    (goto-char beg)
    (scheme-syntax-propertize-sexp-comment (point) end)
    (funcall
     (syntax-propertize-rules
      ("\\(#\\);"
       (1 (prog1 "< cn" (scheme-syntax-propertize-sexp-comment (point) end))))
      ("\\(#\\)>"
       (1 (prog1 "< cn" (my-scheme-syntax-propertize-foreign (point) end))))
      ("\\(#\\)<[<#]\\(.*\\)$"
       (1 (prog1 "< cn" (my-scheme-syntax-propertize-heredoc (point) end)))))
     (point) end)))

(defun my-scheme-mode-setup ()
  (setq font-lock-extend-region-functions
        (cons 'my-scheme-region-extend-function
              font-lock-extend-region-functions)))

(add-hook 'scheme-mode-hook 'my-scheme-mode-setup)

2.3.19 Common Lisp

I like SBCL.

(setq inferior-lisp-program "/usr/bin/sbcl")

2.3.20 NXML

Let's automatically complete closing tags.

(setq nxml-slash-auto-complete-flag t)

2.3.21 CSS

Indentation could be a bit more narrow.

(setq css-indent-offset 2)

2.3.22 Python

Emacs is not aware of version-dependent shebangs.

(add-to-list 'interpreter-mode-alist '("python2" . python-mode))
(add-to-list 'interpreter-mode-alist '("python3" . python-mode))

For some reason guessing the indentation offset is on by default although nearly all Python code I've worked with did use 4 spaces. I wouldn't even care weren't it for the message displayed after it's done.

(setq python-indent-guess-indent-offset nil)

2.3.23 Etags

I've created a TAGS file for finding the definitions to the C sources quickly. To avoid prompting for its name, one can customize the following:

(setq tags-file-name "TAGS")

2.3.24 Info

Make copying use the lispy syntax by default and with a normal syntax argument copy the HTML link.

(defun my-info-copy-current-node-name (arg)
  "Copy the lispy form of the current node.
With a prefix argument, copy the link to the online manual
instead."
  (interactive "P")
  (let* ((manual (file-name-sans-extension
                  (file-name-nondirectory Info-current-file)))
         (node Info-current-node)
         (link (if (not arg)
                   (format "(info \"(%s) %s\")" manual node)
                 ;; NOTE this will only work with emacs-related nodes...
                 (format "https://www.gnu.org/software/emacs/manual/html_node/%s/%s.html"
                         manual (if (string= node "Top")
                                    "index"
                                  (replace-regexp-in-string " " "-" node))))))
    (kill-new link)
    (message link)))

(with-eval-after-load 'info
  (define-key Info-mode-map (kbd "c") 'my-info-copy-current-node-name))

2.3.25 nroff

I'll just pretend that mdoc is the same as nroff:

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

2.3.26 sql-mode

I'm using PostgreSQL most of the time:

(setq sql-product 'postgres)

2.3.27 VC

I rarely if ever do non-Git projects and if I do, I dislike the potentially very long VC indicator in the modeline. It looks as if VC is enabled by merely requiring the feature, so I'll have to disable it differently:

(setq vc-handled-backends nil)

2.3.28 JS

The whole world appears to be using two-space indentation:

(setq js-indent-level 2)

Some people use weird extensions for Javascript files:

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

2.3.29 shell-script-mode

I don't see why using zsh as my shell means I wouldn't want to write bash scripts.

(setq sh-shell-file "/bin/bash")

PKGBUILDs are shell scripts, really:

(add-to-list 'auto-mode-alist '("PKGBUILD\\'" . shell-script-mode))

2.3.30 Edebug

Evaluating expressions in Edebug gives you no completion whatsoever. https://emacs.stackexchange.com/questions/39353/completion-in-edebug-eval-expression shows how to do better.

(defun my-edebug-eval-expression-completion (expr)
  "Replace interactive-spec of `edebug-eval-expression'
with `read--expression' from `eval-expression' to get completion working."
  (interactive (list (read--expression "Eval: "))))

(advice-add 'edebug-eval-expression :before 'my-edebug-eval-expression-completion)

2.3.31 Sieve

Neat to have, but it's annoying to do auth over and over again.

(defun my-sieve-manage ()
  (interactive)
  (let ((host "imap.mailbox.org")
        (user "mail@vasilij.de")
        (secret (lambda () (car (process-lines "pass" "mail/mailbox.org")))))
    (password-cache-add
     `(auth-source :host ,host :port "sieve" :max 1 :create t)
     `((:host ,host :user ,user :secret ,secret)))
    (sieve-manage "imap.mailbox.org")))

2.3.32 yow

(autoload 'yow "yow")
(autoload 'psychoanalyze-pinhead "yow")
(setq yow-file "~/.config/emacs/etc/yow.lines")

2.3.33 rst-mode

One convenience command for updating the date in my blog posts:

(defun my-update-blog-timestamp ()
  (interactive)
  (save-excursion
    (save-restriction
      (goto-char (point-min))
      (when (char-equal (char-after) ?\()
        (narrow-to-region (point-min) (save-excursion (forward-sexp) (point)))
        (when (or (re-search-forward "updated \\. \"\\([^\"]+\\)\"" nil t)
                  (re-search-forward "date \\. \"\\([^\"]+\\)\"" nil t))
          (replace-match (format-time-string "%F %T %z") nil t nil 1))))))

(with-eval-after-load 'rst
  (define-key rst-mode-map (kbd "C-c u") 'my-update-blog-timestamp))

2.4 Packages outside Emacs

Welcome to the blind spot of emacs-devel. Unlike the people on there, I'll not pretend external packages are something to speak of in hushed tones.

2.4.1 smex

Nice improvement over vanilla M-x that gives you persistency and better matching. Let's give it more history and a different file.

(setq smex-save-file (concat user-emacs-directory "etc/smex")
      smex-history-length 50)

2.4.2 CSV

After installing csv-mode from GNU ELPA, I found out it's using a :set form in its customization option for the separators, therefore I had to figure out what "internal" variables they were setting and customized them.

(setq csv-separators '(";" "    " ",")
      csv-separator-chars '(?\; ?        ?,)
      csv--skip-regexp "^
;       ,"
      csv-separator-regexp "[;  ,]"
      csv-font-lock-keywords '(("[;     ,]" (0 'csv-separator-face))))

2.4.3 Quelpa

A client-side MELPA. Hugely useful for development, also useful to obtain packages that are not there or need to be built differently from what it offers. vim-plug comes close, but the closest equivalent to it would be the makepkg utility.

This customization is necessary to have updates of packages happen, even if they already exist.

(setq quelpa-upgrade-p t)

2.4.4 shackle

Declarative popup window rules.

(setq shackle-rules
      '(((svg-2048-mode circe-query-mode clojure-mode) :same t)
        ("*Help*" :align t :select t)
        ("\\`\\*helm.*?\\*\\'" :regexp t :align t)
        ((compilation-mode "\\`\\*firestarter\\*\\'"
          "\\`magit-diff:") :regexp t :noselect t)
        ((inferior-scheme-mode inferior-lisp-mode "*shell*" "*eshell*") :popup t))
       shackle-default-rule '(:select t)
       shackle-default-size 0.4
       shackle-inhibit-window-quit-on-same-windows t)

2.4.5 eyebrowse

Less clumsy management of window configurations.

Switch back and forth just like my i3wm configuration, wrap around, too.

(setq eyebrowse-switch-back-and-forth t
      eyebrowse-wrap-around t)

2.4.6 company-mode

The best auto-completion mode we have out there.

The following sets up a good amount of UI tweaks and everything necessary for the global backends.

(setq company-idle-delay 0.1
      company-minimum-prefix-length 2
      company-selection-wrap-around t
      company-show-numbers t
      company-require-match 'never
      company-dabbrev-downcase nil
      company-dabbrev-ignore-case t
      company-dabbrev-other-buffers nil
      company-backends '(company-nxml company-css
                         company-capf
                         company-dabbrev-code
                         company-files
                         company-dabbrev))

Sometimes it's useful to narrow down the candidate list if it's overly long with something better than C-s.

(with-eval-after-load 'company
  (define-key company-active-map (kbd "C-:") 'helm-company))

Hitting ESC does exit Evil's insert state (which is where I'm usually in when typing completable text), but still keeps the popup open. A similiar problem applies to the candidate search, so here's a workaround for both:

(with-eval-after-load 'company
  (define-key company-search-map (kbd "<escape>") 'company-search-abort))

I've transitioned from auto-complete-mode so I'm missing its selection behaviour. Company is not quite there yet, but company-tng-configure-defaul helps. It needs to be called after this, but before Company is enabled, so I've deferred it to the end of this init file.

2.4.7 dash

Very useful library, too bad I don't know how to properly use it yet. Since it's sprinkled all over in code I'd like to have extra syntax highlighting for it.

(with-eval-after-load 'dash
  (dash-enable-font-lock))

2.4.8 helm

A polarizing package to say the least. The good part of it is that it actually tries enabling abstractions over complex selection UI. The bad part is that it's overly complex, hard to debug and prone to bizarre behaviour. I've handed in ten bugs for it already and don't expect those to be the last. With that being said I find it essential to quickly find your way through Emacs, I just wish it were less idiosyncratic and with developer documentation.

  1. Navigation

    The default navigation isn't as fast as it could be. Automatically switching directories is a must for me. Note the hack with helm-ff--auto-update-state, it's supposedly internal, but only set after using helm-find-files which essentially means that everything using the file selector won't get the auto-switching goodies unless a file has been found before. With this hack however it will. The other hack goes beyond the helm-ff-ido-style-backspace customization and unconditionally enables backspace going up one level in both kinds of file selectors.

    (setq helm-ff-ido-style-backspace 'always
          helm-ff-auto-update-initial-value t
          helm-ff--auto-update-state t)
    
    (with-eval-after-load 'helm-files
      (define-key helm-read-file-map (kbd "<backspace>") 'helm-find-files-up-one-level)
      (define-key helm-find-files-map (kbd "<backspace>") 'helm-find-files-up-one-level))
    

    There are more idiosyncracies to be resolved with file selection. I don't want to see boring files and not get prompted for creating a new file either. The creation of a new directory however is kept as is.

    (setq helm-ff-newfile-prompt-p nil
          helm-ff-skip-boring-files t)
    
  2. Search

    grep is very fast, but not the best tool for code search, especially not within compressed files. That's why I'll go for ag instead, its -z option enables the usage of the very great libarchive. For helm to recognize the matches properly I need to enable line numbers and columns in its output, something the --vimgrep= option (the irony) does. Another subtle hack hidden in here is deliberately using the recursing variant for both types of searches, this might break something, but so far hasn't shown any obvious side-effects.

    (setq helm-grep-default-command "ag --vimgrep -z %p %f"
          helm-grep-default-recurse-command "ag --vimgrep -z %p %f")
    

    Helm breaks API for no reason, so here's my version of helm-do-grep, with addition of a nasty hack to pretend this is grep so that entering spaces in the query works:

    (defun helm-do-grep ()
      "Preconfigured helm for grep.
    Contrarily to Emacs `grep', no default directory is given, but
    the full path of candidates in ONLY.
    That allow to grep different files not only in `default-directory' but anywhere
    by marking them (C-<SPACE>). If one or more directory is selected
    grep will search in all files of these directories.
    You can also use wildcard in the base name of candidate.
    If a prefix arg is given use the -r option of grep (recurse).
    The prefix arg can be passed before or after start file selection.
    See also `helm-do-grep-1'."
      (interactive)
      (require 'helm-mode)
      (let* ((preselection (buffer-file-name (current-buffer)))
             (only (helm-read-file-name "Search in file(s): "
                                        :marked-candidates t))
             (prefarg (or current-prefix-arg helm-current-prefix-arg)))
        (cl-letf (((symbol-function 'helm-grep-command)
                   (lambda (&rest args) "grep")))
          (helm-do-grep-1 only prefarg))))
    

    Here's two commands for pretty common queries, one going through the official Emacs Lisp sources, the other through the C parts:

    (defun my-grep-emacs-elisp ()
      (interactive)
      (cl-letf (((symbol-function 'helm-grep-command)
                 (lambda (&rest args) "grep")))
        (helm-do-grep-1 '("/usr/share/emacs/*/lisp/*.el.gz"
                          "/usr/share/emacs/*/lisp/*/*.el.gz"))))
    
    (defun my-grep-emacs-C ()
      (interactive)
      (cl-letf (((symbol-function 'helm-grep-command)
                 (lambda (&rest args) "grep")))
        (helm-do-grep-1
         (list (expand-file-name "*.c" find-function-C-source-directory)
               (expand-file-name "*.h" find-function-C-source-directory)))))
    

    Integration for comby's search:

    (defun my-comby-command (query dir ext &optional ext-override)
      (concat (format "comby %s '' %s -d %s -o"
                      (shell-quote-argument query)
                      (shell-quote-argument ext)
                      (shell-quote-argument (expand-file-name dir)))
              (if ext-override
                  (format " -m %s" (shell-quote-argument ext-override))
                "")))
    
    (defun my-comby-search-C (query dir)
      (compilation-start (my-comby-command query dir ".c") 'grep-mode))
    
    (defun my-comby-search-elisp (query dir)
      (compilation-start (my-comby-command query dir ".el" ".lisp") 'grep-mode))
    
    (defun my-comby-emacs-C (query)
      (interactive "sQuery: ")
      (my-comby-search-C "~/code/emacsen/emacs-git/src"))
    
    (defun my-comby-emacs-lisp (query)
      (interactive "sQuery: ")
      (my-comby-search-elisp query "~/code/emacsen/emacs-git/lisp"))
    
    (defun my-comby-elpa (query)
      (interactive "sQuery: ")
      (my-comby-search-elisp "~/.config/emacs/elpa"))
    
  3. completing-read behaviour

    I dislike helm taking over tab-completion in my IRC client.

    (setq helm-mode-no-completion-in-region-in-modes
          '(circe-channel-mode
            circe-query-mode
            circe-server-mode))
    
  4. Other

    Highlighting of token matches is a tad slow, let's speed it up.

    (setq helm-mp-highlight-delay 0.3)
    

    I like having my dotfiles repo as default when using helm-cmd-t on a directory that's not under version-control.

    (setq helm-cmd-t-default-repo "~/code/dotfiles")
    

    I don't know why, but helm tries doing window management. Please stop:

    (setq helm-display-function 'pop-to-buffer)
    

    This may or may not avoid a memory leak:

    (setq helm-ff-keep-cached-candidates nil)
    (with-eval-after-load 'helm-files
      (helm-ff-cache-mode -1))
    
  5. Custom commands
    (with-eval-after-load 'helm
      (defun my-helm-rdictcc ()
        (interactive)
        (helm :sources 'my-helm-rdictcc-source
              :buffer "*helm rdictcc*"))
    
      (defvar my-helm-rdictcc-source
        (helm-build-async-source "rdictcc"
          :candidates-process 'my-helm-rdictcc-process
          :candidate-number-limit 99
          :filtered-candidate-transformer 'my-helm-rdictcc-transformer
          :requires-pattern 3))
    
      (defun my-helm-rdictcc-process ()
        (let ((proc (start-process "rdictcc" helm-buffer "rdictcc" "-c" helm-pattern)))
          (set-process-sentinel
           proc
           (lambda (process event)
             (helm-process-deferred-sentinel-hook process event default-directory)))
          proc))
    
      (defun my-helm-rdictcc-transformer (candidates _source)
        (let (result)
          (dolist (candidate candidates)
            (when (string-match-p "=\\{20\\}\\[ [AB] => [AB] \\]=\\{20\\}" candidate)
              (add-face-text-property 0 (length candidate) 'font-lock-comment-face
                                      nil candidate))
            (push candidate result))
          (nreverse result))))
    

2.4.9 flycheck

There's a few languages I like having linting for, see 2.6. Additionally to that there's few things to tweak. For one I prever the tex-lacheck linter over the default tex-chktex linter and don't want to use the emacs-lisp-checkdoc one at all, another thing is that I don't want linting to start on an idle timer, but rather on opening the buffer and saving it to disk.

(setq flycheck-disabled-checkers '(tex-chktex emacs-lisp-checkdoc)
      flycheck-check-syntax-automatically '(mode-enabled save))

For whatever reason the emacs-lisp checker stopped unconditionally initializing packages before doing the check, the following avoids errors for dependencies in packages I write:

(setq flycheck-emacs-lisp-initialize-packages t)

Automatic linting for Emacs Lisp files is nice, but doesn't make any sense for non-packages. For simplicity's sake, I define everything in a few specific directories to be a package:

(defun my-enable-flycheck-for-elisp-packages ()
  (when (and buffer-file-name (string-match-p "/home/wasa/code/elisp/" buffer-file-name))
    (flycheck-mode)))

(add-hook 'emacs-lisp-mode-hook 'my-enable-flycheck-for-elisp-packages)

2.4.10 Hydra

Not sure how to describe it. A library for defining key-centric interfaces? You use it to execute commands with single-key presses first and foremost, I have only come to define repetition-free ones.

  1. Define utility functions
    (defun my-zsh ()
      (interactive)
      (ansi-term "zsh"))
    
    (defun my-info-emacs-lisp-intro ()
      (interactive)
      (info "eintr"))
    
    (defun my-info-emacs-lisp-manual ()
      (interactive)
      (info "elisp"))
    
    (defun my-info-cl ()
      (interactive)
      (info "cl"))
    
    (defun my-info-cl-loop ()
      (interactive)
      (info "(cl) Loop facility"))
    
    (defun my-capture-journal ()
      (interactive)
      (org-capture nil "j"))
    
    (defun my-open-journal ()
      (interactive)
      (find-file "~/org/journal.org.gpg"))
    
    (defun my-open-tracking ()
      (interactive)
      (find-file "~/org/tracking.org"))
    
    (autoload 'cfw:open-org-calendar "calfw-org" "Open Org calendar" t)
    
  2. Define setup function

    This is used in after-init-hook.

    (defun my-setup-hydra ()
      (global-set-key
       (kbd "<f1>")
       (defhydra hydra-help (:color blue)
         "Help"
         ("a" helm-apropos "Apropos")
         ("c" describe-char "Describe Char")
         ("f" find-function "Find Function")
         ("F" describe-function "Describe Function")
         ("k" describe-key "Describe Key")
         ("K" find-function-on-key "Find Key")
         ("m" describe-mode "Describe Modes")
         ("t" list-timers "List timers")
         ("v" find-variable "Find Variable")
         ("V" describe-variable "Describe Variable")))
    
      (global-set-key
       (kbd "<f2>")
       (defhydra hydra-packages (:color blue)
         "Packages"
         ("c" helm-colors "Colors")
         ("f" find-library "Find Library")
         ("g" customize-group "Customize Group")
         ("p" list-packages "Package List")
         ("q" quelpa "Quelpa")))
    
      (global-set-key
       (kbd "<f3>")
       (defhydra hydra-search (:color blue)
         "Search"
         ("e" my-grep-emacs-elisp "Grep Emacs Elisp")
         ("E" my-grep-emacs-C "Grep Emacs C")
         ("g" helm-do-grep "Grep")
         ("i" helm-imenu "Imenu")
         ("o" helm-occur "Occur")))
    
      (global-set-key
       (kbd "<f4>")
       (defhydra hydra-find (:color blue)
         "Find"
         ("f" helm-find "Find")
         ("l" helm-locate "Locate")
         ("t" helm-cmd-t "Cmd-T")))
    
      (global-set-key
       (kbd "<f5>")
       (defhydra hydra-eval (:color blue)
         "Eval"
         ("c" calc "Calc")
         ("e" eshell "Eshell")
         ("g" magit-status "Magit")
         ("i" ielm "IELM")
         ("s" shell "Shell")
         ("t" my-zsh "Term")))
    
      (global-set-key
       (kbd "<f6>")
       (defhydra hydra-doc (:color blue)
         "Doc"
         ("e" info-emacs-manual "Emacs manual")
         ("i" info "Info")
         ("l" my-info-emacs-lisp-manual "Emacs Lisp manual")))
    
      (global-set-key
       (kbd "<f7>")
       (defhydra hydra-insert (:color blue)
         "insert"
         ("u" insert-char "Unicode input")))
    
      (global-set-key
       (kbd "<f8>")
       (defhydra hydra-misc (:color blue)
         "Misc"
         ("g" helm-google-suggest "Google Suggest")
         ("p" helm-list-emacs-process "Emacs Process List")
         ("t" helm-top "Top")))
    
      (global-set-key
       (kbd "<f9>")
       (defhydra hydra-distractions (:color blue)
         "Distractions"
         ("i" my-irc "IRC")
         ("s" my-sieve-manage "Sieve")))
    
      (global-set-key
       (kbd "<f11>")
       (defhydra hydra-capture (:color blue)
         "Org Capture"
         ("j" my-capture-journal "Journal")))
    
      (global-set-key
       (kbd "<f12>")
       (defhydra hydra-lookup (:color blue)
         "Org Lookup"
         ("c" cfw:open-org-calendar "Calendar")
         ("j" my-open-journal "Journal")
         ("t" my-open-tracking "Tracking"))))
    

2.4.11 CIDER

Clojure Interactive Development Environment that Rocks.

Please don't display a splash screen in my REPL:

(setq cider-repl-display-help-banner nil)

I like eldoc for function signatures in both code and REPL buffers.

(add-hook 'cider-repl-mode-hook 'eldoc-mode)
(add-hook 'clojure-mode-hook 'eldoc-mode)

The REPL binds a key for clearing the last evaluated output, but I prefer nuking all output (which is unbound for some reason):

(with-eval-after-load 'cider-repl
  (define-key cider-repl-mode-map (kbd "C-c C-k") 'cider-repl-clear-buffer))

Joker is a light-weight Clojure implementation that can be used as linter for Clojure and ClojureScript files.

(defun my-flycheck-joker-setup ()
  (require 'flycheck-joker))
(add-hook 'clojure-mode-hook 'my-flycheck-joker-setup)
(add-hook 'clojurescript-mode-hook 'my-flycheck-joker-setup)

2.4.12 web-mode

Deals with all kinds of templates and other files with multiple modes one encounters in web development. Other than templates, I prefer using it for HTML these days.

(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.tm?pl\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))

(setq web-mode-markup-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2)

2.4.13 company-tern

Tern is kind of cool. Don't forget installing it via npm, then adding a .tern-project file to your project root.

(add-hook 'js-mode-hook 'tern-mode)
(add-to-list 'company-backends 'company-tern)

2.4.14 enh-ruby-mode

I'm using this mode for everything the stock ruby-mode would be used for because it provides better syntax highlighting and indentation by using an external process.

(add-to-list 'auto-mode-alist '("\\.rb\\'" . enh-ruby-mode))
(add-to-list 'auto-mode-alist '("Gemfile\\'" . enh-ruby-mode))
(add-to-list 'auto-mode-alist '("Rakefile\\'" . enh-ruby-mode))
(add-to-list 'auto-mode-alist '("\\.rake\\'" . enh-ruby-mode))

For deep indentation, allow bouncing towards a less deep level.

(setq enh-ruby-bounce-deep-indent t)

2.4.15 AUCTEX

Improves the standard editing facilities for all things TeX and LaTeX.

  1. Usage tweaks

    It's 2015 and I prefer a TeX engine that can deal with Unicode and use any font I like.

    (setq-default TeX-engine 'luatex)
    

    This isn't enough though to allow overriding the used command. I used to put LaTeX-command into a file-local variable, but that's not picked up if TeX-engine has been customized…

    (setq LuaTeX-command "luatex"
          LuaLaTeX-command "lualatex --jobname=%s"
          TeX-engine-alist
          '((luatex "LuaTeX" LuaTeX-command LuaLaTeX-command LuaTeX-command)))
    

    Set up viewers and a few other things.

    (setq TeX-quote-after-quote t
          TeX-auto-save t
          TeX-parse-self t
          TeX-view-program-list '(("llpp" "llpp %o"))
          TeX-view-program-selection '(((output-dvi style-pstricks)
                                        "dvips and gv")
                                       (output-dvi "xdvi")
                                       (output-pdf "llpp")
                                       (output-html "xdg-open")))
    

    There's more verbatim environments than verbatim:

    (setq LaTeX-verbatim-environments '("minted" "verbatim" "verbatim*"))
    

    Enable PDF mode, enable folding and add a few convenience keybinds (like C-c C-a to run every command until the document can be viewed).

    (defun my-extend-hs-modes-alist ()
      (add-to-list 'hs-special-modes-alist
                   `(latex-mode ,(latex/section-regexp) nil "%"
                                (lambda (arg) (latex/next-section 1)
                                  (skip-chars-backward " \t\n")) nil)))
    
    (autoload 'latex/section-regexp "latex-extra" "LaTeX section regexp" t)
    
    (defun my-latex-setup ()
      (TeX-PDF-mode)
      (latex/setup-keybinds)
      (my-extend-hs-modes-alist))
    
    (add-hook 'LaTeX-mode-hook 'my-latex-setup)
    
  2. completing-read behaviour

    helm-mode enables more convenient completing-read, however it's a bit silly that candidates for common AUCTEX functions aren't required matches.

    (with-eval-after-load 'tex
      (defun TeX-command-master (&optional override-confirm)
        "Run command on the current document.
    
    If a prefix argument OVERRIDE-CONFIRM is given, confirmation will
    depend on it being positive instead of the entry in `TeX-command-list'."
        (interactive "P")
        (TeX-command (my-TeX-command-query (TeX-master-file)) 'TeX-master-file
                     override-confirm))
    
    
      (defun TeX-command-query (name)
        "Query the user for what TeX command to use."
        (let* ((default
                 (cond ((if (string-equal name TeX-region)
                            (TeX-check-files (concat name "." (TeX-output-extension))
                                             (list name)
                                             TeX-file-extensions)
                          (TeX-save-document (TeX-master-file)))
                        TeX-command-default)
                       ((and (memq major-mode '(doctex-mode latex-mode))
                             ;; Want to know if bib file is newer than .bbl
                             ;; We don't care whether the bib files are open in emacs
                             (TeX-check-files (concat name ".bbl")
                                              (mapcar 'car
                                                      (LaTeX-bibliography-list))
                                              (append BibTeX-file-extensions
                                                      TeX-Biber-file-extensions)))
                        ;; We should check for bst files here as well.
                        (if LaTeX-using-Biber TeX-command-Biber TeX-command-BibTeX))
                       ((TeX-process-get-variable name
                                                  'TeX-command-next
                                                  TeX-command-Show))
                       (TeX-command-Show)))
               (completion-ignore-case t)
               (answer (or TeX-command-force
                           (completing-read
                            (concat "Command: (default " default ") ")
                            (TeX-mode-specific-command-list major-mode) nil t
                            default 'TeX-command-history))))
          ;; If the answer is "latex" it will not be expanded to "LaTeX"
          (setq answer (car-safe (TeX-assoc answer TeX-command-list)))
          (if (and answer
                   (not (string-equal answer "")))
              answer
            default))))
    
    (with-eval-after-load 'latex
      (defun LaTeX-section-heading ()
        "Hook to prompt for LaTeX section name.
    Insert this hook into `LaTeX-section-hook' to allow the user to change
    the name of the sectioning command inserted with `\\[LaTeX-section]'."
        (let ((string (completing-read
                       (concat "Level: (default " name ") ")
                       LaTeX-section-list
                       nil nil name)))
          ; Update name
          (if (not (zerop (length string)))
              (setq name string))
          ; Update level
          (setq level (LaTeX-section-level name))))
    
      (defun LaTeX-environment (arg)
        "Make LaTeX environment (\\begin{...}-\\end{...} pair).
    With optional ARG, modify current environment.
    
    It may be customized with the following variables:
    
    `LaTeX-default-environment'       Your favorite environment.
    `LaTeX-default-style'             Your favorite document class.
    `LaTeX-default-options'           Your favorite document class options.
    `LaTeX-float'                     Where you want figures and tables to float.
    `LaTeX-table-label'               Your prefix to labels in tables.
    `LaTeX-figure-label'              Your prefix to labels in figures.
    `LaTeX-default-format'            Format for array and tabular.
    `LaTeX-default-width'             Width for minipage and tabular*.
    `LaTeX-default-position'          Position for array and tabular."
    
        (interactive "*P")
        (let ((environment (completing-read (concat "Environment type: (default "
                                                    (if (TeX-near-bobp)
                                                        "document"
                                                      LaTeX-default-environment)
                                                    ") ")
                                            (LaTeX-environment-list) nil t nil
                                            'LaTeX-environment-history LaTeX-default-environment)))
          ;; Get default
          (cond ((and (zerop (length environment))
                      (TeX-near-bobp))
                 (setq environment "document"))
                ((zerop (length environment))
                 (setq environment LaTeX-default-environment))
                (t
                 (setq LaTeX-default-environment environment)))
    
          (let ((entry (assoc environment (LaTeX-environment-list))))
            (if (null entry)
                (LaTeX-add-environments (list environment)))
    
            (if arg
                (LaTeX-modify-environment environment)
              (LaTeX-environment-menu environment))))))
    

2.4.16 smartparens

Promises to go beyond paredit (which is structured editing for Lisp code) by supporting other languages than Lisp-likes with arbitrary kinds of pairs. I only use its autopairing feature, pair highlighting and a bit of auto-indent though.

  1. Disable some default pairs

    The following wall of code disables pairs for Lisp- and TeX-like modes that make absolutely no sense.

    (with-eval-after-load 'smartparens
      (sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil)
      (sp-local-pair 'minibuffer-inactive-mode "`" nil :actions nil)
      (sp-local-pair 'emacs-lisp-mode "'" nil :actions nil)
      (sp-local-pair 'emacs-lisp-mode "`" nil :actions nil)
      (sp-local-pair 'lisp-interaction-mode "'" nil :actions nil)
      (sp-local-pair 'lisp-interaction-mode "`" nil :actions nil)
      (sp-local-pair 'scheme-mode "'" nil :actions nil)
      (sp-local-pair 'scheme-mode "`" nil :actions nil)
      (sp-local-pair 'inferior-scheme-mode "'" nil :actions nil)
      (sp-local-pair 'inferior-scheme-mode "`" nil :actions nil)
    
      (sp-local-pair 'LaTeX-mode "\"" nil :actions nil)
      (sp-local-pair 'LaTeX-mode "'" nil :actions nil)
      (sp-local-pair 'LaTeX-mode "`" nil :actions nil)
      (sp-local-pair 'latex-mode "\"" nil :actions nil)
      (sp-local-pair 'latex-mode "'" nil :actions nil)
      (sp-local-pair 'latex-mode "`" nil :actions nil)
      (sp-local-pair 'TeX-mode "\"" nil :actions nil)
      (sp-local-pair 'TeX-mode "'" nil :actions nil)
      (sp-local-pair 'TeX-mode "`" nil :actions nil)
      (sp-local-pair 'tex-mode "\"" nil :actions nil)
      (sp-local-pair 'tex-mode "'" nil :actions nil)
      (sp-local-pair 'tex-mode "`" nil :actions nil))
    
  2. Add IDE-like auto-insertion for braces

    Working on college assignments in both C and Java made me wish for an interesting feature I've seen in IDEs: Automatic insertion of a correctly indented newline before the closing brace which allows you to enter its content right away. The following is stolen from its wiki.

    (defun my-create-newline-and-enter-sexp (&rest _ignored)
      "Open a new brace or bracket expression, with relevant newlines and indent."
      (newline)
      (indent-according-to-mode)
      (forward-line -1)
      (indent-according-to-mode))
    
    (with-eval-after-load 'smartparens
      (sp-local-pair 'c-mode "{" nil :post-handlers
                     '((my-create-newline-and-enter-sexp "RET")))
      (sp-local-pair 'js-mode "{" nil :post-handlers
                     '((my-create-newline-and-enter-sexp "RET")))
      (sp-local-pair 'rjsx-mode "{" nil :post-handlers
                     '((my-create-newline-and-enter-sexp "RET")))
      (sp-local-pair 'java-mode "{" nil :post-handlers
                     '((my-create-newline-and-enter-sexp "RET"))))
    
  3. Other

    First of all, no long pair mismatch messages please, they're reserved for debugging purposes.

    (setq sp-message-width nil)
    

    Because I'm using evil, funny things are happening with my cursor, like it not going beyond the end of the line in normal state. To emulate a bit more Vim-like paren highlighting, pairs should be shown from inside, too.

    (setq sp-show-pair-from-inside t)
    

    Automatic quote escaping feels like a mistake to me (and to its author as well ._.).

    (setq sp-autoescape-string-quote nil)
    

    This curiously named variable controls whether the overlay spanning the pair's content disappears on backwards motions, something entirely different than its name suggests.

    (setq sp-cancel-autoskip-on-backward-movement nil)
    

2.4.17 Evil

Here comes the set of sane text editing keybindings I can't live without. Both implementation and execution are excellent and reuse as much from Emacs as possible, resulting in very high compatibility and feature coverage. The only thing I can complain about is that its sources are pretty much incomprehensible to me.

Despite that weakness I've managed writing my own additions to improve integration a good bit more according to my own tastes.

  1. Initial state

    First of all, there are plenty of special modes where neither insert state nor motion state suffice. I've instead decided to do away with motion state and going for Emacs state whenever it makes sense.

    (setq evil-default-state 'emacs
          evil-emacs-state-modes nil
          evil-insert-state-modes nil
          evil-motion-state-modes nil
          evil-normal-state-modes '(text-mode prog-mode fundamental-mode
                                              css-mode conf-mode
                                              TeX-mode LaTeX-mode
                                              diff-mode))
    

    org-capture-mode is a minor mode, that's why I need to use its hook instead. Same goes for view-mode.

    (add-hook 'org-capture-mode-hook 'evil-insert-state)
    (add-hook 'with-editor-mode-hook 'evil-insert-state)
    (add-hook 'view-mode-hook 'evil-emacs-state)
    

    Allow quitting M-x magit-blame with q by toggling Evil's current state.

    (defun my-evil-toggle ()
      (interactive)
      (cond
        ((memq evil-state '(insert normal))
         (evil-emacs-state))
        ((eq evil-state 'emacs)
         (evil-exit-emacs-state))))
    
    (add-hook 'magit-blame-mode-hook 'my-evil-toggle)
    
  2. More Emacs-like feel

    These make movement, undo and search feel a bit less weird.

    (setq evil-cross-lines t
          evil-move-beyond-eol t
          evil-want-fine-undo t
          evil-symbol-word-search t)
    

    However, I want C-w to still be the window map prefix in Emacs state (instead of the standard kill-region command). As the customization setting for that is applied in evil-maps.el which is loaded by evil.el, I need to load it before enabling evil-mode.

    (with-eval-after-load 'evil-vars
      (setq evil-want-C-w-in-emacs-state t))
    
    (with-eval-after-load 'evil-common
      (evil-declare-motion 'recenter-top-bottom))
    
  3. Keymap hacking

    I want Y to yank to the end of line.

    (setq evil-want-Y-yank-to-eol t)
    

    Some minor modes come with keymaps reminiscent of special major modes, these get overridden by Evil. These can be fixed by using evil-normalize-keymaps, at least for edebug-mode.

    (add-hook 'edebug-mode-hook 'evil-normalize-keymaps)
    

    macrostep-mode requires a bit more effort, see evil#511 for the code involved and further explanation.

    (defun my-macrostep-setup ()
      (evil-make-overriding-map macrostep-keymap 'normal)
      (evil-normalize-keymaps))
    (add-hook 'macrostep-mode-hook 'my-macrostep-setup)
    

    Same goes for cider-debug:

    (defun my-cider-debug-setup ()
      (evil-make-overriding-map cider--debug-mode-map 'normal)
      (evil-normalize-keymaps))
    (add-hook 'cider--debug-mode-hook 'my-cider-debug-setup)
    

    Let's poke some holes into its keymaps. Anything not bound will be passed through to Emacs other keymaps. Because SPC, RET and TAB are bound to rather silly commands in Vim I'm unbinding them to allow for much more useful Emacs commands (such as context-aware indentation, following links, scrolling a page down, etc.).

    (with-eval-after-load 'evil-maps
      (define-key evil-motion-state-map (kbd "SPC") nil)
      (define-key evil-motion-state-map (kbd "RET") nil)
      (define-key evil-motion-state-map (kbd "TAB") nil))
    

    Same story with C-. and M-., the latter is usually bound to lookup of symbol at point. The former is unbound because I'm fat-fingering often.

    (with-eval-after-load 'evil-maps
      (define-key evil-normal-state-map (kbd "C-.") nil)
      (define-key evil-normal-state-map (kbd "M-.") nil))
    

    The hole poking continues, this time for the ex completion keymap. Everything with a modifier (except for the toggle key for Emacs state and other useful keys) has to go.

    (with-eval-after-load 'evil-maps
      (setcdr evil-ex-completion-map
              (--reject
               (and (memq 'control (event-modifiers (car-safe it)))
                    ;; abort prompt
                    (/= (car-safe it) ?\C-c)
                    (/= (car-safe it) ?\C-g)
                    ;; exit
                    (/= (car-safe it) ?\C-m)
                    ;; previous/next input
                    (/= (car-safe it) ?\C-p)
                    (/= (car-safe it) ?\C-n))
               (cdr evil-ex-completion-map))))
    

    There is a relatively new customizable for Evil's insert state keymap that disables setting up anything vimmish in it:

    (setq evil-disable-insert-state-bindings t)
    
  4. C-w

    C-w works in Emacs state, but not in insert state. Let's fix that.

    (with-eval-after-load 'evil-maps
      (define-key evil-insert-state-map (kbd "C-w") 'evil-window-map))
    
  5. C-i

    C-i is used in Vim as counterpart to C-o for going back and forth in the jump list. It also happens to be interpreted as TAB, simply because terminals are a nightmare. Fortunately GUI Emacs can be told to not resolve C-i to indentation by defining a function in key-translation-map that returns the desired key. That way I'm sending a custom <C-i> when Evil is active, in normal state and C-i (as opposed to the TAB key) has been pressed, otherwise TAB is passed through.

    (defun my-translate-C-i (_prompt)
      (if (and (= (length (this-single-command-raw-keys)) 1)
               (eql (aref (this-single-command-raw-keys) 0) ?\C-i)
               (bound-and-true-p evil-mode)
               (eq evil-state 'normal))
          (kbd "<C-i>")
        (kbd "TAB")))
    
    (define-key key-translation-map (kbd "TAB") 'my-translate-C-i)
    
    (with-eval-after-load 'evil-maps
      (define-key evil-motion-state-map (kbd "<C-i>") 'evil-jump-forward))
    
  6. C-u

    C-u is bound to a scroll up command in Vim, in Emacs however it's used for the prefix argument. This feels pretty weird to me after having bothered learning C-u as command for killing a whole line in everything using the readline library. I consider M-u as a good replacement considering it's bound to the rather useless upcase-word command by default which I most definitely will not miss.

    (define-key global-map (kbd "C-u") 'kill-whole-line)
    (define-key global-map (kbd "M-u") 'universal-argument)
    (define-key universal-argument-map (kbd "C-u") nil)
    (define-key universal-argument-map (kbd "M-u") 'universal-argument-more)
    (with-eval-after-load 'evil-maps
      (define-key evil-motion-state-map (kbd "C-u") 'evil-scroll-up))
    
  7. Extra keybindings

    Emacs 24.4 introduced electric-indent-mode as default which happens to be a global mode. I'm not particularly fond of it (and anything starting with electric-), that's why I disable it later after initialization is done and instead bind newline-and-indent in insert state.

    (with-eval-after-load 'evil-maps
      (define-key evil-insert-state-map (kbd "RET") 'newline-and-indent))
    

    Let's get rid of ; for the questionable benefit of having a modifier less to hit for entering ex state.

    (with-eval-after-load 'evil-maps
      (define-key evil-motion-state-map (kbd ";") 'evil-ex)
      (define-key evil-visual-state-map (kbd ";") 'evil-ex))
    

    U is a much more fit key for redoing than C-r.

    (with-eval-after-load 'evil-maps
      (define-key evil-normal-state-map (kbd "U") 'redo))
    

    The evil-numbers package is pretty nice, but I don't want to use the standard Vim keybinds (C-a and C-x) for its commands. Instead I'm going for the much more mnemonic + and -.

    (with-eval-after-load 'evil-maps
      (define-key evil-normal-state-map (kbd "-") 'evil-numbers/dec-at-pt)
      (define-key evil-normal-state-map (kbd "+") 'evil-numbers/inc-at-pt)
      (define-key evil-visual-state-map (kbd "-") 'evil-numbers/dec-at-pt)
      (define-key evil-visual-state-map (kbd "+") 'evil-numbers/inc-at-pt)
      (define-key evil-visual-state-map (kbd "g -") 'evil-numbers/dec-at-pt-incremental)
      (define-key evil-visual-state-map (kbd "g +") 'evil-numbers/inc-at-pt-incremental))
    

    The z map is full of keybindings I can never remember for dealing with code folding. First of all, get rid of them.

    (with-eval-after-load 'evil-maps
      (setcdr evil-normal-state-map
              (--reject
               (eq (car-safe it) ?z)
               (cdr evil-normal-state-map)))
    
      (setcdr evil-motion-state-map
              (--reject
               (eq (car-safe it) ?z)
               (cdr evil-motion-state-map))))
    

    Next, define a few toggling commands and bind them.

    (defvar my-hs-hide nil
      "Current state of hideshow for toggling all.")
    
    (with-eval-after-load 'evil-common
      (evil-define-command my-evil-toggle-folds ()
        "Open or close all folds."
        (setq my-hs-hide (not my-hs-hide))
        (if my-hs-hide
            (hs-hide-all)
          (hs-show-all))))
    
    (defun my-toggle-mode-line-minor-modes ()
      (interactive)
      (if rm-blacklist
          (setq rm-blacklist nil)
        (setq rm-blacklist ".*"))
      (force-mode-line-update t))
    
    (defun my-narrow-to-region-with-mode (beg end mode)
      (interactive (list (region-beginning) (region-end)
                         (completing-read "Major mode: "
                                          (mapcar 'cdr auto-mode-alist) nil t)))
      (unless (region-active-p)
        (error "No region for narrowing selected"))
      (narrow-to-region beg end)
      (deactivate-mark)
      (funcall (intern mode)))
    
    (defun my-revert-buffer ()
      (interactive)
      (revert-buffer nil t))
    
    (defun my-theme-toggle ()
      (interactive)
      (cond
       ((memq 'punpun-light custom-enabled-themes)
        (disable-theme 'punpun-light)
        (load-theme 'punpun-dark t))
       ((memq 'punpun-dark custom-enabled-themes)
        (disable-theme 'punpun-dark)
        (load-theme 'punpun-light t))))
    
    (with-eval-after-load 'evil-maps
      (define-key evil-normal-state-map (kbd "z r") 'my-revert-buffer)
      (define-key evil-normal-state-map (kbd "z b") 'magit-blame)
      (define-key evil-normal-state-map (kbd "z s") 'describe-char)
      (define-key evil-normal-state-map (kbd "z e") 'toggle-debug-on-error)
      (define-key evil-normal-state-map (kbd "z q") 'toggle-debug-on-quit)
      (define-key evil-normal-state-map (kbd "z t") 'my-theme-toggle)
      (define-key evil-normal-state-map (kbd "z m") 'my-toggle-mode-line-minor-modes)
      (define-key evil-normal-state-map (kbd "z n") 'my-narrow-to-region-with-mode)
      (define-key evil-normal-state-map (kbd "z TAB") 'evil-toggle-fold)
      (define-key evil-normal-state-map (kbd "z <backtab>") 'my-evil-toggle-folds))
    

    Define my most-used helpers (stolen from unimpaired.vim) next.

    (defun my-evil-unimpaired-insert-newline-above (count)
      "Insert an empty line below point"
      (interactive "p")
      (save-excursion
        (dotimes (i count)
          (evil-insert-newline-above))))
    
    (defun my-evil-unimpaired-insert-newline-below (count)
      "Insert an empty line below point"
      (interactive "p")
      (save-excursion
        (dotimes (i count)
          (evil-insert-newline-below))))
    
    (with-eval-after-load 'evil-maps
      (define-key evil-normal-state-map (kbd "[ SPC") 'my-evil-unimpaired-insert-newline-above)
      (define-key evil-normal-state-map (kbd "] SPC") 'my-evil-unimpaired-insert-newline-below))
    

    Add a few convenience bindings to the window map on C-w.

    (defun my-work-on-scratch ()
      (interactive)
      (switch-to-buffer (get-buffer-create "*scratch*")))
    
    (with-eval-after-load 'evil-maps
      (define-key evil-window-map (kbd "n") 'my-work-on-scratch)
      (define-key evil-window-map (kbd "u") 'winner-undo)
      (define-key evil-window-map (kbd "b") 'helm-mini)
      (define-key evil-window-map (kbd "d") 'kill-buffer)
      (define-key evil-window-map (kbd "D") 'kill-buffer-and-window)
      (define-key evil-window-map (kbd "C-d") 'kill-buffer-and-window))
    

    Then some "leader" bindings.

    (defun my-switch-to-last-buffer ()
      (interactive)
      (switch-to-buffer (other-buffer)))
    
    (defun my-find-file-with-root-privileges (filename)
      (interactive "F")
      (let ((pw (concat (password-read "Enter password: ") "\n"))
            (sudo-process (start-process "Sudo" "*sudo*" "sudo" "-Se" filename)))
        (process-send-string sudo-process pw)))
    
    (with-eval-after-load 'evil-maps
      (define-key evil-normal-state-map (kbd ", ,") 'my-switch-to-last-buffer)
      (define-key evil-normal-state-map (kbd ", .") 'helm-mini)
      (define-key evil-normal-state-map (kbd ", /") 'helm-find-files)
      (define-key evil-normal-state-map (kbd ", ?") 'my-find-file-with-root-privileges))
    

    As calc keeps bewildering me, but calculator doesn't, I'll bind the latter for one-off calculations:

    (with-eval-after-load 'evil-maps
      (define-key evil-normal-state-map (kbd "=") 'calculator))
    
  8. Load supplementary modes

    Finally, there's a few minor modes depending on Evil being loaded before they are.

    (defun my-after-evil ()
      (global-evil-surround-mode)
      (eyebrowse-mode)
      (eyebrowse-setup-opinionated-keys)
      (require 'evil-cleverparens-text-objects))
    
    (add-hook 'evil-mode-hook 'my-after-evil)
    
  9. evil-cleverparens

    I'm not a fan of it rebinding either the small or big word movements. There's unfortunately no option to do neither, so we'll have to undo all those bindings.

    (with-eval-after-load 'evil-cleverparens
      (evil-cp--populate-mode-bindings-for-state evil-cp-regular-movement-keys
                                                 'normal nil))
    

    Its insertion replacement inserting spaces isn't terribly helpful.

    (setq evil-cleverparens-use-regular-insert t)
    

    Why did they change M-w from copying the region to pasting something? I like the other bindings though, so I'll leave them as is.

    (with-eval-after-load 'evil-cleverparens
      (evil-define-key 'normal evil-cleverparens-mode-map (kbd "M-w") nil))
    

    It's useful to have slurping/barfing in insert mode so that you can quickly update things while typing.

    (with-eval-after-load 'evil-maps
      (define-key evil-insert-state-map (kbd "M-<") 'evil-cp-<)
      (define-key evil-insert-state-map (kbd "M->") 'evil-cp->))
    

    This resolves a bug in the change command eating a space when applied on a word:

    (with-eval-after-load 'evil-vars
      (push 'evil-cp-change evil-change-commands))
    
  10. undo-redo

    Burgling Emacs 28 functionality to have undo-redo in Emacs 27.1:

    (defun undo--last-change-was-undo-p (undo-list)
      (while (and (consp undo-list) (eq (car undo-list) nil))
        (setq undo-list (cdr undo-list)))
      (gethash undo-list undo-equiv-table))
    
    (defun undo-redo (&optional arg)
      "Undo the last ARG undos."
      (interactive "*p")
      (cond
       ((not (undo--last-change-was-undo-p buffer-undo-list))
        (user-error "No undo to undo"))
       (t
        (let* ((ul buffer-undo-list)
               (new-ul
                (let ((undo-in-progress t))
                  (while (and (consp ul) (eq (car ul) nil))
                    (setq ul (cdr ul)))
                  (primitive-undo arg ul)))
               (new-pul (undo--last-change-was-undo-p new-ul)))
          (message "Redo%s" (if undo-in-region " in region" ""))
          (setq this-command 'undo)
          (setq pending-undo-list new-pul)
          (setq buffer-undo-list new-ul)))))
    

    And enabling it in Evil:

    (setq evil-undo-system 'undo-redo)
    ;; (add-hook 'after-init-hook 'global-undo-tree-mode)
    ;; (setq evil-undo-system 'undo-tree)
    

2.4.18 yasnippet

Snippets are quite useful for boilerplatey languages. Like, Java. Although, if you take it far enough, even something as org-mode qualifies considering I can never remember the proper syntax for code blocks. The following sets up a single directory for snippets.

(setq yas-snippet-dirs '("~/.config/emacs/snippets"))
(with-eval-after-load 'yasnippet
  (yas-reload-all))

This package does way too chatty logging by default:

(setq yas-verbosity 2)

2.4.19 Circe

rcirc is too small, ERC is too large. So I chose Circe as my IRC client living inside Emacs. As for why IRC in Emacs in the first place, I wanted to leave irssi behind and didn't really like Weechat. So, why not try something extensible?

(defun my-face-at-point-p (face)
  (let ((face-or-faces (get-text-property (point) 'face)))
    (cond
      ((consp face-or-faces)
       (memq face face-or-faces))
      ((symbolp face-or-faces)
       (eq face face-or-faces)))))

(defun my-circe-inhibit-nick-highlight-function ()
  (or (eq major-mode 'circe-server-mode)
      (my-face-at-point-p 'circe-server-face)
      (my-face-at-point-p 'circe-my-message-face)))

(setq circe-inhibit-nick-highlight-function
      'my-circe-inhibit-nick-highlight-function)

(setq circe-track-faces-priorities '(circe-highlight-nick-face))
  1. Authentication and identification

    First of all, let's define who I am and change the quit/part message to something less advertising.

    (setq circe-default-nick "wasamasa"
          circe-default-user "wasamasa"
          circe-default-realname "wasamasa"
          circe-default-part-message "Bye"
          circe-default-quit-message "Bye")
    

    I'm using ZNC to connect to Freenode. Passwords for the Nickserv service are kept in pass.

    (defun my-pass-password* (name)
      (car (process-lines "pass" name)))
    
    (fset 'my-pass-password (my-memoize 'my-pass-password*))
    
    (defun my-retrieve-irc-password (_)
      (cond
       ((string= circe-server-network "ZNC/freenode")
        (my-pass-password "irc/znc"))
       (t (error "Unknown network"))))
    
    (setq circe-network-options
          '(("ZNC/freenode" :host "brause.cc" :port 30832
             :user "wasamasa/freenode" :pass my-retrieve-irc-password
             :use-tls t :reduce-lurker-spam t)
            ("Freenode" :nick "not_wasamasa")))
    
  2. Basic usability tweaks

    I cannot imagine why I wouldn't want to use in-line tab-completion with cycling just as it exists in other IRC clients.

    (setq circe-use-cycle-completion t)
    

    Highlighting the last read position in buffer is invaluable:

    (setq lui-track-behavior 'before-switch-to-buffer)
    

    Let's customize a few format strings.

    (setq circe-format-self-say "<{nick}> {body}"
          circe-format-server-topic "*** Topic Change by {userhost}: {topic-diff}"
          circe-server-buffer-name "{network}"
          circe-prompt-string (propertize ">>> " 'face 'circe-prompt-face))
    

    Other entities using my nickname are not ghosted by default, that's why I enable it, but only after authenticating in some way.

    (setq circe-nickserv-ghost-style 'after-auth)
    

    ZNC handles autojoins for me, but Circe does not recognize these. So, instead I'll just ignore all buffers that are opened implicitly.

    (setq circe-new-buffer-behavior 'ignore)
    

    General highlight settings.

    (setq circe-track-faces-priorities '(circe-highlight-nick-face
                                         lui-highlight-face))
    (setq circe-extra-nicks '("wasa" "bagelmasa")
          lui-formatting-list '(("\\(?:^\\|[[:space:]]\\)\\(\\*[^*[:space:]]+?\\*\\)\\(?:$\\|[[:space:]]\\)" 1 lui-strong-face)
                                ("\\(?:^\\|[[:space:]]\\)\\(_[^_[:space:]]+?_\\)\\(?:$\\|[[:space:]]\\)" 1 underline))
          circe-highlight-nick-in-server-messages-p nil
          circe-highlight-nick-type 'all)
    

    When using the circe-color-nicks contrib module, please color nicknames inside messages as well.

    (setq circe-color-nicks-everywhere t)
    

    Additionally to that, make use of colors more compatible with my theme.

    (setq circe-color-nicks-pool-type
          '("#ffaf00" "#d75f00" "#d70000" "#00af00"
            "#5f00ff" "#0087ff" "#ff005f" "#8700d7"))
    

    Sometimes I get spurious tracking buffers in the modeline, this seems to happen if the frame loses focus. Here's a workaround:

    (autoload 'tracking-remove-visible-buffers "tracking")
    (add-hook 'focus-in-hook 'tracking-remove-visible-buffers)
    
  3. Add extra keybinds

    Let's add a few extra keybindings common in all buffers Circe spawns. I want word killing to behave the same as for comint, C-l to redraw and reposition and C-u to kill the whole line since there's a more appropriate command than the default one bound to C-u.

    (defun my-window-C-l ()
      (interactive)
      (goto-char (point-max))
      (recenter-top-bottom -1))
    
    (with-eval-after-load 'lui
      (define-key lui-mode-map (kbd "<remap> <kill-word>") 'my-kill-word)
      (define-key lui-mode-map (kbd "<remap> <backward-kill-word>") 'my-backward-kill-word)
      (define-key lui-mode-map (kbd "C-l") 'my-window-C-l)
      (define-key lui-mode-map (kbd "C-u") 'lui-kill-to-beginning-of-line))
    

    Copy-pasting from other sources (like, browsers) can leave more than one line of text in the input area. Directly sending it would be annoying as this would result in either multiple messages or autopaste detection. To avoid resorting to joining lines manually, I've written a command doing the opposite of M-q, but named it similiarly as the intent is the same (making the given text conform to a more suitable form).

    (defun my-trim-surrounding-lui-whitespace ()
      (save-excursion
        (let ((bol (line-beginning-position)))
          (goto-char bol)
          (skip-syntax-forward "-")
          (when (/= (point) bol)
            (delete-region bol (point)))
          (let ((eol (point-max)))
            (goto-char eol)
            (skip-syntax-backward "-" bol)
            (when (/= (point) eol)
              (delete-region (point) eol))))))
    
    (defun my-fill-lui-input ()
      (interactive)
      (fill-delete-newlines lui-input-marker (point-max) nil nil nil)
      (my-trim-surrounding-lui-whitespace)
      (goto-char (point-max)))
    
    (with-eval-after-load 'lui
      (define-key lui-mode-map (kbd "M-q") 'my-fill-lui-input))
    

    Sometimes I like knowing just how many people are online.

    (defun my-circe-count-nicks ()
      (interactive)
      (when (eq major-mode 'circe-channel-mode)
        (message "%i entities are online on %s."
                 (length (circe-channel-nicks))
                 (buffer-name))))
    
    (with-eval-after-load 'circe
      (define-key circe-channel-mode-map (kbd "C-c n") 'my-circe-count-nicks))
    

    There's some occasional pastebin spamming going on at #chicken, for that I wrote me a small helper to report it instantly:

    (defvar my-vandusen-paste-re
      (rx bol
          (? space) ; margin property hides here
          "<vandusen>"
          (? (+ any))
          "\"" (group (+ any))
          "\" pasted \"" (group (+ any))
          "\" " (group (+ any))
          eol))
    
    (defun my-report-spam ()
      (interactive)
      (save-excursion
        (re-search-backward my-vandusen-paste-re nil t))
      (let ((url (match-string-no-properties 3)))
        (when url
          (let ((id (cadr (split-string url "paste\\?id="))))
            (insert "vandusen: spam " id)))))
    
    (with-eval-after-load 'circe
      (define-key circe-channel-mode-map (kbd "C-c s") 'my-report-spam))
    

    I keep forgetting how to send people a memo:

    (defun my-circe-send-memo (target message)
      (interactive "sTarget: \nsMessage: ")
      (cond
       ((equal circe-chat-target "#chicken")
        (insert (format "vandusen: tell %s: %s" target message)))
       ((member circe-chat-target '("#emacs" "#scheme"))
        (insert (format "rudybot: later tell %s %s" target message)))
       (t
        (user-error "Unsupported channel"))))
    
    (with-eval-after-load 'circe
      (define-key circe-channel-mode-map (kbd "C-c m") 'my-circe-send-memo))
    

    I've registered a few nicks, but need to use them once in a while for them not to expire. The following command makes this task a bit easier:

    (defun my-next-nick ()
      (interactive)
      (when (not circe-server-buffer)
        (user-error "Used outside of a Circe buffer"))
      (let* ((nicks '("wasamasa" "wasa" "{{{" "}}}"))
             (current-nick (circe-nick))
             (index (--find-index (equal it current-nick) nicks))
             (new-nick (nth (mod (1+ index) (length nicks)) nicks)))
        (insert (format "/nick %s" new-nick))))
    
    (with-eval-after-load 'circe
      (define-key circe-channel-mode-map (kbd "C-c >") 'my-next-nick))
    

    I like irssi's key binding to switch to the next active buffer:

    (with-eval-after-load 'circe
      (define-key circe-mode-map (kbd "M-a") 'tracking-next-buffer))
    
  4. Hacks

    The standard nickname switching function is a bit silly. I own a bunch of nicknames and will use the wasa one for switching.

    (defun my-circe-nick-next (oldnick)
        (cond ((string= oldnick "wasamasa") "wasa")
              ((string= oldnick "wasa" "wasamasa"))))
    
    (setq circe-nick-next-function 'my-circe-nick-next)
    

    There isn't a highlighting function yet that could do something useful like setting a X urgency hint (taken from the wiki), asides from that I want a bit more of control to treat highlights in private queries different from channel highlights. The following code yanks out the default one and replaces it with something slightly better.

    (defun my-x-urgency-hint (&rest _)
      (let* ((wm-hints (append (x-window-property
                                "WM_HINTS" nil "WM_HINTS" nil nil t)
                               nil))
             (flags (car wm-hints)))
        (setcar wm-hints (logior flags #x00000100))
        (x-change-window-property "WM_HINTS" wm-hints nil "WM_HINTS" 32 t)))
    
    (defvar my-circe-hl-function 'my-x-urgency-hint)
    (defvar my-bot-nicks '("fsbot" "phrik"))
    
    (defun my-circe-message-regex-handler (nick userhost command &rest args)
      (let* ((irc-message (cadr args))
             (nick-regexp (regexp-opt (append (list (circe-server-nick))
                                              circe-extra-nicks)
                                      'symbols))
             (highlight-match (string-match-p nick-regexp irc-message)))
        (when (and irc-message
                   (not (member nick my-bot-nicks))
                   (not (circe--ignored-p nick userhost irc-message))
                   (not (string-match-p "LAGMON" irc-message))
                   (or highlight-match (string= (car args) (circe-server-nick))))
          (message "HL: %s!%s %s %S" nick userhost command args)
          (funcall my-circe-hl-function nick userhost command args)))
      'noop)
    
    (with-eval-after-load 'circe
      (circe-set-display-handler "PRIVMSG" 'my-circe-message-regex-handler))
    

    Highlight quoted text in a green color for fun and profit (or to be honest, to discern 4chan people from the rest).

    (defface my-circe-greentext-face '((t (:foreground "spring green")))
      "Face for greentext detected in circe.")
    
    (defun my-circe-color-greentext ()
      (when (memq major-mode '(circe-channel-mode circe-query-mode))
        (let ((body-beg (text-property-any (point-min) (point-max)
                                           'lui-format-argument 'body))
              (greentext-regex "\\([^[:space:]]+?: \\)?\\(>[[:word:][:space:]]\\)"))
          (when body-beg
            (goto-char body-beg)
            (when (looking-at greentext-regex)
              (add-text-properties (match-beginning 2) (point-max)
                                   '(face my-circe-greentext-face)))))))
    
    (add-hook 'lui-pre-output-hook 'my-circe-color-greentext)
    

    I dislike custom, but want persistent fools and ignore. So, advice it is!

    (defvar my-circe-fool-file "~/.config/emacs/etc/fools")
    (defvar my-circe-fool-list nil)
    (defvar my-circe-ignore-file "~/.config/emacs/etc/ignore")
    (defvar my-circe-ignore-list nil)
    
    (defun my-serialize (path data)
      (with-temp-file path
        (let (print-length print-level)
          (insert (prin1-to-string data))
          (pp-buffer))))
    
    (defun my-deserialize (path)
      (when (file-exists-p path)
        (with-temp-buffer
          (insert-file-contents-literally path)
          (goto-char (point-min))
          (read (current-buffer)))))
    
    (defun my-circe-command-FOOL (line)
      (let* ((regex (and (string-match "\\S-+" line) (match-string 0 line)))
             (reason (read-string "Why? ")))
        (push (list regex reason) my-circe-fool-list))
      (my-serialize my-circe-fool-file my-circe-fool-list))
    
    (defun my-circe-command-UNFOOL (line)
      (let ((regex (and (string-match "\\S-+" line) (match-string 0 line))))
        (setq my-circe-fool-list
              (--remove (equal (car it) regex) my-circe-fool-list)))
      (my-serialize my-circe-fool-file my-circe-fool-list))
    
    (defun my-circe-command-IGNORE (line)
      (let* ((regex (and (string-match "\\S-+" line) (match-string 0 line)))
             (reason (read-string "Why? ")))
        (push (list regex reason) my-circe-ignore-list))
      (my-serialize my-circe-ignore-file my-circe-ignore-list))
    
    (defun my-circe-command-UNIGNORE (line)
      (let ((regex (and (string-match "\\S-+" line) (match-string 0 line))))
        (setq my-circe-ignore-list
              (--remove (equal (car it) regex) my-circe-ignore-list)))
      (my-serialize my-circe-ignore-file my-circe-ignore-list))
    
    (advice-add 'circe-command-FOOL :after 'my-circe-command-FOOL)
    (advice-add 'circe-command-UNFOOL :after 'my-circe-command-UNFOOL)
    (advice-add 'circe-command-IGNORE :after 'my-circe-command-IGNORE)
    (advice-add 'circe-command-UNIGNORE :after 'my-circe-command-UNIGNORE)
    
    (with-eval-after-load 'circe
      (setq my-circe-fool-list (my-deserialize my-circe-fool-file))
      (setq my-circe-ignore-list (my-deserialize my-circe-ignore-file))
      (setq circe-fool-list (mapcar 'car my-circe-fool-list))
      (setq circe-ignore-list (mapcar 'car my-circe-ignore-list)))
    

    Let's test having fluid-width windows, now that both Circe and Emacs 24.4 seem to be less wonky about it. Adapted from the wiki, extended to avoid the rather annoying behaviour of the cursor jumping into the fringe when reaching the full text width.

    (setq lui-time-stamp-position 'right-margin
          lui-fill-type nil)
    
    (defun my-no-fill-lui-setup ()
      (setq fringes-outside-margins t
            right-margin-width 10
            visual-fill-column-extra-text-width '(0 . -10)
            fill-column 80
            wrap-prefix "    ")
      (visual-line-mode)
      (setf (cdr (assoc 'continuation fringe-indicator-alist)) nil)
      (make-local-variable 'overflow-newline-into-fringe)
      (setq overflow-newline-into-fringe nil))
    
    (add-hook 'circe-chat-mode-hook 'my-no-fill-lui-setup)
    

    Port of the pangotext irssi script:

    (defvar my-pango-color
      '(("white" . 0)
        ("black" . 1)
        ("blue" . 2)
        ("green" . 3)
        ("lightred" . 4)
        ("red" . 5)
        ("purple" . 6)
        ("orange" . 7)
        ("yellow" . 8)
        ("lightgreen" . 9)
        ("cyan" . 10)
        ("lightcyan" . 11)
        ("lightblue" . 12)
        ("lightpurple" . 13)
        ("gray" . 14)
        ("lightgray" . 15)))
    
    (defvar my-pango-color-order
      ["white" "lightgray" "lightcyan" "lightblue" "lightgreen"
       "lightpurple" "yellow" "lightred" "orange" "red" "purple"
       "cyan" "blue" "green" "gray" "black"])
    
    (defvar my-pango-color-ordermap nil)
    (dotimes (i (length my-pango-color-order))
      (push (cons (aref my-pango-color-order i) i) my-pango-color-ordermap))
    
    (defvar my-pango-tag-registry
      '(("rb" . my-pango-rainbow)
        ("rainbow" . my-pango-rainbow)
        ("checker" . my-pango-checker)
        ("checkers" . my-pango-checker)
        ("gradient" . my-pango-gradient)
        ("gradiant" . my-pango-gradient)
        ("grad" . my-pango-gradient)
        ("ul" . my-pango-underline)
        ("underline" . my-pango-underline)
        ("b" . my-pango-bold)
        ("bold" . my-pango-bold)
        ("inv" . my-pango-inverse)
        ("inverse" . my-pango-inverse)))
    
    (defun my-pango-palettize (text palette)
      (with-temp-buffer
        (let ((count 0))
          (dotimes (i (length text))
            (let ((char (aref text i)))
              (when (not (= (char-syntax char) ?\s))
                (insert ?\003 (format "%02d" (aref palette (% count (length palette)))))
                (setq count (1+ count)))
              (when (= char ?,)
                (insert ?,))
              (insert char))))
        (insert ?\003)
        (buffer-string)))
    
    (defun my-pango-rainbow (text &optional _attribs)
      (let ((palette [2 3 4 5 6 7 8 9 10 11 12 13]))
        (my-pango-palettize text palette)))
    
    (defun my-pango-gradient (text &optional attribs)
      (let* ((start (or (cdr (assoc "start" attribs)) "white"))
             (end (or (cdr (assoc "end" attribs)) "lightpurple"))
             (start (cdr (assoc start my-pango-color-ordermap)))
             (end (cdr (assoc end my-pango-color-ordermap)))
             (min (min start end))
             (max (max start end))
             (i min)
             palette)
        (while (<= i max)
          (push (cdr (assoc (aref my-pango-color-order
                                  (% i (length my-pango-color-order)))
                            my-pango-color))
                palette)
          (setq i (1+ i)))
        (my-pango-palettize text (vconcat (nreverse palette)))))
    
    (defun my-pango-checker (text &optional _attribs)
      (with-temp-buffer
        (let ((count 0)
              (palette ["01,04" "04,01"]))
          (dotimes (i (length text))
            (let ((char (aref text i)))
              (when (not (= (char-syntax char) ?\s))
                (insert ?\003 (aref palette (% count (length palette))))
                (setq count (1+ count)))
              (when (= char ?,)
                (insert ?,))
              (insert char))))
        (insert ?\003)
        (buffer-string)))
    
    (defun my-pango-bold (text &optional _attribs)
      (format "\002%s\002" text))
    
    (defun my-pango-underline (text &optional _attribs)
      (format "\037%s\037" text))
    
    (defun my-pango-inverse (text &optional _attribs)
      (format "\026%s\026" text))
    
    (defvar my-pango-tag-rx
      (rx "<" (* space) (group (+ (not (any ">" space))))
          (* space) (group (* (not (any ">")))) ">"
          (group (+? any))
          "<" (? "/") (backref 1) ">"))
    
    (defvar my-pango-tag-attr-rx
      (rx (group (+ (not space)))
          (* space) "=" (* space)
          (group (+ (not space)))))
    
    (defun my-pango-render (text)
      (while (string-match my-pango-tag-rx text)
        (let* ((action (match-string 1 text))
               (extra (match-string 2 text))
               (msg (match-string 3 text))
               (op (cdr (assoc action my-pango-tag-registry)))
               attribs)
          (save-match-data
            (while (string-match my-pango-tag-attr-rx extra)
              (push (cons (match-string 1 extra) (match-string 2 extra)) attribs)
              (setq extra (replace-match "" t t extra))))
          (when (not op)
            (user-error "unknown action: %s" action))
          (setq text (replace-match (funcall op msg attribs) t t text))))
      text)
    
    (defun circe-command-PANGO (line)
      "Send LINE using Pango markup."
      (interactive "sLine: ")
      (if (not circe-chat-target)
          (circe-display-server-message "No target for current buffer")
        (let* ((colored (my-pango-render line))
               (preview (with-temp-buffer
                          (insert colored)
                          (lui-irc-colors)
                          (buffer-string))))
          (when (yes-or-no-p (format "Send message? %s" preview))
            (circe-command-SAY colored))
          (message ""))))
    
  5. Entry point

    And finally, the function for entering IRC.

    (defun my-irc ()
      "Connect to all my IRC servers after enabling contrib modules."
      (interactive)
      (circe-lagmon-mode)
      (enable-circe-color-nicks)
      (enable-lui-autopaste)
      (enable-lui-track)
      (require 'circe-chanop)
      (circe "ZNC/freenode"))
    

2.4.20 hl-todo

Minor mode for coloring TODO, NOTE, FIXME and many more keywords of that sort prevalent in comments and strings.

(setq hl-todo-keyword-faces '(("TODO" . hl-todo)
                              ("NOTE" . hl-todo)
                              ("HACK" . hl-todo)
                              ("FIXME" . hl-todo)
                              ("KLUDGE" . hl-todo)))

2.4.21 macrostep

Expand macros interactively.

I'll go with the recommended keybinding for it.

(define-key emacs-lisp-mode-map (kbd "C-x e") 'macrostep-expand)

2.4.22 visual-fill-column

There used to be longlines-mode which did display a file soft-wrapped without breaking words and using the value of fill-column. However it got deprecated for visual-line-mode which does the same except it doesn't take fill-column into account. The visual-fill-column package fixes that, I want to enable its mode automatically when enabling visual-line-mode.

(add-hook 'visual-line-mode-hook 'visual-fill-column-mode)

2.4.23 magit

The very best.

For some reason they've started nagging me to opt-in for default behaviour to be used without confirmation.

(setq magit-revert-buffers 'silent
      magit-push-always-verify nil)

Enable inline highlighting of differences for all hunks:

(setq magit-diff-refine-hunk 'all)

Experimental:

(setq magit-display-buffer-function 'display-buffer
      magit-display-file-buffer-function 'display-buffer)

2.4.24 nov.el

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

2.4.25 alda-mode

Make : and indentation less magic, then use this mode for waka.

(with-eval-after-load 'alda-mode
  (define-key alda-mode-map (kbd ":") 'self-insert-command))

(defun my-alda-mode-setup ()
  (setq indent-line-function 'indent-relative))
(add-hook 'alda-mode-hook 'my-alda-mode-setup)

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

2.4.26 rainbow-delimiters-mode

I like highlighting the outermost parentheses, but keep the inner ones in a lighter color. This is done by the theme and a customization option:

(setq rainbow-delimiters-outermost-only-face-count 1)

2.4.27 Elpher

Finally a usable Gopher client. I'd like it to open gopher:// URLs for me, yet keep using xdg-open for everything else.

(defun my-browse-url-elpher (url &rest args)
  (require 'elpher)
  (elpher-go url))

(defvar my-url-handlers
  '(("^gopher://" . my-browse-url-elpher)
    ("." . browse-url-xdg-open)))

(if (boundp 'browse-url-handlers)
    (setq browse-url-handlers my-url-handlers)
  (setq browse-url-browser-function my-url-handlers))

Another odd choice is how it customizes Evil to use motion state for its buffers.

(with-eval-after-load 'elpher
  (evil-set-initial-state 'elpher-mode 'emacs))

2.4.28 explain-pause-mode

This might help with debugging UX issues. It's a bit too noisy for my taste though, so let's make it more quiet and increase the threshold:

(setq explain-pause-alert-via-message nil)
(setq explain-pause-blocking-too-long-ms 100)

2.5 Keybinds

We already have F1 for help, so let's turn C-h and M-h more readline-like.

(global-set-key (kbd "C-h") 'delete-backward-char)
(global-set-key (kbd "M-h") 'backward-kill-word)

Deactivate all other uses of insert than Shift-Insert.

(global-set-key (kbd "<insert>") nil)
(global-set-key (kbd "<C-insert>") nil)

FWIW, yanking is what Emacs calls pasting text. Unlike Vim where it stands for copying text. Anyways, I want to rectify the curious default of making S-insert paste from the same place as C-y.

(defun my-yank-primary ()
  (interactive)
  (let ((primary (or (x-get-selection-value)
                     (x-get-selection))))
    (unless primary
      (error "No selection is available"))
    (push-mark (point))
    (insert-for-yank primary)))

(global-set-key (kbd "<S-insert>") 'my-yank-primary)

Install a keybind that saves all buffers with asking (use a prefix argument to inhibit the questions), then kills Emacs (including the daemon) on M-<f4>.

(defun my-quit-emacs (arg)
  (interactive "P")
  (save-some-buffers (when (consp arg) t) t)
  (kill-emacs))

(global-set-key (kbd "M-<f4>") 'my-quit-emacs)

Make M-x more useful, put its original functionality on C-c M-x instead.

(global-set-key (kbd "M-x") 'helm-smex)
(global-set-key (kbd "C-c M-x") 'execute-extended-command)

Helm stuff

(global-set-key (kbd "C-x C-f") 'helm-find-files)
(global-set-key (kbd "C-x b") 'helm-buffers-list)
(global-set-key (kbd "<f10>") 'helm-resume)

C-c C-+ and C-c C-- are pretty useful, but only resize the current buffer. Here's a hack using set-frame-font and altering the font size only:

(defun my-alter-frame-font-size (fn)
  (let* ((current-font-name (frame-parameter nil 'font))
         (decomposed-font-name (x-decompose-font-name current-font-name))
         (font-size (string-to-number (aref decomposed-font-name 5))))
    (aset decomposed-font-name 5 (number-to-string (funcall fn font-size)))
    (set-frame-font (x-compose-font-name decomposed-font-name))))

(defun my-inc-frame-font-size ()
  (interactive)
  (my-alter-frame-font-size '1+))

(defun my-dec-frame-font-size ()
  (interactive)
  (my-alter-frame-font-size '1-))

(global-set-key (kbd "C-+") 'my-inc-frame-font-size)
(global-set-key (kbd "C-=") 'my-inc-frame-font-size)
(global-set-key (kbd "C--") 'my-dec-frame-font-size)

2.6 Hooks

First of all, let's define a helper function that does the boilerplate for us.

(defun my-add-function-to-hooks (function hooks)
  (dolist (hook hooks)
    (add-hook hook function)))

2.6.1 Basic setup

I'll start with a list of hooks for everything that's not a special mode or in other words, related to programming and text editing. This will inevitably contain modes that have not been properly derived, might be worth reporting those.

(defun my-non-special-modes-setup ()
  (setq indicate-empty-lines t)
  (setq indicate-buffer-boundaries '((top . left) (bottom . left)))
  (setq show-trailing-whitespace t)
  (modify-syntax-entry ?_ "w")
  (goto-address-mode)
  (smartparens-mode)
  (show-smartparens-mode)
  (yas-minor-mode))

(my-add-function-to-hooks
  'my-non-special-modes-setup
  '(text-mode-hook prog-mode-hook css-mode-hook diff-mode-hook))

Same deal with programming-related hooks and text-related hooks.

(my-add-function-to-hooks
  'auto-fill-mode
  '(text-mode-hook css-mode-hook))
(defun my-prog-modes-setup ()
  (make-local-variable 'comment-auto-fill-only-comments)
  (setq comment-auto-fill-only-comments t)
  (auto-fill-mode)
  (column-enforce-mode))

(my-add-function-to-hooks
  'my-prog-modes-setup
  '(prog-mode-hook))

If a file contains a shebang, mark it as executable after saving.

(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)

2.6.2 Mode-specific setup

To fine tune completion behavior, I'll prefer company-mode over global-company-mode. I should add more hooks for REPLs, too.

(my-add-function-to-hooks
  'company-mode
  '(prog-mode-hook css-mode-hook nxml-mode-hook sgml-mode-hook
    css-mode-hook ielm-mode-hook))

Enable linting for a few select modes by default. The rest is programming languages I don't happen to use, languages I don't have a linter installed for or that would be too annoying to always check. Like, the default emacs-lisp (and emacs-lisp-checkdoc, too) linter which always assumes I'm writing an Emacs package.

(my-add-function-to-hooks
 'flycheck-mode
 '(python-mode-hook ruby-mode-hook enh-ruby-mode-hook LaTeX-mode-hook
   clojure-mode-hook clojurescript-mode-hook))

Lisp-specific setup goes here:

(defun my-lisp-modes-setup ()
  (rainbow-delimiters-mode)
  (evil-cleverparens-mode))

(my-add-function-to-hooks
  'my-lisp-modes-setup
  '(emacs-lisp-mode-hook ielm-mode-hook scheme-mode-hook
    inferior-scheme-mode-hook lisp-mode-hook lisp-interaction-mode-hook
    clojure-mode-hook nrepl-interaction-mode-hook))

Colored color codes however only make sense for CSS, HTML and XML.

(my-add-function-to-hooks
  'rainbow-mode
  '(css-mode-hook sgml-mode-hook nxml-mode-hook web-mode-hook))

smartparens ought to be enabled in the minibuffer, but only for M-:.

(defun my-smartparens-minibuffer-setup ()
  (when (eq this-command 'eval-expression)
    (smartparens-mode)
    (show-smartparens-mode)))

(add-hook 'minibuffer-setup-hook 'my-smartparens-minibuffer-setup)

2.7 Other software

I'm using qutebrowser which provides a C-e keybind for editing the contents of the current text box inside my editor. As it creates a file at /tmp/qutebrowser-editor-<random>.txt, I'm changing some of the text-mode defaults for nicer editing:

(defun my-qutebrowser-edit ()
  (markdown-mode)
  (auto-fill-mode -1)
  (setq require-final-newline nil))

(add-to-list 'auto-mode-alist '("\\`/tmp/qutebrowser-editor-" . my-qutebrowser-edit))

MAL requires me to use modes for lesser known languages, so their config goes there:

(defvar inhibit-first-line-modes-regexps nil) ; HACK
(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/")
(autoload 'smalltalk-mode "smalltalk-mode" "Smalltalk mode" t)
(add-to-list 'auto-mode-alist '("\\.st\\'" . smalltalk-mode))
(push 'smalltalk-mode evil-normal-state-modes)

(add-hook 'smalltalk-mode-hook 'my-non-special-modes-setup)
(add-hook 'smalltalk-mode-hook 'my-prog-modes-setup)

(with-eval-after-load 'smalltalk-mode
  (define-key smalltalk-mode-map "!" nil)
  (define-key smalltalk-mode-map ":" nil))

(add-to-list 'auto-mode-alist '("\\.cfml?\\'" . js-mode))
(add-to-list 'auto-mode-alist '("\\.cfc\\'" . js-mode))

(defun my-sclang-mode ()
  (require 'sclang)
  (sclang-mode))

(autoload 'sclang-mode "sclang-mode" "SCLang mode" t)
(add-to-list 'auto-mode-alist '("\\.sc\\'" . my-sclang-mode))
(push 'sclang-mode evil-normal-state-modes)
(setq sclang-indent-level 4)

I use keychain to manage passphrases for SSH, the following integrates it into Emacs:

(defun my-keychain-setup ()
  (let* ((output (shell-command-to-string "keychain --query --quiet id_rsa id_ed25519"))
         (lines (split-string output "\n" t)))
    (dolist (line lines)
      (when (string-match "^\\([^=]+\\)=\\(.*\\)$" line)
        (setenv (match-string 1 line) (match-string 2 line))))))

cloc replacement:

(defun my-cloc ()
  (interactive)
  (let ((blanks 0)
        (comments 0)
        (code 0))
    (font-lock-ensure)
    (save-excursion
      (save-restriction
        (when (region-active-p)
          (narrow-to-region (region-beginning) (region-end)))
        (goto-char (point-min))
        (while (not (eobp))
          (skip-syntax-forward " ")
          (cond
           ((eolp)
            (setq blanks (1+ blanks)))
           ((memq (get-text-property (point) 'face)
                  '(font-lock-comment-face font-lock-comment-delimiter-face))
            (setq comments (1+ comments)))
           (t
            (setq code (1+ code))))
          (forward-line 1))))
    (message "blank: %d, comment: %d, code: %d" blanks comments code)))

2.8 Load unpublished packages

Emacs Lisp files can be easily turned into packages one can load and install via package.el. While initialization is done, it only happens after successful startup.

My unpublished packages reside in ~/.config/emacs/unpublished and need to be added to the load-path.

(add-to-list 'load-path "~/.config/emacs/unpublished")
(load "~/.config/emacs/unpublished/private.el")

2.9 Set up unpublished packages

I have a bunch of stuff I'm working on.

(require 'helm-smex)

2.10 Post-Init

While packages were prematurely initialized, the following is unconditionally run after init has finished.

(defun my-after-init ()
  (setq my-init-time 0)
  (add-to-list 'load-path "~/code/elisp/evil")
  (add-to-list 'load-path "~/code/elisp/circe")
  (autoload 'circe-lagmon-mode "circe-lagmon" "Enable lagmon" t)
  (autoload 'enable-circe-color-nicks "circe-color-nicks" "Enable coloring nicks" t)
  (autoload 'enable-lui-autopaste "lui-autopaste" "Enable lui autopaste" t)
  (autoload 'enable-lui-track "lui-track" "Enable lui track bar" t)
  (autoload 'circe "circe" "Enable circe" t)
  (autoload 'nov-mode "nov" nil t)
  (autoload 'chicken-doc-describe "chicken-doc" nil t)
  (add-to-list 'load-path "~/code/elisp/shackle")
  (add-to-list 'load-path "~/code/elisp/eyebrowse")
  (add-to-list 'load-path "~/code/elisp/form-feed")
  (add-to-list 'load-path "~/code/elisp/firestarter")
  (add-to-list 'load-path "~/code/elisp/esxml")
  (add-to-list 'load-path "~/code/elisp/nov.el")
  (add-to-list 'load-path "~/code/elisp/goto-chg")
  (add-to-list 'load-path "~/code/elisp/chip8")
  (add-to-list 'load-path "~/code/elisp/chicken-doc.el")
  (add-to-list 'load-path "~/code/elisp/yaml-mode")
  (add-to-list 'load-path "~/code/elisp/svnwiki-mode")
  (with-timer (global-so-long-mode))
  ;; (with-timer (explain-pause-mode))
  (with-timer (sml/setup))
  (with-timer (recentf-mode))
  (with-timer (savehist-mode))
  (with-timer (save-place-mode))
  (with-timer (require 'saveplace))
  (with-timer (winner-mode))
  (with-timer (shackle-mode))
  (with-timer (firestarter-mode))
  (with-timer (smex-initialize))
  (with-timer (helm-mode))
  (with-timer (electric-indent-mode -1))
  (with-timer (my-setup-hydra))
  (with-timer (require 'evil))
  (with-timer (evil-mode))
  (with-timer (my-disable-tramp-file-handlers))
  (with-timer (cl-lib-highlight-initialize))
  (with-timer (cl-lib-highlight-warn-cl-initialize))
  (with-timer (line-number-mode))
  (with-timer (column-number-mode))
  (with-timer (my-keychain-setup))
  (with-timer (company-tng-configure-default))
  (with-timer (global-form-feed-mode))
  (with-timer (global-hl-todo-mode))
  (load custom-file)
  (setq gc-cons-threshold 800000)
  (message "post-init: (%.3fs)" my-init-time))
(add-hook 'after-init-hook 'my-after-init)

3 Stuff to do

4 Epilogue

I consider this experiment successful. I've managed to uncruftify my previous setup, have a highly readable init file and get recommendations and Github favourites from an reasonable amount of people. It went better than expected because explaining my reasoning and hacks in a config file fits me better than doing the same for a program's code.

Debugging has suffered a bit due to the indirection. While C-x C-e works globally, I no longer have C-M-x and the prefix argument version for instrumenting, so I need to copy the piece of code over to a scratch buffer. esup reports an artificially low time, unlike what M-x emacs-init-time does.