Emacs
Posts related to the one true editor, Emacs. Not to be confused with that other editor.
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