Posts related to the one true editor, Emacs. Not to be confused with that other editor.

Dim Unfocused Minibuffer Prompts

I can easily tell if an Emacs buffer is focused by looking at the buffer’s mode-line. On the other hand, the only indicator that the minibuffer is focused or not is the cursor, and that’s pretty easy to miss.

I wanted to avoid making any intrusive changes to the minibuffer, so I decided to style the prompt based on whether the minibuffer is focused. When not focused, I remap the minibuffer prompt face to shadow.

Focused: focused minibuffer

Unfocused: unfocused minibuffer

The code is pretty simple:

(defvar-local steb/focused-minibuffer-face-remap nil
  "Face-remapping for a dimmed-minibuffer prompt.")

(defun steb/focused-minibuffer-update (w)
  (when (eq w (minibuffer-window))
    (when steb/focused-minibuffer-face-remap
      (face-remap-remove-relative steb/focused-minibuffer-face-remap)
      (setq steb/focused-minibuffer-face-remap nil))
    (unless (eq w (selected-window))
      (with-selected-window (minibuffer-window)
        (setq steb/focused-minibuffer-face-remap
              (face-remap-add-relative 'minibuffer-prompt 'shadow))))))

(defun steb/minibuffer-setup-focus-indicator ()
  (add-hook 'window-state-change-functions 'steb/focused-minibuffer-update nil t))

(add-hook 'minibuffer-setup-hook #'steb/minibuffer-setup-focus-indicator)

My approach was heavily inspired by this Emacs Stack Exchange answer, but I tried to simplify the implementation and make the styling a bit less intrusive.

Grammar Tools in Emacs

Up till now the only prose checker/linter I’ve used in Emacs is jinx. It’s an amazing spell checker for programmers and technical writers: it handles snake_case and CamelCase symbols, knows what to spell-check based on the text’s face, etc. Unfortunately, it’s just a spell checker and won’t catch any grammatical errors.

Now I’m looking for a local (offline) grammar checker. Something to catch stupid mistakes: repeat words, cut off sentences, tense disagreements, homonyms, etc. I tend to jump around a lot when writing and want a tool that’ll tell me when I started a sentence in one tense and finished it in another, or failed to finish it entirely. Ideally the tool would also provide general writing advice — wording improvements, etc. — but I’ll probably need a full language model for that.

Vale

I first tried Vale because it’s by far the simplest option and has great support for markup languages and can even lint prose within source code. To be honest, it looks like a great choice for teams that want custom rule-based prose linting. Its primary power is the expressivity of its rules allowing organizations to set and enforce writing standards (e.g., “don’t start a sentence with ‘but’”). Unfortunately, it’s not a general-purpose grammar checker, and I’m not trying to conform to some specific set of writing stylistic rules.

However, if that’s what you want, vale has great Emacs integration via flymake-vale. I say great because (a) it uses flymake (no language server setup required) and (b) only checks changed text making it very fast and lightweight.

Harper

Next I tried Harper. Like Vale, it’s mostly rule-based. However, it’s more focused on providing general-purpose writing tips than on enforcing specific stylistic rules: it can catch repeat words and tell me when a sentence is too long; it has some very basic grammar rules and can catch article/noun disagreements (a/an); it even has some rules to catch “it’s” versus “its”. I hate it.

  • Harper thinks “given its computed value” should be “given it’s computed value”.
  • Harper won’t catch errors like “Go to a the park.”
  • Harper likes to harp on “mistakes” like “long” sentences.

Harper is like that middle-school english teacher with their highly rigid rules about writing: great for teaching good habits to the notice writer but, at this point, I’ve ingrained them. When I break these rules, it’s (usually) because I meant to break them.

What I need is a tool to help me catch stupid errors and/or a tool to help improve the flow of my writing. Harper is not that tool.

Worse, the only Emacs integration I found was harper-ls, a language server. In Emacs specifically, this has a few downsides:

  • Eglot (the default language server package in Emacs) only supports a single language server per buffer, so I can’t use Harper along with some other language server.
  • Eglot expects Language Servers to be used in “projects” and will enumerate all project files when it starts the language server. Starting a language server in my home directory takes forever as it tries to enumerate all my files (including my backup snapshots…).
  • Eglot will start one language server per project (in this case, often per directory).

However, if you really want to use Harper, you can trick Eglot into treating all documents (with the same major mode) managed by harper as if they were in the same project. If you don’t care, skip to the next section.

First you’ll need to define a new project type for harper. In my case, I “root” the project in a guaranteed empty directory.

(cl-defmethod project-root ((project (eql harper)))
  "/var/empty")

(cl-defmethod project-name ((project (eql harper)))
  "harper")

This virtual “harper” project contains all files managed by the harper language server. Honestly, it’s probably OK to simply return nil here.

(cl-defmethod project-files ((project (eql harper)))
  (when-let* ((server (cl-find-if #'eglot--languageId
                                  (gethash (eglot--current-project)
                                           eglot--servers-by-project))))
    (mapcar #'buffer-file-name (eglot--managed-buffers server))))

Finally, add a project “finder” function that puts all buffers that would be managed by harper into the “harper” project, if and only if we’re acting on behalf of Eglot (eglot-lsp-context is non-nil).

(defun project-try-harper (dir)
  (and eglot-lsp-context
       (string= (cadr (eglot--lookup-mode major-mode)) "harper-ls")
       'harper))

(add-to-list 'project-find-functions #'project-try-harper)

LanguageTool

Finally, I broke down and set up LanguageTool: it’s by far the most advanced free-software grammar tool. The next step-up is Grammarly and/or LanguageTool’s online offering, but I absolutely refuse to use a remote grammar tool (I’m not sending all my notes, etc. to someone else’s server).

Unfortunately, it’s definitely a heavyweight. It uses at least 2.5GiB of memory, and you’ll need the 14GiB n-gram database if you really want it to do its job. On the other hand, it’s leagues beyond Harper and Vale in terms of catching grammatical mistakes.

In terms of Emacs integration, I ended up creating a fork of flymake-languagetool that has improved performance (avoids re-checking the entire document on change); excludes markup, code, etc. before sending text to LanguageTool instead of filtering errors afterward for improved accuracy/performance; and renders diagnostics in the correct location even when Emoji are present in the buffer. I considered using a language server, but I had enough of that with Harper.

I recommend you find a good LanguageTool docker container or something because setting it up from scratch isn’t a fun exercise. However, if you’re like me and (a) use Arch Linux and (b) avoid sketchy docker containers, read on.

On Arch, I installed the languagetool package from the extra repository (pick jre21-openjdk-headless when prompted), along with a few AUR packages: languagetool-ngrams-en, fasttext, and fasttext-langid-models. The fasttext packages are only used for language detection so they should be optional, but LanguageTool’s HTTP server complains if they’re not installed.

Next, you’ll need to create a configuration file to tell LanguageTool where to find everything:

fasttextModel=/usr/share/fasttext/lid.176.bin
fasttextBinary=/usr/bin/fasttext
languageModel=/usr/share/ngrams

You’ll need to pass --config /path/to/my/config.properties to the languagetool command (which you can configure via flymake-languagetool-server-command and flymake-languagetool-server-args:

(setopt flymake-languagetool-server-command "languagetool"
        flymake-languagetool-server-args
        '("--http" "--allow-origin" "*"
          "--config" "/path/to/my/config.properties"))

That’ll get you a basic functioning LanguageTool server.

Taming Org-Mode With Side Windows

Org-mode will, occasionally, pop up a window that takes over half the screen. Specifically, this happens to when:

  1. Capturing a note (org-capture): I get a half-screen window asking me to select the capture template.
  2. Inserting a timestamp (org-timestamp): The calendar takes over half the screen (assuming org-read-date-popup-calendar is non-nil).

This is especially annoying in the second case because the calendar window invariably covers the window I’m referencing while taking notes. That is, I usually take notes with the following setup where the red window is the selected window and the gray window is some document I’m referencing.

reference & notes windows side-by-side

In this case, attempting to insert a timestamp in my notes causes a calendar window to pop-up, covering the reference window:

calendar window replacing the reference window

The solution is to use side windows. E.g., I can put the calendar at the top of the screen above the notes/reference windows, replacing neither.

calendar window above the notes & reference windows

First, put calendar windows in a dedicated side-window at the top of the screen.

(setf (alist-get (rx bos "*Calendar*" eos) display-buffer-alist nil nil #'equal)
      '(display-buffer-in-side-window (side . top) (dedicated . t)
                                      (window-height . fit-window-to-buffer)))

Then, put org-capture template selection buffers in a side-window at the bottom of the screen, instead of taking over half the screen.

(setf (alist-get (rx bos "*Org Select* eos) display-buffer-alist #nil nil 'equal)
      '(display-buffer-in-side-window (dedicated . t)))

If you want to better understand Emacs window management, I highly recommend Mickey’s (of Mastering Emacs) excellent write up: Demystifying Emacs’s Window Manager.

Executable Org-Mode Files

My Emacs config lives in a massive org-mode file and I often want to re-tangle from outside Emacs (because, e.g., I broke my Emacs session for some reason). I could just use a simple Makefile, but that’s no fun. Instead, I’ve turned my config into a self-tangling script by adding the following two lines to the top of my init.org file and marking it as executable:

#!/usr/bin/env -S sh -c 'emacs --batch --eval="(setq org-confirm-babel-evaluate nil)" --file "$0" -f org-babel-tangle'
# -*- mode: org; lexical-binding: t -*-

Now I can run ./init.org in my ~/.emacs.d directory and my config will tangle itself into the correct files.

This might sound like a weird gimmick – to be honest, it kind of is – but I’ve been tangling my config this way for years at this point and it “just works” with no dependencies other than Emacs.


A similar trick can be used to execute all org-babel blocks in an org-mode file, making org-mode a viable meta-scripting language for tying together scripts written in different languages – even scripts executing across multiple machines via TRAMP.

#!/usr/bin/env -S sh -c 'emacs --batch --eval="(setq org-confirm-babel-evaluate nil)" --file "$0" -f org-babel-execute-buffer'
# -*- mode: org; lexical-binding: t -*-
#+TITLE: Hello World

#+BEGIN_SRC elisp
(message "Hello World!")
#+END_SRC

Pipe to Emacs

While there are many ways to pipe to emacs, they all involve either shuttling text by repeatedly calling emacsclient or writing to a temporary file. However, neither are necessary.

Basically, while emacs can’t (yet) read from a named pipe (FIFO), it can read standard output from a process so, one gratuitous use of cat later…

(defun pager-read-pipe (fname)
  (let ((buf (generate-new-buffer "*pager*"))
        (pname (concat "pager-" fname)))
    (with-current-buffer buf (read-only-mode))
    (switch-to-buffer buf)

    (let ((proc (start-process pname buf "/usr/bin/cat" fname)))
      (set-process-sentinel proc (lambda (proc e) ()))
      (set-process-filter proc (lambda (proc string)
                                 (when (buffer-live-p (process-buffer proc))
                                   (with-current-buffer (process-buffer proc)
                                     (save-excursion
                                       ;; Insert the text, advancing the process marker.
                                       (let ((inhibit-read-only t))
                                         (goto-char (process-mark proc))
                                         (insert string)
                                         (set-auto-mode)
                                         (set-marker (process-mark proc) (point))))))))
      proc)))

…and you can read a from named pipe. As an added bonus, this function will try to autodetect the correct mode.

To actually use this, I recommend the following shell script:

#!/bin/bash
set -e

cleanup() {
    trap - TERM INT EXIT
    if [[ -O "$FIFO" ]]; then
        rm -f "$FIFO" || :
    fi
    if [[ -O "$DIR" ]]; then
        rmdir "$DIR" || :
    fi
}
trap "cleanup" TERM INT EXIT

SOCKET="${XDG_RUNTIME_DIR:-/run/user/$UID}/emacs/server"

# Create a named pipe in /dev/shm
DIR=$(mktemp -d "/dev/shm/epipe-$$.XXXXXXXXXX")
FIFO="$DIR/fifo"
mkfifo -m 0600 "$DIR/fifo"

# Ask emacs to read from the names socket.
emacsclient -s "$SOCKET" -n --eval "(pager-read-pipe \"$FIFO\")" >/dev/null <&-

exec 1>"$FIFO"
cleanup # Cleanup early. Nobody needs the paths now...
cat

You will probably need to set the SOCKET variable to your emacs socket filename.

Usage:

dmesg --follow | epipe