Listful Andrew: Counting Words VI: Clojure
-1:-- Counting Words VI: Clojure (Post Listful Andrew)--L0--C0--2026-04-09T14:06:00.000Z
-1:-- Counting Words VI: Clojure (Post Listful Andrew)--L0--C0--2026-04-09T14:06:00.000Z
-1:-- Counting Words V: More solutions in Emacs Lisp (Post Listful Andrew)--L0--C0--2026-04-09T14:05:00.000Z
Flymake has been quietly sitting in my config for years doing exactly what it says on the tin, squiggly lines under things that are wrong, and I mostly left it alone. But recently I noticed I was doing the same little dance over and over: spot a warning, squint at the modeline counter, run `M-x flymake-show-buffer-diagnostics`, scroll through the list to find the thing I was actually looking at, then flip back. Two windows, zero connection between them.
So I wired it up properly, and while I was in there I gave it a set of keybindings that feel right to my muscle memory.
The obvious bindings for stepping through errors are `M-n` and `M-p`, and most people using flymake bind exactly those. The problem is that in my config `M-n` and `M-p` are already taken, they step through simply-annotate annotations (which is itself a very handy thing and I am not giving it up!). So I shifted a key up and went with the shifted variants: `M-N` for next, `M-P` for previous, and `M-M` to toggle the diagnostics buffer.
(setq flymake-show-diagnostics-at-end-of-line nil)
(with-eval-after-load 'flymake
(define-key flymake-mode-map (kbd "M-N") #'flymake-goto-next-error)
(define-key flymake-mode-map (kbd "M-P") #'flymake-goto-prev-error))
With M-M I wanted it to be a bit smarter than just “open the buffer”. If it is already visible I want it gone, if it is not I want it up. The standard toggle pattern:
(defun my/flymake--diag-buffer ()
"Return the visible flymake diagnostics buffer, or nil."
(seq-some (lambda (b)
(and (with-current-buffer b
(derived-mode-p 'flymake-diagnostics-buffer-mode))
(get-buffer-window b)
b))
(buffer-list)))
(defun my/flymake-toggle-diagnostics ()
"Toggle the flymake diagnostics buffer."
(interactive)
(let ((buf (my/flymake--diag-buffer)))
(if buf
(quit-window nil (get-buffer-window buf))
(flymake-show-buffer-diagnostics)
(my/flymake-sync-diagnostics))))
Now the interesting bit. What I really wanted was a follow mode, something like how the compilation buffer tracks position or how Occur highlights the current hit. When my point lands on an error in the source buffer, the corresponding row in the diagnostics buffer should light up. That way the diagnostics window becomes a live index of where I am rather than a static dump and think in general this is how a lot of other IDEs work.
I tried the lazy route first, turning on hl-line-mode in the diagnostics buffer and calling hl-line-highlight from a post-command-hook in the source buffer. The line lit up once and then refused to move. Nothing I did would shift it. This is because hl-line-highlight is really only designed to be driven from the window whose line is being highlighted, and I was firing it from afar.
Ok, so why not just manage my own overlay:
(defvar my/flymake--sync-overlay nil
"Overlay used to highlight the current entry in the diagnostics buffer.")
(defun my/flymake-sync-diagnostics ()
"Highlight the diagnostics buffer entry matching the error at point."
(when-let* ((buf (my/flymake--diag-buffer))
(win (get-buffer-window buf))
(diag (or (car (flymake-diagnostics (point)))
(car (flymake-diagnostics (line-beginning-position)
(line-end-position))))))
(with-current-buffer buf
(save-excursion
(goto-char (point-min))
(let ((found nil))
(while (and (not found) (not (eobp)))
(let ((id (tabulated-list-get-id)))
(if (and (listp id) (eq (plist-get id :diagnostic) diag))
(setq found (point))
(forward-line 1))))
(when found
(unless (overlayp my/flymake--sync-overlay)
(setq my/flymake--sync-overlay (make-overlay 1 1))
(overlay-put my/flymake--sync-overlay 'face 'highlight)
(overlay-put my/flymake--sync-overlay 'priority 100))
(move-overlay my/flymake--sync-overlay
found
(min (point-max) (1+ (line-end-position)))
buf)
(set-window-point win found)))))))
My first pass at the walk through the tabulated list did not work. I was comparing (tabulated-list-get-id) directly against the diagnostic returned by flymake-diagnostics using eq, and it was always false, which meant found stayed nil forever and the overlay never moved. A dive into flymake.el revealed why. Each row in the diagnostics buffer stores its ID as a plist, not as the diagnostic itself:
(list :diagnostic diag
:line line
:severity ...)
So I need to pluck out :diagnostic before comparing. Obvious in hindsight, as these things always are. With plist-get in place the comparison lines up and the overlay moves exactly where I want it, tracking every navigation command.
The fallback lookup using line-beginning-position and line-end-position is there because flymake-diagnostics (point) only returns something if point is strictly inside the diagnostic span. When I land between errors or on the same line as an error but a few columns off, I still want the diagnostics buffer to track, so I widen the search to the whole line.
Finally, wrap the hook in a minor mode so I can toggle it per buffer and enable it automatically whenever flymake comes up:
(define-minor-mode my/flymake-follow-mode
"Sync the diagnostics buffer to the error at point."
:lighter nil
(if my/flymake-follow-mode
(add-hook 'post-command-hook #'my/flymake-sync-diagnostics nil t)
(remove-hook 'post-command-hook #'my/flymake-sync-diagnostics t)))
(add-hook 'flymake-mode-hook #'my/flymake-follow-mode)
(define-key flymake-mode-map (kbd "M-M") #'my/flymake-toggle-diagnostics)
The end result is nice. M-M pops the diagnostics buffer, M-N and M-P walk through the errors, and as I navigate the source the matching row in the diagnostics buffer highlights in step with me. If I close the buffer with another M-M everything goes quiet, and I can still step through with M-N/M-P on their own.
Three little keybindings and twenty lines of elisp, but they turn flymake from a static reporter into something that actually feels connected to where I am in the buffer.
-1:-- Wiring Flymake Diagnostics into a Follow Mode (Post James Dyer)--L0--C0--2026-04-09T05:13:00.000Z
A recent Mastodon post showing the days until the next U.S. election got me to wonder, “how can I compute that in Emacs?” Turns out, this is trivial with the Org mode function org-time-stamp-to-now doing the timestamp computation for you.
We can wrap org-time-stamp-to-now in an internal function cc/--days-until that generates a formatted string of the days until a target date.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
From there we can then start defining commands that use cc/--days-until. The command cc/days-until shown below will prompt you with a date picker to enter a date. Note that you can enter a date value (e.g. “Dec 25, 2026”) in the mini-buffer prompt for org-read-date.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Going back to the original motivator for this post, here’s an implementation of days until the next two major U.S. election dates with the command cc/days-until-voting.
1 2 3 4 5 6 7 8 9 10 11 12 | |
The result of M-x cc/days-until-voting as of 8 April 2026 is:
209 days until 2026 midterms, 944 days until 2028 presidential election
It’s so human to want to know how long it’s going to take. Feel free to build your own countdown clocks using the code above. May your journey to whatever you plan be a happy one!
-1:-- Computing Days Until with Emacs (Post Charles Choi)--L0--C0--2026-04-08T23:00:00.000Z
...those question headers are displaying differently, with the background colour no longer spanning the width of the window. I'd like to understand why.
Turns out it was pretty straightforward:
diff --git a/quiz.el b/quiz.el
index 2dbe45d..c1ba255 100644
--- a/quiz.el
+++ b/quiz.el
@@ -40,7 +40,8 @@
(defface quiz-question-number-face
'((t :height 1.3
:background "black"
- :foreground "white"))
+ :foreground "white"
+ :extend t))
"Face for the question number."
:group 'quiz)
and so v1.7 has happened.

It looks like, perhaps, at some point in the past, :extend was t by
default, but it no longer is? Either way, explicitly setting it to t has
done the trick.
-1:-- quiz.el v1.7 (Post Dave Pearson)--L0--C0--2026-04-08T15:54:38.000Z
Floofcode over that the Emacs subreddit asks a question that resonates with me. He notes that he often has a repetitive task and wonders whether it would be worthwhile writing some Elisp to automate it. Usually, he has to repeat the task several times before he gets fed up and fixes it for good. He wonders how other people deal with this. Do they have to repeat the task a certain number of times before automating it or is the criterion more subjective.
I can relate. This happens to me all the time. I keep doing the same task over and over until one day I realize that I’m being stupid and spend a few minutes dashing off a bit of Elisp that solves the problem once and for all. Every time, I tell myself, “Well, I won’t that mistake again. Next time I’m going to get this type of task automated right away.” Of course, the next time the same thing happens.
As to floofcode’s question, I would guess that it depends on the person. For me, it’s a subjective matter. The amount of time I’ll spend repeating the same boring task over and over varies but it always ends in a fit of anger when I ask myself why I’m still doing things manually. The thing is, when I’m repeatedly doing the task manually, I’m not wondering whether I should automate it. That happens at the end when I realize I’ve been stupid.
I guess the answer is something of the sort that after you’ve repeated the task twice, just automate it. Sure sometimes you’ll lose and waste time but in my experience it will most often be a win. I wish I could learn this.
-1:-- Tolerance For Repetition (Post Irreal)--L0--C0--2026-04-08T14:33:12.000Z
Today's Emacs Lisp package tidy-up is of a package I first wrote a couple of employers ago. While working on code I often found myself viewing FASTA files in an Emacs buffer and so I thought it would be fun to use this as a reason to knock up a simple mode for highlighting them.
fasta.el was the result.

While I doubt it was or is of much use to others, it helped me better understand simple font-locking in Emacs Lisp, and also made some buffers look a little less boring when I was messing with test data.
As for this update: it's the usual stuff of cleaning up deprecated uses of
setf, mostly.
If bioinformatics-related Emacs Lisp code written by
a non-bioinformatician is your thing, you might also find
2bit.el of interest too. Much like
fasta.el it too probably doesn't have a practical use, but it sure was fun
to write and taught me a few things along the way; it also sort of goes
hand-in-hand with fasta.el
too.
-1:-- fasta.el v1.1 (Post Dave Pearson)--L0--C0--2026-04-08T10:25:43.000Z
Conventional file managers have conditioned me to expect that a single left-button mouse click (<mouse-1>) will select a file (or directory) and double-click will open it. This is not the default behavior of Dired, where a single click is an open action. I find this far too twitchy for my taste.
This post shows how to make Dired mouse interaction align with a conventional file manager where:
Single-click on a file or directory will move the point to it, making it the implicit target for any subsequent Dired command.
Left double-click on file or directory will open it.
Selecting multiple files is emulated using Dired marking, in this case using the binding M-<mouse-1> to toggle marking a file.
The first two points above can be addressed with the global variable mouse-1-click-follows-link. Dired uses this variable to control its mouse behavior, but we don’t want to change it everywhere, just for Dired buffers. This can be implemented by setting mouse-1-click-follows-link locally as a hook to dired-mode-hook:
1 2 3 4 | |
To address multiple file selection, we can define a function cc/dired-mouse-toggle-mark and bind it to M-<mouse-1>.
1 2 3 4 5 6 7 8 9 10 | |
Coupled with Anju support for a Dired specific context menu and many basic file manager operations can be done in Dired via mouse with minimal fuss.
-1:-- Calming Mouse Interaction in Dired (Post Charles Choi)--L0--C0--2026-04-07T20:25:00.000Z
The other day, I wrote about repeat mode. My take was that it was a way repeating certain commands without having to retype their, possibly, complex prefixes. All of that is true but as Karthik informed me in a comment, there is much, much more to repeat mode than simply repeating commands.
It is a way, he says, of grouping a set of related commands together into a sort of mode. Thus, there is more to Ctrl+x } than simply repeating the enlarge window command. Once you type Ctrl+x you can type any of {, }, ^, v to resize the window in any direction. The Ctrl+x enables a keymap with those four single keys to resize the current window, defining, in effect, a “resize window mode”.
Four years ago, Karthik wrote a long post that explains all this and, at least on an intuitive level, how it works. My first thought was to add an update to my post that pointed to Karthik’s and I did that but then I thought that his post was so good that I should devote a new post to it so that anyone who missed it the first time would see it.
Repeat mode really is an excellent facility—Karthik says it’s a cornerstone of his Emacs usage—and every Emacser should be familiar with it. If nothing else, it’s worth enabling repeat mode so that you can use the built in repeat maps. You can see what they are by running the command describe-repeat-maps.
-1:-- Karthik On Repeat Mode (Post Irreal)--L0--C0--2026-04-07T15:59:32.000Z
A quick little refresh of one of my old packages, this time
quiz.el. This is a nice little
distraction when you're working in Emacs, letting you
spin up a quick trivia quiz in a buffer.

It's backed by the Open Trivia Database, so there's a good few subjects, questions, and levels of difficulty to play with.
The only changes I've made to it in this release are the usual clean-ups of
the deprecated uses of setf,
plus I've added q as a binding to the quiz window to quickly quit
the quiz.
I might have to come back and revisit it soon, as it looks like the default face choices could probably do with a rethink, and I can see at the moment that the attempt at a window-wide "header" for each question isn't working any longer. For comparison, here's how the package looked when running back when I first wrote it back in 2017:

Leaving aside the fact that I was still running a very light Emacs then, those question headers are displaying differently, with the background colour no longer spanning the width of the window. I'd like to understand why.
-1:-- quiz.el v1.6 (Post Dave Pearson)--L0--C0--2026-04-07T08:18:23.000Z
Good artists borrow, great artists steal.
– Pablo Picasso
After spending the past couple of weeks updating Prelude and my personal Emacs config, I figured it wouldn’t hurt to see what the competition has been up to. I hadn’t done a proper survey of other people’s configs in years, and the Emacs landscape has changed quite a bit since the last time I looked.
So I went through Doom Emacs, Purcell’s emacs.d, Centaur Emacs, Prot’s dotfiles, and a handful of others. Here are some of the most interesting things I found – settings and tricks that I either didn’t know about or had forgotten about entirely.
If you don’t edit right-to-left languages (Arabic, Hebrew, etc.), Emacs is doing a bunch of work on every redisplay cycle for nothing. These settings tell Emacs to assume left-to-right text everywhere and skip the bidirectional parenthesis algorithm:
(setq-default bidi-display-reordering 'left-to-right
bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)
The difference is hard to measure in small buffers, but in large files (think multi-thousand-line JSON or log files) it adds up. Doom enables this unconditionally and I’ve never seen anyone complain about it.
Emacs normally fontifies (syntax-highlights) text even while you’re actively typing. This can cause micro-stutters, especially in tree-sitter modes or large buffers. One setting fixes it:
(setq redisplay-skip-fontification-on-input t)
Emacs will defer fontification until you stop typing. In practice you never notice the delay – the highlighting catches up instantly – but scrolling and typing may feel smoother.
The default read-process-output-max is 64KB, which is still quite
conservative. Modern LSP servers like rust-analyzer or clangd routinely
send multi-megabyte responses. Bumping this reduces the number of
read calls Emacs has to make:
(setq read-process-output-max (* 4 1024 1024)) ; 4MB
If you use eglot (or lsp-mode), this is basically free performance. Three of the most popular configs out there all set it – that should tell you something.
Note: I’m really surprised I didn’t discover this one sooner. Probably that’s because I rarely work on big projects these days.
If you have several windows visible, Emacs draws a cursor in each of them – even the ones you’re not working in. It also highlights selections in non-focused windows. Two settings to stop that:
(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil)
This is mostly a visual preference (I don’t mind the phantom cursors but I know some people find them distracting), but it also reduces rendering work.
All four of these performance settings are safe to add unconditionally – they have no downsides for the vast majority of users.
Here’s a scenario: you copy a URL from your browser, switch to Emacs,
kill a line with C-k, and then try to yank the URL you copied earlier with C-y.
Gone. The kill replaced it on the clipboard.
This setting makes Emacs save the existing clipboard content into the kill ring before overwriting it:
(setq save-interprogram-paste-before-kill t)
Now C-y gets the kill, and M-y gets you back to the URL. Such a
small thing, but it eliminates a genuinely annoying problem.
Kill the same line three times and you get three identical entries in the kill ring, wasting slots. This deduplicates them:
(setq kill-do-not-save-duplicates t)
Most configs that use savehist-mode only persist search rings. But
savehist can save any variable – including the kill ring. Add it
and you get clipboard history that survives restarts:
(setq savehist-additional-variables
'(search-ring regexp-search-ring kill-ring))
One thing to watch out for: the kill ring can accumulate text properties (fonts, overlays, etc.) that bloat the savehist file. Doom strips them before saving:
(add-hook 'savehist-save-hook
(lambda ()
(setq kill-ring
(mapcar #'substring-no-properties
(cl-remove-if-not #'stringp kill-ring)))))
Probably overkill for most people, but it’s good to be aware of if your savehist file starts growing suspiciously large.
If you create a file that starts with #! (a shebang line), it should
be executable. But you always forget to chmod +x it, run the script,
get “Permission denied”, curse, go back, chmod, try again. This hook
does it automatically:
(add-hook 'after-save-hook
#'executable-make-buffer-file-executable-if-script-p)
Save a file with a shebang, and Emacs chmod +xes it for you. One
of those things that should arguably be a default.
Note: Okay, I have to admit I’ve always known this one, but seeing it in so many configs made me want to include it here.
re-builder (M-x re-builder) is an interactive tool for developing
regexps – you type a pattern and see matches highlighted live in the
target buffer. The problem is the default syntax: read. In read
syntax, you have to double-escape everything, so a word boundary is
\\< and a group is \\(...\\). It’s the regexp equivalent of
trying to type with oven mitts on.
Switch to string syntax and things look like normal Emacs regexps:
(setq reb-re-syntax 'string)
Now \< is \< and \(foo\) is \(foo\). Much less painful.
See also: If you want live feedback on the regexp structure as you type it (color-coded groups, character classes, etc.), check out minibuffer-regexp-mode – a new built-in mode in Emacs 30.
Ever had Emacs freeze for a few seconds when you ran
find-file-at-point (or a command that uses it internally)? If the
text under point looks like a hostname – say, something.com in a
comment – ffap tries to ping it to check if it’s reachable. On a
slow or firewalled network, that’s a multi-second hang.
(setq ffap-machine-p-known 'reject)
This tells ffap to never try network lookups. If you actually want to open a remote file, you can type the path explicitly.
When you split a window with C-x 2 or C-x 3, Emacs halves the
current window. If you already have a multi-window layout, this can
produce one awkwardly tiny window while others stay large. With this
setting, all windows in the frame resize proportionally:
(setq window-combination-resize t)
The difference is subtle but makes multi-window layouts feel more balanced without manual resizing.
C-x 1 (Purcell)C-x 1 (delete-other-windows) is the nuclear option – it nukes
your entire window layout to focus on one buffer. Then you spend the
next minute recreating the layout you just destroyed.
With winner-mode and a small wrapper, you can make C-x 1
toggle: press it once to go single-window, press it again to restore
the previous layout:
(winner-mode +1)
(defun toggle-delete-other-windows ()
"Delete other windows in frame if any, or restore previous window config."
(interactive)
(if (and winner-mode
(equal (selected-window) (next-window)))
(winner-undo)
(delete-other-windows)))
(global-set-key (kbd "C-x 1") #'toggle-delete-other-windows)
Just drop this into your config as-is – it’s self-contained. This is one of those tricks where once you have it, you can’t imagine going back.
The mark ring is one of Emacs’s most underused navigation features.
Every time you jump somewhere – isearch, M-<, M->,
goto-line, imenu, and many more – Emacs pushes your old position
onto the mark ring. C-u C-SPC pops it, jumping you back.
The annoyance: you need C-u C-SPC every single time. With this
setting, after the first C-u C-SPC you can keep pressing just
C-SPC to continue popping:
(setq set-mark-command-repeat-pop t)
This pairs beautifully with repeat-mode if you have it enabled
(and you should – see my earlier post on repeat-mode).
save-place-mode is great – it remembers where you were in each file
and jumps back there when you reopen it. The problem is that it can
leave your cursor on the last visible line of the window, which is
disorienting. This advice recenters the view after the jump:
(advice-add 'save-place-find-file-hook :after
(lambda (&rest _)
(when buffer-file-name (ignore-errors (recenter)))))
Small thing, but it makes reopening files feel much more natural.
When you press C-h f or C-h v, Emacs opens the help buffer but
leaves your cursor in the original window. You almost always want to
read the help right away, so you end up pressing C-x o every
single time. This fixes it:
(setq help-window-select t)
Bonus: Many of the configs I surveyed also use built-in lazy
isearch counting (showing “match N of M” in the minibuffer) instead
of third-party packages like anzu. I recently wrote about that in
a dedicated post.
The funny thing about all of this is how much overlap there is between
configs. Half of these tricks appear in three or four of the configs I
surveyed. At this point I’m convinced there are about 200 essential
Emacs settings floating around in the collective unconscious, and
every serious config independently converges on roughly the same
subset. Picasso was right – we all steal from each other, and the
kill ring makes it embarrassingly easy. M-w and move on.
That’s all I have for you today! Keep hacking!
-1:-- Stealing from the Best Emacs Configs (Post Emacs Redux)--L0--C0--2026-04-07T06:00:00.000Z
While I have been doing a lot of hacking on
blogmore.el, I haven't forgotten
my plan to revisit and refresh some of my older personal packages. This
evening I've paid some attention to
expando.el.
This started life a long time ago, as part of my grab-bag of handy functions that got carried around and copied from machine to machine, until I did a big tidy-up of everything back in 2017 and turned various things into packages that I managed via a self-hosted (well, GitHub pages hosted) package index.
It's a pretty simple but very useful bit of code that lets me quickly
macroexpand a sexp at point and pretty print it into a display window.
I've often found it indispensable when it came to writing my own macros.

This release simply adds a lexical-binding header to the file, and also
adds a q key binding to the resulting view window so that it can
be quickly and easily closed.
Also, as with all my other personal packages, I've swapped away from using
delpa to simply using :vc to pull it in.
(use-package expando
:vc (:url "https://github.com/davep/expando.el" :rev :newest)
:bind
("C-c e" . expando-macro))
Or perhaps I should say...
(progn
(use-package-vc-install
'(expando (:url "https://github.com/davep/expando.el") nil) nil)
(defvar use-package--warning69
#'(lambda (keyword err)
(let
((msg
(format "%s/%s: %s" 'expando keyword
(error-message-string err))))
(display-warning 'use-package msg :error))))
(condition-case err
(progn
(if (fboundp 'expando-macro) nil
(autoload #'expando-macro "expando" nil t))
(let*
((name "C-c e") (key [3 101])
(kmap
(or (if (and nil (symbolp nil)) (symbol-value nil) nil)
global-map))
(kdesc
(cons (if (stringp name) name (key-description name))
(if (symbolp nil) nil 'nil)))
(binding (lookup-key kmap key)))
(require 'bind-key)
(let
((entry (assoc kdesc personal-keybindings))
(details
(list #'expando-macro (if (numberp binding) nil binding))))
(if entry (setcdr entry details)
(add-to-list 'personal-keybindings (cons kdesc details))))
(define-key kmap key #'expando-macro)))
((debug error) (funcall use-package--warning69 :catch err))))
-1:-- expando.el v1.5 (Post Dave Pearson)--L0--C0--2026-04-06T19:43:29.000Z
Despite having bumped it from 2.x to 3.x
yesterday, I'm calling
v4.0 on
blogmore.el today. There's a good
reason for this though. While tinkering with some of the configuration
yesterday, and also answering a configuration question last night, I
realised that it made sense to make some of the internals into public
utility functions.
Now, sure, Emacs Lisp doesn't really have internals in
the private function sense, but I've always liked the approach that a
package-- prefix communicates "internal, might go away" vs package-
which tells me "this is a stable part of the API of this package". With
this in mind I've always tried to write my code using this convention. I did
this with blogmore.el too and a lot of the code had the blogmore--
prefix.
There's plenty of code in there that someone might want to make use of, if they wanted to add their own commands, or do fun things with the configuration. So with this in mind I've "promoted" a bunch of code to being "public" and, in that case, I feel this deserves another major version bump1.
Things that are now part of the "public" interface include:
blogmore-clean-time-stringblogmore-get-frontmatterblogmore-remove-frontmatterblogmore-set-frontmatterblogmore-slugblogmore-toggle-frontmatterblogmore-with-postEach one is documented via its docstring (just a quick
Ctrl+h f function-name RET
away) and hopefully is pretty self-explanatory.
blogmore-with-post is especially handy as it provides a quick and easy way
of pulling information from a post file. So something like this:
(blogmore-with-post "~/write/davep.github.com/content/posts/2026/04/2026-04-05-blogmore-el-v3-1.md"
(list
(blogmore-get-frontmatter "title")
(blogmore-get-frontmatter "date")))
resulting in:
("blogmore.el v3.1" "2026-04-05 20:04:44+0100")
Meaning that this snippet from yesterday's post:
(with-temp-buffer
(insert-file-contents-literally file)
(parse-iso8601-time-string
(blogmore--clean-time-string (blogmore--get-frontmatter-property "date"))))
becomes:
(blogmore-with-post file
(parse-iso8601-time-string
(blogmore-clean-time-string (blogmore-get-frontmatter "date"))))
Not massively different, but it reads better and now all the calls are to the "public API" of the package.
Not all the changes are "promoted internals". I've also added a
blogmore-remove-tag command (and also added it to the transient menu).

I've also changed the way that blogmore-add-tag works so that, now, if
it's called from the transient, it immediately goes back to the tag input
prompt, allowing for another tag to be immediately selected (you can quit
out of this with Ctrl+g). Removal of a tag works in a
similar way, making things a lot quicker.
I've also added some extra tests too, which makes it even easier for me to make future changes with confidence. The more I work with it the more I appreciate that ERT is available.
Ordinarily it shouldn't matter as the public interface isn't changing, but some of the "internal" functions had been mentioned as options for configuration. ↩
-1:-- blogmore.el v4.0 (Post Dave Pearson)--L0--C0--2026-04-06T16:04:16.000Z
I know it’s scary, but sometimes you have to delete files, and once they’re gone—they’re gone. I guess that was why GUI systems invented the trash folder. The trash is a safe place to store files you want to delete, just in case you made a mistake.
In the UNIXy and Emacs worlds, once you delete that file, it’s gone, so you better have your story straight.
In UNIXy world, you can delete files with the trusty rm (remove) command, as follows:
rm file.txt rm -rf directory/
Of course, once you have run that command, the file will be totally obliterated with no trace.
While you should never do this, you should be aware of the dreaded nuclear solution to delete your whole filesystem from the root level:
rm -rf /*
The Emacs way of deleting files will also obliterate files entirely, but with dired you get the added safety of marking files first with the d key. Once you have made your selections, you can use the x key to execute the deletion operation
You can also use capital D for immediate deletion.
Likewise, if you wanted to run deletions interactively, you can run the functions directly:
M-x delete-fileM-x delete-directorySo that’s how you can delete files using your command line, in UNIXy world, or your “dired” directory editor in Emacs.
The post The Emacs Way: Deleting Files appeared first on Chris Maiorana.
-1:-- The Emacs Way: Deleting Files (Post Chris Maiorana)--L0--C0--2026-04-06T16:00:30.000Z
View in the Internet Archive, watch or comment on YouTube, or email me.
Chapters:
Thanks for your patience with the audio issues! At some point, I need to work out the contention between all the different processes that I want to be listening to the audio from my mic. =)
In this livestream, I categorize Emacs News for 2026-04-06, show epwgraph for managing Pipewire connections from Emacs, and share some of my language learning workflows.
You can e-mail me at sacha@sachachua.com.
-1:-- YE12: Categorizing Emacs News, epwgraph, languages (Post Sacha Chua)--L0--C0--2026-04-06T14:36:57.000Z
Bozhidar Batsov has a post that mentions how M-x shows a lot of commands many of which make no sense in the current context. This has never bothered me because after inputting a few (fuzzy) letters, the display converges on the command I’m looking for. Others, are not so sanguine and find the long list annoying.
It turns out that there’s an easy fix for this: read-extended-command-predicate. As of Emacs 28, You can set this variable to one of 3 values—or nil—to control the filtering of commands in the candidate list. These are:
command-completion-default-include-pcommand-completion-using-modes-and-keymaps-pcommand-completion-using-modes-pThe first option is the most conservative and general and is what Batsov recommends for everyday use.
Batsov also explains how functions can declare what mode or modes they’re appropriate for. That’s simply a matter of listing them in the interactive declaration. There are a lot more details in Batsov’s post so be sure to take a look. The read-extended-command-predicate mechanism is similar to the execute-extended-command-for-buffer mechanism that I wrote about previously.
As I said, the long list doesn’t bother me but if it annoys you, this may be the answer.
Update : prefix → predicate
-1:-- Read Extended Command Predicate (Post Irreal)--L0--C0--2026-04-06T14:22:46.000Z
Way back in 2013 I wrote about the deprecation of
flet and how
noflet could fill the gap. Thirteen years later, it’s probably time
for a proper overview of what replaced flet in cl-lib and when to
use each option.
Emacs Lisp doesn’t have a built-in way to define local functions (the
way let defines local variables), so cl-lib provides several macros
for this. If you’ve ever been confused by cl-flet, cl-labels, and
cl-letf – you’re not alone. The naming doesn’t make the distinctions
obvious, and the documentation is a bit dry. Let’s try to fix that.
The original flet (from the old cl package) let you temporarily
override a function’s definition. It worked by swapping out the
function’s symbol-function cell and restoring it when the body
finished – essentially a dynamic let for functions:
;; Old-style flet (deprecated since Emacs 24.3)
(flet ((some-function () "overridden result"))
;; Everything here, including called functions, sees the override
(some-function))
This was very handy for testing (stubbing impure functions), but it conflated two different things into one macro:
When cl was reorganized into cl-lib in Emacs 24.3, flet was
split into separate macros for each use case. This also brought the
lexical variants in line with Common Lisp semantics, where flet and
labels are lexically scoped.
cl-flet binds function names lexically within its body. The key
thing to understand is that the binding is only visible in the body
forms – not inside the function’s own definition, and not to any
functions you call:
(cl-flet ((double (n) (* n 2)))
(double 21)) ; => 42
Because it’s lexical, cl-flet cannot override functions seen by
other code:
(defun my-helper () (+ 1 2))
(defun my-caller () (my-helper))
(cl-flet ((my-helper () 999))
(my-caller)) ; => 3, NOT 999!
my-caller still sees the original my-helper. This is the
fundamental difference from the old flet.
There’s also cl-flet*, which is to cl-flet what let* is to
let – each binding can reference the ones before it.
Use cl-flet when you just need a simple local helper and don’t need
recursion. Think of it as let for functions.
cl-labels is like cl-flet, but the function is visible inside
its own body and inside the bodies of sibling bindings. This makes
recursion and mutual recursion possible:
(cl-labels ((factorial (n)
(if (<= n 1) 1
(* n (factorial (- n 1))))))
(factorial 10)) ; => 3628800
This would blow up with cl-flet because factorial wouldn’t be
defined inside its own body.
Mutual recursion works too:
(cl-labels ((my-even-p (n) (if (= n 0) t (my-odd-p (- n 1))))
(my-odd-p (n) (if (= n 0) nil (my-even-p (- n 1)))))
(list (my-even-p 4) (my-odd-p 3))) ; => (t t)
Use cl-labels when your local functions need to call themselves or
each other.
Note: cl-labels requires lexical-binding to be t in the file (which
it really should be for any modern Emacs Lisp code).
This is the one that actually replaces the old flet’s dynamic
behavior. cl-letf temporarily rebinds a generalized place (anything
setf can handle) and restores it on exit:
(defun my-helper () (+ 1 2))
(defun my-caller () (my-helper))
(cl-letf (((symbol-function 'my-helper) (lambda () 999)))
(my-caller)) ; => 999
Now my-caller does see the override, because cl-letf modifies the
actual symbol-function cell of my-helper – just like the old flet did.
The original definition is restored when the body exits, even on error.
The syntax is a bit verbose because cl-letf isn’t specific to
functions – it’s a general-purpose temporary binding macro for any
setf-able place. (symbol-function 'name) is a “generalized
variable” that refers to the function stored in a symbol’s function
cell – it’s just one of many places cl-letf can bind. For example:
;; Temporarily silence messages
(cl-letf (((symbol-function 'message) #'ignore))
(do-something-noisy))
Use cl-letf when you need the old dynamic flet behavior – typically
for testing (stubbing functions) or temporarily suppressing/redirecting
behavior.
| Scope | Recursion | Overrides global? | |
|---|---|---|---|
cl-flet |
Lexical | No | No |
cl-labels |
Lexical | Yes | No |
cl-letf |
Dynamic | N/A | Yes |
In other words:
cl-flet for local helpers. It’s the simplest and
most predictable.cl-labels when you need recursion or mutual
recursion in local functions.cl-letf only when you genuinely need dynamic override –
mainly in tests. Modifying global function cells is a sharp tool and
it’s not thread-safe, so keep it contained.That’s all I have for you today. Keep hacking!
-1:-- The Many Faces of flet: cl-flet, cl-labels, and cl-letf (Post Emacs Redux)--L0--C0--2026-04-06T12:00:00.000Z
There's a lot of buzz around the remote code execution thing that involves Git, but it seems to be more of a Git issue than an Emacs one. This might be a workaround if you want, and in the meantime, don't check out git repositories you don't trust. There's no page for the Emacs Carnival for April yet, but you can start thinking about the theme of "newbies/starter kits" already, and I'm sure Cena or someone will round things up afterwards. Enjoy!
Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!
You can e-mail me at sacha@sachachua.com.
-1:-- 2026-04-06 Emacs news (Post Sacha Chua)--L0--C0--2026-04-06T12:00:00.000Z
This is the third article in a small series inspired by my recent cleanup of Prelude and my personal Emacs configuration, following the ones on repeat-mode and read-extended-command-predicate. I’ve been going through the Emacs 28-30 changelogs for features I had ignored so far, and this one from Emacs 30 immediately caught my eye.
Writing Emacs regexps has always been a bit of a dark art. Between the
double-escaped backslashes and the various group syntaxes (\(...\),
\(?:...\), \(?N:...\)), it’s easy to lose track of what you’re
actually matching. You type something into query-replace-regexp,
press RET, and hope for the best.
Emacs 30 added minibuffer-regexp-mode, a minor mode that gives you
live visual feedback as you compose a regexp in the minibuffer:
(minibuffer-regexp-mode 1)
When active, the mode highlights the structure of your regexp as you type it in the minibuffer. Capture groups, character classes, and other constructs get color-coded so you can see at a glance whether your grouping is right.
I find this particularly useful when building a regexp with
multiple groups for query-replace-regexp, where you need to get the
group numbering right for the replacement string (e.g., \1, \2).
The visual feedback makes it obvious which group is which.
You might be wondering how this compares to re-builder (M-x
re-builder). They’re complementary, really. re-builder shows
matches in the buffer as you type a regexp in a dedicated editing
window – great for developing complex patterns against actual text.
minibuffer-regexp-mode, on the other hand, highlights the regexp itself
in the minibuffer. It kicks in automatically whenever you’re prompted
for a regexp (e.g., isearch-forward-regexp, query-replace-regexp,
keep-lines, etc.).
One helps you see what your regexp matches; the other helps you see what your regexp says. I’d suggest using both.
That’s all I have for you today. Keep hacking!
-1:-- Live Regexp Feedback with minibuffer-regexp-mode (Post Emacs Redux)--L0--C0--2026-04-06T09:00:00.000Z
Raw link: https://www.youtube.com/watch?v=Vunpn7ovEOc
[ The stream will be recorded. You can watch it later. ]
Tonight I will work on my denote package. There is a feature branch
I implemented this morning and am now ready to continue refining the
code. The immediate goals:
denote-dired command and all
its ancillary functions.denote-grep, denote-backlinks,
denote-query-contents-link).I expect the stream to go on for 2-3 hours, but we will see.
I will keep the chat open in case there are any comments. I am happy to respond to them.
-1:-- Emacs live stream for writing Denote tests and more on Monday 6 April @ 20:00 Europe/Athens (Post Protesilaos Stavrou)--L0--C0--2026-04-06T00:00:00.000Z
Watch on Internet Archive, watch/comment on YouTube, download captions, or email me
Where can you define an Emacs Lisp function so
that you can use find-function to jump to it
again later?
eval-defun (hint)nope
C-c ' (org-edit-special) inside the block; execute the defun with C-M-x (eval-defun), C-x C-e (eval-last-sexp), or eval-buffer.
(defun my-test-1 () (message "Hello"))
B: In an Org Mode file by executing the block with C-c C-c (hint)nope
(defun my-test-2 () (message "Hello"))
C: In a .el file (hint)yup
file:///tmp/test-search-function.el : execute the defun with C-M-x (eval-defun), C-x C-e (eval-last-sexp), or eval-buffer
D: In a scratch buffer, other temporary buffer, or really any buffer thanks to eval-last-sexp (hint)nope
(defun my-test-4 () (message "Hello"))
Only option C works - it's gotta be in an .el file for
find-function to find it. But I love jumping to
function definitions using find-function or
lispy-goto-symbol (which is bound to M-. if
you use lispy and set up lispy-mode) so
that I can look at or change how something works.
It can be a little frustrating when I try to jump
to a definition and it says, "Don't know where
blahblahblah is defined." I just defined it five
minutes ago! It's there in one of my other
buffers, don't make me look for it myself.
Probably this will get fixed in Emacs core
someday, but no worries, we can work around it
today with a little bit of advice.
I did some digging around in the source code.
Turns out that symbol-file can't find the
function definition in the load-history variable
if you're not in a .el file, so
find-function-search-for-symbol gets called with
nil for the library, which causes the error.
(emacs:subr.el)
I wrote some advice that searches in any open
emacs-lisp-mode buffers or in a list of other
files, like my Emacs configuration.
This is how I activate it:
(setq sacha-elisp-find-function-search-extra '("~/sync/emacs/Sacha.org"))
(advice-add 'find-function-search-for-symbol :around #'sacha-elisp-find-function-search-for-symbol)
Now I should be able to jump to all those functions wherever they're defined.
(my-test-1)
(my-test-2)
(my-test-3)
(my-test-4)
Note that by default, M-. in emacs-lisp-mode uses xref-find-definitions, which seems to really want files. I haven't figured out a good workaround for that yet, but lispy-mode makes M-. work and gives me a bunch of other great shortcuts, so I'd recommend checking that out.
Here's the source code for the find function thing:
(defvar sacha-elisp-find-function-search-extra
nil
"List of filenames to search for functions.")
;;;###autoload
(defun sacha-elisp-find-function-search-for-symbol (fn symbol type library &rest _)
"Find SYMBOL with TYPE in Emacs Lisp buffers or `sacha-find-function-search-extra'.
Prioritize buffers that do not have associated files, such as Org Src
buffers or *scratch*. Note that the fallback search uses \"^([^ )]+\" so that
it isn't confused by preceding forms.
If LIBRARY is specified, fall back to FN.
Activate this with:
(advice-add 'find-function-search-for-symbol
:around #'sacha-org-babel-find-function-search-for-symbol-in-dotemacs)"
(if (null library)
;; Could not find library; search my-dotemacs-file just in case
(progn
(while (and (symbolp symbol) (get symbol 'definition-name))
(setq symbol (get symbol 'definition-name)))
(catch 'found
(mapc
(lambda (buffer-or-file)
(with-current-buffer (if (bufferp buffer-or-file)
buffer-or-file
(find-file-noselect buffer-or-file))
(let* ((regexp-symbol
(or (and (symbolp symbol)
(alist-get type (get symbol 'find-function-type-alist)))
(alist-get type find-function-regexp-alist)))
(form-matcher-factory
(and (functionp (cdr-safe regexp-symbol))
(cdr regexp-symbol)))
(regexp-symbol (if form-matcher-factory
(car regexp-symbol)
regexp-symbol))
(case-fold-search)
(regexp (if (functionp regexp-symbol) regexp-symbol
(format (symbol-value regexp-symbol)
;; Entry for ` (backquote) macro in loaddefs.el,
;; (defalias (quote \`)..., has a \ but
;; (symbol-name symbol) doesn't. Add an
;; optional \ to catch this.
(concat "\\\\?"
(regexp-quote (symbol-name symbol)))))))
(save-restriction
(widen)
(with-syntax-table emacs-lisp-mode-syntax-table
(goto-char (point-min))
(if (if (functionp regexp)
(funcall regexp symbol)
(or (re-search-forward regexp nil t)
;; `regexp' matches definitions using known forms like
;; `defun', or `defvar'. But some functions/variables
;; are defined using special macros (or functions), so
;; if `regexp' can't find the definition, we look for
;; something of the form "(SOMETHING <symbol> ...)".
;; This fails to distinguish function definitions from
;; variable declarations (or even uses thereof), but is
;; a good pragmatic fallback.
(re-search-forward
(concat "^([^ )]+" find-function-space-re "['(]?"
(regexp-quote (symbol-name symbol))
"\\_>")
nil t)))
(progn
(beginning-of-line)
(throw 'found
(cons (current-buffer) (point))))
(when-let* ((find-expanded
(when (trusted-content-p)
(find-function--search-by-expanding-macros
(current-buffer) symbol type
form-matcher-factory))))
(throw 'found
(cons (current-buffer)
find-expanded)))))))))
(delq nil
(append
(sort
(match-buffers '(derived-mode . emacs-lisp-mode))
:key (lambda (o) (or (buffer-file-name o) "")))
sacha-elisp-find-function-search-extra)))))
(funcall fn symbol type library)))
I even figured out how to write tests for it:
(ert-deftest sacha-elisp--find-function-search-for-symbol--in-buffer ()
(let ((sym (make-temp-name "--test-fn"))
buffer)
(unwind-protect
(with-temp-buffer
(emacs-lisp-mode)
(insert (format ";; Comment\n(defun %s () (message \"Hello\"))" sym))
(eval-last-sexp nil)
(setq buffer (current-buffer))
(with-temp-buffer
(let ((pos (sacha-elisp-find-function-search-for-symbol nil (intern sym) nil nil)))
(should (equal (car pos) buffer))
(should (equal (cdr pos) 12)))))
(fmakunbound (intern sym)))))
(ert-deftest sacha-elisp--find-function-search-for-symbol--in-file ()
(let* ((sym (make-temp-name "--test-fn"))
(temp-file (make-temp-file
"test-" nil ".org"
(format
"#+begin_src emacs-lisp\n;; Comment\n(defun %s () (message \"Hello\"))\n#+end_src"
sym)))
(sacha-elisp-find-function-search-extra (list temp-file))
buffer)
(unwind-protect
(with-temp-buffer
(let ((pos (sacha-elisp-find-function-search-for-symbol nil (intern sym) nil nil)))
(should (equal (buffer-file-name (car pos)) temp-file))
(should (equal (cdr pos) 35))))
(delete-file temp-file))))
You can comment on Mastodon or e-mail me at sacha@sachachua.com.
-1:-- YE11: Fix find-function for Emacs Lisp from org-babel or scratch (Post Sacha Chua)--L0--C0--2026-04-05T21:03:48.000Z
When I first started writing
blogmore.el it was just going to be
a handful of commands that let me spin up a new blog post, and insert the
odd link here and there when needed. Initially it only handled a single
blog, and everything it did was based around how I lay my personal blog out,
and was also very much geared to how I'd made
BlogMore work.
But then I wanted to use it to edit both my personal blog and my photoblog. So then I had to add support for configuring different ways of laying out posts for different blogs, etc.
Still, it was mostly written as a personal tool that worked for my stuff. I tried to make it so that it was easy enough to configure (and let's be fair: it's for Emacs and written in Emacs Lisp, it's kind of hard to not be very configurable if you're happy to get your hands dirty with some coding), but there were still some parts of it that weren't as easy to change as I'd have liked.
Also, when I'd originally added the multi-blog configuration, I'd chosen a
format for the list of blogs that was an assoc-list of pure lists wrapped by
a cl-defstruct to make for easier access. It worked well but was very
positional in its nature.
So when the request came in to be able to have better control over the name of the file when starting a new post, which meant I was going to need to rearrange the structure again, it was time to try and do something about it.
Which is how I'm now on v3.1 (yes, there was a
v3.0 but I quickly
found something in that that needed fixing1). It's a major version bump
because I've totally changed how the blogmore-blogs variable holds the
data.
From now on I'm using
EIEIO to
create a class that holds all of the data for a given blog. This, I believe,
makes the code easier to read and should also make it more resilient to the
addition of any new properties. Also thanks to how such classes can work
with the customize
system
the customize experience remains pretty much the same too.
Personally I don't use the customize UI, instead I declare everything
via use-package. As of the time of writing my declaration for blogmore
looks like this:
(use-package blogmore
:ensure t
:defer t
:vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
:init
(add-hook 'blogmore-new-post-hook #'end-it)
(blogmore-work-on "blog.davep.org")
:custom
(blogmore-blogs
(list
(blogmore-blog
:title "blog.davep.org"
:posts-directory "~/write/davep.github.com/content/posts/"
:post-subdirectory-function (lambda () (format-time-string "%Y/%m/")))
(blogmore-blog
:title "seen-by.davep.dev"
:posts-directory "~/write/seen-by/content/posts/")))
:bind
("<f12> b" . blogmore))
There's a bunch of other changes and tweaks under the hood in this release
too. All of these should come together to make blogmore.el a little more
configurable than it was before. I think, to get the best out of it, anyone
wanting to configure it "just so" for their purposes will still have to do a
little bit of work, which makes me want to spend some time soon writing some
proper documentation, complete with examples of how you might achieve
different things.
One big change I've made under the hood is to the code that is used when you
insert a link to a post (blogmore-link-post). When this runs it lets me
pick a file in your filesystem that is a post from my currently-active blog.
Once it has the filename it needs to turn it into a root-relative link. So
this:
~/write/davep.github.com/content/posts/2026/04/01/2026-04-01-foo.md
needs to become:
/2026/04/01/foo.html
Until now I just did some regexp faffing that took the 2026-04-01- at the
start of the filename and swapped each - for /. Nice and easy. Simple
enough to code up and get things working a few weeks back. Not at all
flexible.
So as a proof-of-concept of how sophisticated someone could get with
configuring this I've changed blogmore-default-post-maker-function from
this:
(defcustom blogmore-default-post-maker-function
(lambda (file)
(replace-regexp-in-string
(rx bos (group (+ digit)) "-" (group (+ digit)) "-" (group (+ digit)) "-")
"\\1/\\2/\\3/"
(file-name-base (file-name-sans-extension file))))
"Default function to generate a link for a blog post from its filename."
:type 'function
:group 'blogmore)
and turned it into this:
(defcustom blogmore-default-post-maker-function
(lambda (file)
(format
"%s/%s"
(format-time-string
"%Y/%m/%d"
(with-temp-buffer
(insert-file-contents-literally file)
(parse-iso8601-time-string
(blogmore--clean-time-string (blogmore--get-frontmatter-property "date")))))
(replace-regexp-in-string
(rx bol (= 4 digit) "-" (= 2 digit) "-" (= 2 digit) "-")
""
(file-name-base file))))
"Default function to generate a link for a blog post from its filename."
:type 'function
:group 'blogmore)
So whereas before I was simply messing with the file's name, now I'm loading
the date frontmatter from the chosen file and using that to create the
date portion of the path in the URL that I use on my blog. The benefit here
is that someone might want to keep the date portion of the path in the URL,
but never want it as part of the Markdown source file's name, and so this
change means they can call the file anything they want; it doesn't look at
that but instead uses the actual date from the post's frontmatter.
I think this nicely demonstrates that, especially thanks to how powerful
Emacs and Emacs Lisp are, it's fairly easy to make blogmore.el work just
the way you want. I think I've provided almost all the hooks necessary to
configure it all sorts of ways, but if you do happen to use it and run into
something I might have missed, let me
know.
I have at least two slightly different date formats going on in my
Markdown and Emacs' parse-iso8601-time-string is kind of picky. So I
added a function to try and clean that
up. ↩
-1:-- blogmore.el v3.1 (Post Dave Pearson)--L0--C0--2026-04-05T19:04:44.000Z
As many you know, I was a Vim user for a couple of decades before I found the one true editor. One of the things I miss from Vim is the easy to use repeat command. If you’re in command mode and perform some command, you can repeat that command by simply pressing .. Sadly, Emacs doesn’t have anything similar. There are a couple of repeat commands: one for simple commands and another for “complex” commands but I was never able to internalize them.
Happily, there is repeat mode that allows you to repeat a command by omitting the prefix and pressing the last key. For example, if you want to repeat the command to enlarge a window horizontally (Ctrl+x }) you can simply repeat the } as many times as needed.
As Bozhidar Batsov explains, this doesn’t work with every key sequence but it is easy to add the functionality to multikey sequences. The TL;DR is that you have to provide a special keymap that maps the last key(s) to their action(s). Batsov has a worked example to show you how to do it. It’s not very hard. But, it turns out, it can be even easier. Omar Antolín explains in a comment that there is also a macro, defvar-keymap that abstracts all the boilerplate away and makes it really easy to define and install a new repeat map.
Repeat mode still isn’t as nice as Vim’s repeat but it can reduce the friction of repeating certain commands. Take a look at Batsov’s post for the details.
Update :
Karthik Chikmagalur has an excellent write-up on repeat mode that shows it’s useful for much more than simply repeating a command. See his comment below.
-1:-- Repeat Mode (Post Irreal)--L0--C0--2026-04-05T14:31:14.000Z
It was really fun to learn about
advising Lisp functions
to extend functionality in Emacs. My first use case was to run a custom function every time a certain function in
Bastian Bechtold’s
org-static-blog is called. Of course, I could customize that function directly in my own fork, but Lisp advice allows you to modify functions without clobbering them directly. This approach has aesthetic and practical advantages.
But I’ve struggled to understand the concepts and implementation of advice. Today I posted to Mastodon about how excited I was to get my first working use, and Philip asked me to share the code.
My problem was specifying HTML boilerplate that org-static-blog puts in each post when it publishes all the static files, using string variables like org-static-blog-page-header. These strings are complex enough that I put them in their own files, like these lines in the #header.html file that specifies the page metadata:
<script type="module" src="https://esm.sh/emfed@"></script> <meta name="author" content="James Endres Howell"> <meta name="referrer" content="no-referrer"> <link href="static/style.css" rel="stylesheet" type="text/css" /> <meta name="fediverse:creator" content="@jameshowell@fediscience.org"> <meta property="og:image" content="https://jamesendreshowell.com/static/education-of-james-endres-howell.png">
First, Stack Exchange and I solved the problem of reading a file into a string. (?! How is this not a native function!? Maybe I missed something obvious.)
(defun jeh/file-to-string (file) "Return a string that is the contents of FILE." (with-temp-buffer (insert-file-contents file) (buffer-string)))
And then, for example:
(setq org-static-blog-page-header (jeh/file-to-string (expand-file-name "#header.html" org-static-blog-template-blocks-directory)))
The unscratched itch was that every time I edit one of these files, I always, but always, forget to update the appropriate variable with the contents of the file! And so publishing doesn’t reflect the changes, and I get confused, and then I remember….
Here is the solution:
(defun jeh/org-static-blog-read-templates (&rest ignore) "Set org-static-blog-page -header, -preamble, -postamble variables by reading files from `org-static-blog-template-blocks-directory'." (setq org-static-blog-page-header ;;; HTML to put in the <head> of each page. (jeh/file-to-string (expand-file-name "#header.html" org-static-blog-template-blocks-directory))) (setq org-static-blog-page-preamble ;;; HTML to put before the content of each page. (jeh/file-to-string (expand-file-name "#preamble.html" org-static-blog-template-blocks-directory))) (setq org-static-blog-page-postamble ;;; HTML to put after the content of each page. (format (jeh/file-to-string (expand-file-name "#postamble.html" org-static-blog-template-blocks-directory)) (number-to-string emacs-major-version) (number-to-string emacs-minor-version) org-version))) ;;; Re-read the template files before publishing, ;;; so changes will be included in output. (advice-add #'org-static-blog-publish :before #'jeh/org-static-blog-read-templates)
The function jeh/org-static-blog-read-templates sets the variables
org-static-blog-page-header,
org-static-blog-page-preamble, and
org-static-blog-page-postamble to the contents of the appropriate files. Making that function a hook to the function which generates all the static pages, org-static-blog-publish, solves my problem. But there is no hook for it! I had a suspicion that add-advice could give me the same result, and—I hope you’re sitting down—I
read the fine manual
and learned that the syntax of the last line accomplishes that very thing.
Of course, advice-add (and related functions) can do much more! Maybe as I learn I will be able to customize functions from other packages without just banging on a local fork.
-1:-- My first advice! (in Emacs Lisp) (Post James Endres Howell)--L0--C0--2026-04-04T22:41:00.000Z
There's no question that the experiment that is BlogMore has resulted in me blogging more. Although my previous setup wasn't exactly all friction, there's something about "owning" most of the tools and really knowing how they work, and being able to quickly modify them so they work "just so", that makes me more inclined to quickly write something up.
I can see this if I look at the numbers in the archive for this
blog. In March alone I wrote 43 posts; that's more
than I wrote in any whole year, other than 2023. While I suspect
this will start to calm down as work on BlogMore and
blogmore.el settles down, I sense
I'll be writing a bit more often for some time to come.
Because of this I decided to do a little bit of housekeeping on the posts directory in the blog's source repository. Originally I had the Markdown source for every post all in one directory. Then last month I broke those down by year. Then earlier today, seeing how this year is going, I decided to break 2026 down by month.
Then I realised I had a problem in blogmore.el. It assumed that the
Markdown file for a new post (blogmore-new) would always be created in a
subdirectory named after the year, underneath the defined posts directory.
Until today that was the case1, but now I wanted it to work
differently.
So this is why I'm making a second release in one day: I added the ability
to configure the subdirectory where a new post is
created. I've changed the
default now so that it assumes the user wants the subdirectory to be
YYYY/MM/DD (because more granular feels like the right default). In my
case I don't want that, I just want YYYY/MM, but now I can configure that.
The value that is set is a function that returns the name of the
subdirectory, so in the case of my blog I have it as:
(lambda () (format-time-string "%Y/%m/"))
On the other hand, for my photoblog I want the
full date as a subdirectory so I can leave it as the default. The whole
use-package for blogmore now looks like:
(use-package blogmore
:ensure t
:defer t
:vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
:init
(add-hook 'blogmore-new-post-hook #'end-it)
(blogmore-work-on "blog.davep.org")
:custom
(blogmore-blogs
'(("blog.davep.org"
;; Root directory for posts.
"~/write/davep.github.com/content/posts/"
;; Subdirectory for new posts, relative to the root.
(lambda () (format-time-string "%Y/%m/")))
("seen-by.davep.dev"
;; Root directory for posts.
"~/write/seen-by/content/posts/")))
:bind
("<f12> b" . blogmore))
Technically this is a breaking change because it bumps the meaning of each
"position" in the values within
blogmore-blogs.
However, in my case, because I was only ever defining the blog name and the
top-level directory for the posts (both mandatory), this didn't break
anything; I also strongly suspect nobody else is using this so I very much
doubt I'm messing with someone else's setup2. If I have I apologise; do
let me know.
Anyway, all of this goes to explain why the heck I made two releases of the same package back to back in the same day. This is what happens when my namesake is having fun outside and so I just want to sit on the sofa, hack on some code, and watch the chaos in the garden.
-1:-- blogmore.el v2.7 (Post Dave Pearson)--L0--C0--2026-04-04T18:24:39.000Z
Irreal readers can’t help but know who Álvaro Ramírez is, if for no other reason that I’m always blathering on about his Journelly app that is my most used and useful iOS app. But of course, Ramírez has many other excellent apps, many of which are Emacs packages. In particular, there is his dwim-shell-command that allows you to automate shell commands—particularly those with complex invocations—from the comfort of Emacs. There’s also his take on music players, an uniform AI access app, and a lot more besides.
You can check out his blog posts or take a look at his iOS/macOS/Emacs applications. He’s been an independent developer for some time but now he’s taken on a new role: fatherhood. He’s now the proud dad of a new son. He tells us that his output may slow down a bit because, after all, he has to start working on his son’s init.el.
-1:-- Congratulations Álvaro (Post Irreal)--L0--C0--2026-04-04T14:19:44.000Z
Like most people, I imagine, I first ran into transient when first using magit. I took to it pretty quickly and it's always made sense to me as a user interface. But... I've never used it for any code I've ever written.
I think, incorrectly, I'd half assumed it was going to be some faff to set up, and of course for a good while it wasn't part of Emacs anyway. Given this, I'd always had it filed under the heading "that's so neat I'll give it a go one day but not at the moment".
Meanwhile... ever since I did the last big revamp of my Emacs configuration, I found myself leaning into a command binding approach that does the whole prefix-letter-letter thing. For reasons I can't actually remember I fell into the habit of using F121 as my chosen prefix key. As such, over the past 10 or so years (since I greatly overhauled my Emacs setup), I've got into setting up bindings for commands that follow this prefix convention.
So when I created blogmore.el I
set up the commands following this pattern.
(use-package blogmore
:ensure t
:defer t
:vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
:init
(add-hook 'blogmore-new-post-hook #'end-it)
(blogmore-work-on "blog.davep.org")
:custom
(blogmore-blogs
'(("blog.davep.org" "~/write/davep.github.com/content/posts/")
("seen-by.davep.dev" "~/write/seen-by/content/posts/")))
:bind
("<f12> b b" . blogmore-work-on)
("<f12> b n" . blogmore-new)
("<f12> b e" . blogmore-edit)
("<f12> b d" . blogmore-toggle-draft)
("<f12> b s c" . blogmore-set-category)
("<f12> b a t" . blogmore-add-tag)
("<f12> b u d" . blogmore-update-date)
("<f12> b u m" . blogmore-update-modified)
("<f12> b l p" . blogmore-link-post)
("<f12> b l c" . blogmore-link-category)
("<f12> b l t" . blogmore-link-tag))
It works well, it makes it nice and easy to remember the bindings, etc. Nobody needs me to sell them on the merits of this approach.
Then I got to thinking last night: why am I setting up all those bindings when I could probably do it all via a transient? So that was the moment to actually RTFM and get it going. The first version was incredibly quick to get up and running and I was kicking myself that I'd taken so long to actually look at the package properly.
This morning I've worked on it a little more and the final form is still pretty straightforward.
(transient-define-prefix blogmore ()
"Show a transient for BlogMore commands."
[:description
(lambda ()
(format "BlogMore: %s\n"
(if (blogmore--chosen-blog-sans-error)
(blogmore--blog-title)
"No blog selected")))
["Blog"
("b" "Select blog" blogmore-work-on)]
["Post"
("n" "New post" blogmore-new :inapt-if-not blogmore--chosen-blog-sans-error)
("e" "Edit post" blogmore-edit :inapt-if-not blogmore--chosen-blog-sans-error)
("d" "Toggle draft status" blogmore-toggle-draft :inapt-if-not blogmore--blog-post-p)
("c" "Set post category" blogmore-set-category :inapt-if-not blogmore--blog-post-p)
("t" "Add tag" blogmore-add-tag :inapt-if-not blogmore--blog-post-p)
("u d" "Update date" blogmore-update-date :inapt-if-not blogmore--blog-post-p)
("u m" "Update modified date" blogmore-update-modified :inapt-if-not blogmore--blog-post-p)]
["Links"
("l c" "Link to a category" blogmore-link-category :inapt-if-not blogmore--blog-post-p)
("l p" "Link to a post" blogmore-link-post :inapt-if-not blogmore--blog-post-p)
("l t" "Link to a tag" blogmore-link-tag :inapt-if-not blogmore--blog-post-p)]])
With this in place I can simplify my use-package quite a bit, just binding
a single key to run blogmore.
(use-package blogmore
:ensure t
:defer t
:vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
:init
(add-hook 'blogmore-new-post-hook #'end-it)
(blogmore-work-on "blog.davep.org")
:custom
(blogmore-blogs
'(("blog.davep.org" "~/write/davep.github.com/content/posts/")
("seen-by.davep.dev" "~/write/seen-by/content/posts/")))
:bind
("<f12> b" . blogmore))
Now, when I'm working on a blog post, I can just hit F12 b and I get a neat menu:

Better still, because of how transient works, I can ensure that only
applicable commands are available, while still showing them all. So if I've
not even got a blog selected yet:

Or with a blog selected but not actually working on a post yet:

So far I'm really liking this approach, and I'm tempted to lean into transient more with some of my packages now. While on the surface it does seem that it has the downside of the binding choices being dictated by me, the fact is that the commands are all still there and anyone can use their own bindings, or I guess override the transient itself and do their own thing.
Yes, it is a bit out of the way on the keyboard, but so is Esc. I find my muscle memory has no problem with it. ↩
-1:-- blogmore.el v2.6 (Post Dave Pearson)--L0--C0--2026-04-04T10:48:21.000Z
This is another article inspired by my recent cleanup of Prelude and my personal Emacs config, following the one on repeat-mode. I’ve been going through the Emacs 28-30 changelogs looking for features I had overlooked, and this small one from Emacs 28 turned out to be a real gem.
Ever noticed how M-x shows you every command, including ones that
make no sense in your current buffer? Org commands while editing Ruby,
Magit commands in a shell buffer, that sort of thing. It’s not a huge
deal if you know what you’re looking for, but it adds noise to the
candidate list – especially if you’re using a completion framework
like Vertico or Ivy that shows everything at a glance.
Emacs 28 added a simple way to fix this:
(setq read-extended-command-predicate
#'command-completion-default-include-p)
With this setting, M-x hides commands that declare themselves
inapplicable to the current major mode from the completion candidates.
So if you’re in a Python buffer, you won’t see dired-do-rename or
clojure-align cluttering your results.
How does the filtering actually work? command-completion-default-include-p
looks at the modes a command declares it belongs to (via the interactive
form) or checks its completion-predicate symbol property. If no modes are
declared and there’s no completion predicate, the command is included as
usual – so existing commands that haven’t been updated are not affected.
Emacs actually ships with three predicates you can choose from (plus nil
for no filtering):
command-completion-default-include-p – the safe default. Excludes
commands tagged for other modes, includes everything else.command-completion-using-modes-and-keymaps-p – more aggressive. Shows
commands tagged for the current mode plus any command that has a
keybinding in the buffer’s active keymaps. Also always includes
customize-* commands. Untagged commands without keybindings are hidden.command-completion-using-modes-p – the strictest option. Only shows
commands explicitly tagged for the current mode. Untagged commands are
hidden too, so this can be quite aggressive.I’d recommend starting with command-completion-default-include-p since
it’s the most conservative – it won’t hide anything that hasn’t explicitly
opted in to the filtering.
Package authors can declare mode affiliation by adding a mode specification
to the interactive form:
(defun my-foo-command ()
"Do something useful in foo-mode."
(interactive nil foo-mode)
...)
The nil is the interactive spec (no arguments in this case), and foo-mode
tells Emacs this command is only relevant in foo-mode buffers. If a command
applies to multiple modes, just list them all:
(defun cider-eval-defun-at-point ()
"Evaluate the top-level form at point."
(interactive nil clojure-mode clojure-ts-mode)
...)
This is handy for packages like CIDER that need to work in both the
classic clojure-mode and the newer Tree-sitter-based clojure-ts-mode.
As for how well this works in practice – many built-in commands
already declare their applicable modes, so you’ll see a noticeably
cleaner M-x right away. Third-party package adoption is growing
but uneven. Commands that haven’t been updated will simply continue
to show up everywhere, same as before – so there’s no downside to
enabling this.
If you followed the Vertico sample
configuration,
you’ll find this setting already there – commented out. It was
shipped that way because it was new at the time and some users found
the disappearing commands surprising. It’s been stable for years now
and works great with Vertico, Orderless, and Marginalia. Just
uncomment it and enjoy a less noisy M-x.
Commands that are filtered out aren’t gone – the filtering only
affects completion candidates. If you type the full command name at
the M-x prompt it will still execute just fine.
That’s all I have for you today. Keep hacking!
-1:-- Declutter M-x with read-extended-command-predicate (Post Emacs Redux)--L0--C0--2026-04-04T08:00:00.000Z
I’ve been going through the Emacs 28-30 changelogs recently as part of a big
update to Prelude and my personal config, looking
for features I never got around to trying. repeat-mode is one I wish I’d adopted sooner.
How many times have you typed C-x o C-x o C-x o to cycle through a
few windows? Or C-x { C-x { C-x { to keep shrinking one? All that
prefix repetition is pure friction.
repeat-mode is a built-in minor mode (Emacs 28+) that lets you
drop the prefix after the first invocation and just keep pressing the
final key. Enable it with one line:
(repeat-mode 1)
One important thing to understand – this doesn’t magically work for every key sequence. A command is only “repeatable” if it has been explicitly added to a repeat map. Emacs ships with repeat maps for a bunch of common built-in commands, though, so you get a decent experience out of the box. Here are some of the highlights:
C-x o o o – keep cycling windowsC-x { { { / C-x } } } – shrink/grow window horizontallyC-x ^ ^ ^ – grow window verticallyC-x u u u – keep undoingC-x <left> <left> / C-x <right> <right> – cycle through buffer historyM-g n n n / M-g p p p – jump through next-error resultsThe transient state ends as soon as you press any key that isn’t part of the repeat map.
If you’d prefer it to time out automatically, there’s a setting for that:
(setq repeat-exit-timeout 5) ;; exit after 5 seconds of inactivity
The real power comes from defining repeat maps for your own commands.
For instance, if you use expreg
for expand-region, you can set things up so that C-= = = = -
expands three times then contracts once:
(defvar expreg-repeat-map
(let ((map (make-sparse-keymap)))
(define-key map "=" #'expreg-expand)
(define-key map "-" #'expreg-contract)
map))
(put 'expreg-expand 'repeat-map 'expreg-repeat-map)
(put 'expreg-contract 'repeat-map 'expreg-repeat-map)
The pattern is simple: create a keymap, then attach it to the relevant
commands via the repeat-map symbol property. Any command with that
property becomes “repeatable” after first invocation.
That’s all there is to it. One line to enable, and a lot less C-x
mashing in your future.
Are you using repeat-mode? Have you defined any custom repeat maps
that you find particularly useful? I’d love to hear about them in the
comments!
Keep hacking!
-1:-- Repeat Mode: Stop Repeating Yourself (Post Emacs Redux)--L0--C0--2026-04-04T07:00:00.000Z
I realized that one of the mistakes I often make with Emacs is not asking other people for help, so I experimented with a coaching session with Prot. With his permission, here's a recording of our conversation.
View in the Internet Archive, watch/comment on YouTube, download the captions, or e-mail me your thoughts.
defvar-keymap :prefix defines how it should be called as a command, which is good for handling updates to keymaps as well0:01: Intro: Sacha: Fantastic, this is great, I finally get to talk to you. I appreciate that you blogged so quickly about some of the things that I mentioned, and we can certainly dive right into that, or you have a lot more experience with how these conversations go, so I can let you take the lead.
Prot: Since you put in the effort to write, we already have a very good structure. The idea is, let's have your screen, so you can share your screen with Jitsi.
Sacha: Yeah. I will share my screen.
Prot: And we can go right into it. Let's see. So if you hover over… Okay, yeah, you have it.
Sacha: yeah oh you know if if at some point I should be really like fancy… Future session, we should get crdt working because that's fun.
Prot: Ah, yes. Oh, that would be nice. Yes.
Sacha: Yeah, that would be nice. All right.
0:50: Organizing my config into multiple modules and org-babel-post-tangle-hook: Sacha: So I've been making good progress in splitting up my config into multiple modules. I just have to iron out a couple of things like do I actually have to load the autoloads from the user list directory or does it automatically take care of that? Because sometimes it doesn't seem like it's doing the thing. Anyway. It's making good progress. And in fact, I came across something that I'm not sure you know about yet, or maybe you know about it and you decided not to do it. I found out that, so, okay, so here's the context. You know, when you do your literate config, you have your modules and they're actually just one big file, like one big source block with a commentary and everything in it. Yeah, yeah. So I found out that you can use a hook if you want to, to add stuff to the tangled files afterwards. So the way I set it up with my config is I still want all the different functions scattered all over the place because I'm not yet as organized as you in terms of the modules. So the org-babel-post-tangle-hook here, post. Yeah, yeah, yeah, post
Prot: So what did you do with that? Let's see.
Sacha: and boilerplate… has that boilerplate here we go so what it's what this does is when it tangles it it then goes back into the file and it inserts all that extra text and the footer into the tangled files so I still have my links to
Prot: Nice.
Sacha: the different source files where it comes from. So this is the section where it comes from but I also have all the extra lovely commentary and stuff so I'm like…
Prot: Ah, that's smart. That's good. That's good. Yes.
Sacha: That way, you don't have to keep repeating things. Although I guess if you really wanted to repeat things you could you could theoretically have the license just as a no web reference and then have it go in there automatically. anyway so I thought that was really cool so I'm making progress on the things that I had mentioned in the in the blog post about organizing my config into multiple modules and other yeah…
Prot: And how far are you in that project? How far are you?
Sacha: Let me see. I can look at the sacha.el here and I can do an occur on the files that have the lines that have the defun. I only have 482 defuns to get rid of. This is already a lot less than what I started with because like you, I have a very large… Almost 40,000 lines in this sacha.org.
Prot: Yeah, yeah, that's massive. Yeah.
Sacha: It's fun and it's interesting. It is a little reassuring to know that people still rely on your published modules instead of actually, like, do people take your config? I know you've got stuff in the config that makes it possible for people to just load it and add their customizations on top, but do you hear from a lot of people who do that?
Prot: From a few of them, yes. And this is why I actually created those customizations. But I must say, I have been trying to
Sacha: Yeah, yeah.
Prot: make it more difficult for them. So I used to have a use package, but now I disabled it on purpose and I have my own macros, so that somebody doesn't just copy-paste. And I don't do this to be mean, but I do it because this way somebody will have to think about, like, okay, what is this? What am I doing here?
Sacha: yeah I figure making making them still do that okay what am I doing here while still being able to automatically load all the function definitions will probably get them over that you know like make it a little bit easier for them so at least that way like right now it is difficult to copy things from my config like like you're so like okay maybe this is a feature but you know, maybe changing it will be nice.
4:45: Changing namespace, renaming functions: Sacha: The other big thing that I need to do with my config is I'm thinking about shifting everything to the sacha- namespace instead of the my- namespace, which is going to be a lot of renaming, which is actually, it was actually the question that I had about renaming things, not necessarily coming up with clever names that have good acronyms like you do. And I love that the humor that you have in there, but like, like just mechanically, are we talking wgrep is like, is there a more modern, emacs 31 way to rename things? Am I just using erefactor or like replace-regexp? What do you do when you need to rename a symbol in possibly multiple files?
Prot: If it's in multiple files, I do the grep approach. So it's not that sophisticated, but it works. Because the thing with the multiple files is, and it goes also to what you were telling me in that article, is first you organize, and then you refactor. It's that idea. The multiple files will not have a lot of extraneous information. You will not be matching, at least in theory, you will not be matching too many false positives.
Sacha: Yeah, and if you're doing a single file,
Prot: So you won't have to sort it.
Sacha: what do you like to do?
Prot: I have a package called substitute. One of the ways I do it is just substitute the symbol at point. But of course, this is just a minor convenience. You can do that with a query-replace. I'm not saying that you really need the package. But the idea is that you do it and you know that it works. Like, for me… I know that it works in the file. So for me, that's very reliable. But the other thing I should mention is keyboard macros from dired combined with substitute. So you start from a dired buffer, and you go file by file. That's the general idea. And in each file, you will perform, for example, a search to the symbol. Once you are on the symbol, you do the substitute-replace command, and then you move to the next file. So that is the workflow. And I do that a lot, for example, with my themes, because they have a lot of repetitive code, like each theme.
7:11: Defining aliases for old functions: Sacha: Okay, the other thing that I was thinking of as a workflow improvement here, because I'm sure that I'm going to keep calling them by their old names, especially interactively, like have a lot of commands that go off the meta x, my do this and this and this is I might also need to think about adding a function alias automatically. And one way I was thinking of doing that was just, you know, iterating over our array and bulk defining aliases so that all the sacha- stuff is now named my- stuff but I was wondering if that was a or actually also manually inserting the like well not of course manually but but creating forms for like defining the aliases somewhere but I was wondering if this was something that you already did as part of your workflow like do you when you rename things okay
Prot: No, I haven't. When I rename things for my packages, I do use aliases. But for my own code, if I rename it, basically, it's
Sacha: yeah yeah
Prot: just the latest name. So I don't try to keep aliases around. Because I eventually use a similar name, it won't be very different.
Sacha: huh all right yeah yeah I mean like it's it's there you
Prot: But what you said about the obarray makes perfect sense.
Sacha: might as well do do it automatically right okay all right that's me oh okay okay I can
8:30: Improving my streaming setup: Sacha: I can do my jitsi thing so I can see everyone and the screen at the same time the screen is very small okay so so that's I do have a dual monitor setup, which
Prot: Yeah, you need that dual monitor setup,
Sacha: is why I was like, OK, maybe I can start looking at your B-frame things. And in fact, in the minutes before I called, I figured out how to use MasterChat CLI to get the YouTube live chat into a command line program, which means that it can be run from call-process or make-process or other such wonderful things. So now it's an Emacs buffer. And then I was thinking, okay, maybe I'll make a pause frame or like a B-framed like dedicated frame for it so that I can have the chat of a live stream displayed within Emacs because you know, it's Emacs. We should do that. Yeah, yeah, yeah.
Prot: Nice. Yes, of course. And you can have it in a side window dedicated buffer.
Sacha: Although I might actually have to write my own like Node.js program so that I can also send text from it, from Emacs. I think the masterchat CLI, it only displays, but the library that it uses, if you pass it your browser cookie, you can use it to send messages back to chat as well. The reason I had liked Twitch before was because Twitch had some kind of IRC type thing that you could connect to. And then that meant, of course, that you can use ERC from within Emacs in order to send stuff to it. Anyway, live streaming and chatting, I've also been getting into that lately. And I was going to pick your brain about this whole like workflow for making videos or live streaming them. And more importantly, going back afterwards and remembering to post them or edit it, in case you forgot something or whatever. So if you happen to have any tips or the things that you like about your setup, I would love to hear about that.
Prot: Though in my case, the setup is really simple, and I admit that I could improve it. But it's really simple right now, where I have a wide monitor. So it's one display, I don't have two, but it's like 2500 pixels instead of 920. So I have a little sidebar on the side, and there on the sidebar I put OBS, for example, and I put everything I need there on the sidebar. And then I have enough space to have whatever it is I am displaying and maybe another widget on the side. So that is in terms of the physical layout of the monitor here. And then in terms of the Emacs side, I don't have a lot going on. I have one package to load the different font configuration. So when I do streaming or videos, I will load basically the presentation setup.
Sacha: It is a spontaneous.
Prot: That's Fontaine, exactly. But again, it's not a matter of the package. You could have a function that just changes the default phase, the height attribute.
Sacha: I have this monitor and then laptop, so this is my workaround for not having enough space in this desk for a super wide monitor. My husband has a super wide monitor which I like to borrow during EmacsConf. Hello child who is wonderful and likes to make cameos during my EmacsConf. Okay, I'm going to hug you.
Prot: Hello!
Sacha: Yes. So okay, so live streaming and then you just basically hop on the stream and talk about stuff.
12:09: Keeping things from accidentally airing: Sacha: I know you've mentioned things like just starting Emacs with your Scratch buffer, but yeah, how about the interesting workflows for not accidentally spilling secret stuff online?
Prot: Part of that is… so I use Vertico normally for my completions. I have some configurations for private Vertico, where by default, it doesn't display what Vertico normally displays. It's just a blank minibuffer, the way it is with the default Emacs minibuffer UI, right? But what happens is as soon as you do TAB or as soon as you move up and down, then it displays Vertico. So that, that is one way for me to make sure that I'm not showing anything I didn't want to show. The other thing is when I do videos, I don't use consult actually, even though I like it, because of the preview functionality. I don't want to be switching between files and then consult shows us something which is private. Right. So. So the private VertiCo, it's a small extension that I have with a few functions for Vertico.
Sacha: I've been thinking about modifying the console preview states so that I can elide more, I can skip over things that might be private. And things like, I already have a filter function for marginalia so that it doesn't show me the values of variables that might be private. But yeah, just turning off all these things makes it a little bit easier to say, okay, I'm just going to jump on the live stream and do this thing. Some of the other fun stuff that I've been doing along the
Prot: So there is that. And for private, of course, the other thing with privacy is that you want to have a generally good sense of where you put your files. So for example, in my pictures folder, I know that I don't have anything private there. But there are some sub folders which are like personal. So I know not to go there. So it might happen, I need to show a picture, okay, I just go to the pictures folder, and I show it, no problem.
Sacha: lines of keeping things organized is if I have a stream tag on a task, I know that's safe to show on screen. And then I modified my Org jump stuff. There's a hook that you can use to narrow things to just that subtree. So at least I can jump to it and not have to worry about the rest of the context in my inbox. Trying to slowly slowly get the hang of this
14:50: Livestreaming and recording: Sacha: Okay. So it's live stream. Do you like to live stream and record at the same time locally or just live stream and then go into the YouTube thing afterwards to download?
Prot: I just do the latter.
Sacha: It takes a little bit of a while,
Prot: I just download it from Youtube afterwards
Sacha: so I'm like… I could get started on the transcription.
15:09: Keeping track of interesting moments: Sacha: Do you have anything to keep track of interesting moments that you want to revisit, or do you just, I don't know, skip around in the video, look at the transcript, whatever?
Prot: I remember, I know this sounds bad, but I remember.
Sacha: People with good memories, boo!
Prot: And generally I try to also sharpen my memory. So whenever I can practice something, I will do it like that. But otherwise, if you really need to take a note of something, you can always have a small function that just records the timestamp. Like, what is the current time? And then you know when you started, so you will know where you are in the video. Like, it would be a very simple function that simply prints the current time, you know, format-time-string,
Sacha: Yeah. I just have to write something that gets the time
Prot: in a buffer at the bottom of a buffer. And that buffer is like your interesting moments kind of thing. And if you really want, you can make that prompt you for some text, like here is the timestamp and here is like, you know Prot said a joke or whatever, you know, like…
Sacha: started from YouTube and then calculates the offset automatically, so that I can say okay, here are my chapters roughly.
Prot: Yeah, that's even more fancy. Or you could do the other thing, which is all local, which is the moment the stream starts, you hit this command, like you invoke it, so it resets the time and then it performs the calculation locally. So you can do calculations with time in Emacs. So you can perform that as well.
Sacha: Yeah, that's really straightforward. Okay, so that's definitely something that I'm going to want to think about, because video is great for enthusiasm and showing cool stuff that you might otherwise forget to mention, but it's just so slow to review afterwards.
Prot: Yeah, of course, of course, of course. Just to say another thing with video, what I have found that is really helpful is to display line numbers.
Sacha: Oh yeah? Huh.
Prot: Me personally, I don't use line numbers, but I have found that when I am displaying something that others need to follow, line numbers help them. Because for example, earlier you were jumping around trying to find that hook, you were…
Sacha: Oh yeah, yeah, yeah. Okay. Display now.
Prot: And of course, me, I have experience, so I kind of know what you are doing, but somebody who is not really into it will be like, what is happening? Why are things moving up and down so quickly, right?
Sacha: Okay. And they can mention it, too, in the comments,
Prot: And they cannot track where you are.
Sacha: which is nice.
Prot: Yes, yes, of course. And also, when you are displaying something, you can say, look, on line 100, what I am doing, for example.
Sacha: I have to steal your config for the highlight line
Prot: And it's easy for everybody. Yeah.
Sacha: priority because I've been using the highlight line for that. But sometimes it overwrites things. I'm like, OK. Today it is well-behaved, so I'm glad for that.
18:19: Editing: Sacha: Making videos, all right. Just go ahead and make the videos, you just do it pretty straight, you don't do a lot of editing afterwards, I'm hearing, following the same kind of philosophy that you use for your blog posts?
Prot: That's the idea.
Sacha: All right, I should just go do things and not worry about whether the live stream demo that I just made of how I can highlight the PDF of your literate config and extract the stuff into whatever has a bug in it. And I'm like, oh, I just demonstrated that. It's okay, I can update it in the show notes. Oh, that's true, especially since
Prot: Or even better, you do a second video afterwards, a follow up.
Sacha: now I figured out that you can use org-pdfview view to link to pages in the PDF. So now my index.org has the highlights from your config, and it takes me back to the page that it was on. Very very cool stuff.
Prot: That's nice.
Sacha: Okay, so I just gotta do it.
Prot: I think Org-noter also is another package you could use for that.
Sacha: Yeah, probably. and then I just need to get… I think I've got PDF tools or PDF view set up. And then reader of course looks very interesting also. So I've got to tweak my config a little bit more to get it running because it has an external dependency. Anyway, so just got to do the live streaming. I was delighted. People have actually been dropping by and commenting or chatting during the live streams, which is great because I get to remember, oh yeah, I should explain that part, taking it for granted.
Prot: The thing with a live stream,
Sacha: So all of that is good stuff.
Prot: because it's something you also wrote, like getting used to talking to yourself, right? So, of course, that takes some practice, but I think, yeah, you have the hang of it already.
Sacha: Something is ringing. Hang on, sorry. I forgot. That was just my reminder that the kiddo is back to school. Virtual school is fine. Anyways, OK, so so just got to do it.
20:26: Writing: Sacha: Thank you for the tips. This is very helpful for
Prot: You're welcome.
Sacha: writing. I'm getting better at actually remembering to include more bits and pieces from my config, and I'm sure that now that I have them in different files, it'll be easier for me to then write the post that links to, oh yeah, here's the five other functions you need in order to make this little snippet work. But do you happen to, knowing the kinds of stuff that we like to write about, do you have any other tips from your workflow?
Prot: When it comes to sharing code like that, I already noticed while you were moving around that you have many things like my-consult, my-org, etc. What helps there is to just make those their own module right away. And from there, you know that, okay, this is either self-contained or it has an explicit require, so I can already know where I need to search for dependencies. So it's really that. It's because, for example, if you take just a my-consult function, right, of course, you know by the name that it depends on consult, but you don't know if it depends on my- common functions, for example. Right. Whereas if you have it in its own file, there will be a require at the top. So, you know, OK, require my-common-functions. And that way you can tell, okay, there is a dependency here. So then when you are to share this function, you can search for, okay, my-common-functions, is it mentioned here? Yes or no. And then you know what the dependency is.
Sacha: And I think this process of moving things into those separate files will make it easier for then, for people to say, okay, yes, I do want to try that thing. Let me check out the repository required, just load-file that particular file and then be off to the races. So we'll see how it works. I don't know if people actually… Sometimes people mention borrowing stuff from my blog. So maybe people are actually reading the non-Emacs News posts. We'll get to see that.
22:34: Packaging: Sacha: Sometimes I feel like a lot of my tweaks are very idiosyncratic, right?
Prot: Yes, what I found that has helped me is I implement the
Sacha: They're very suited to the particular need that I have. And then it's difficult to say, OK, if I were going to generalize this for other people, what kind of defcustoms will I need? What kind of options? And there's always that trade-off between, yeah, but I just want to implement the next little thing that I want to make for myself versus, well, if I put in the polishing effort, then possibly other people could use it, and learn from it, and then contribute their own ideas, and then everything gets better without me having to do the work myself. So it's a bit of a balance.
Prot: package that I want. So for example with denote, but this applies to everything, denote version 0.1 is the package that I wanted. So basically, it works for me. Ever since, I have been adding other things that people want, which are, of course, good things to have. They improve the package, but I have already been using the package that I want since the beginning. So ever since, it's just adding stuff and learning about how people use it and refining the code, which everybody benefits from. So whenever you have an idea that you are like, okay, this may be too idiosyncratic, don't worry about it. Make it into a package, and then what other people need will become apparent, and then over time it will change, but the core package is still what you want.
Sacha: Although it is interesting to see, for example, with the university calendar, institution-calendar thing, it's like, okay, you can get it to work for a small number of institutions, ELPA wants… they want it to work for everyone, everywhere, all the time. Okay, that might be too general. You might need to actually have lots of other people saying what they need in order to make that happen in the first place, right?
Prot: Which at that point, of course, what you want is to write the documentation. So for example, with the institution calendar, I wrote a couple of examples. Okay, how do you extend this? And yeah, I think that helps. But then of course, you cannot cover every use case like people have to also make contributions if they really care about.
Sacha: Yeah, so I think at the moment, I've been writing for n equals one, the audience is really just me. And occasionally I hear from people who are like, oh, that's an interesting idea, let me adapt it. Sometimes if I'm really lucky, they will go and write their own package on top of the stuff that I shared, which is the ideal situation, because then I can just like, oh, yeah, I'm going to borrow that and use it. It'll have more features and they're in charge of dealing with that. But I suppose at some point it behooves me to practice. OK, I'm just going to write it as a package, pretending that this is something, as you said, this is something that I want to be able to install and use myself. Then if other people find it useful, it's a lot easier for them to experiment with and then add on to.
25:40: Responding to email: Sacha: Which goes to my second thing. Doing this and making things open to other people probably means being more responsive to email. And this is, for me, this is a bit of a challenge. I'm starting to feel less time-starved, which is good. I'm starting to actually be able to schedule things. One of these days, we should probably see if we can schedule a Prot Asks thing. I don't know if I can do two hours, but maybe I can do one hour or whatever. Anyway, the rest of it involves actually doing
Prot: For sure.
Sacha: the responsible thing and responding to issues and emails and whatever. It's always a bit of a trade-off, like, oh, do I implement this other crazy idea I have, or do I answer my email?
Prot: For that, of course, it's a challenge. I must say that a lot of the maintenance work I do is via email. Or email or Signal or Telegram. People will ask me, hey, Prat, what is this? And many of the issues are not with my package. I had an issue earlier with the modus themes, no, the ef-themes, and eventually it was something to do with the user's configuration of some function of center tabs. But I had to go into it and check. So, of course, there will be that. But I must say, it's not too bad. It's not a big issue. You can always have in your email, like, hey, please don't use this for issues. And it's not a replacement for that. Just use the issue tracker.
Sacha: I know I just have to… I think I just have to like reframe my perspective. This is a gift. Other people are taking their time and effort to do this. It's wonderful that they're trying things out and putting their… actually doing things themselves and then reaching out in case… 'cause it would be nice to get things working on more people's computers. I think that the stuff that I've been building around learning languages and doing voice input into Emacs probably… There are a lot of these things already, but they tend to also be very individual workflows and individual setups. So it'll be interesting to get to the point where we can start to even have a conversation with shared code.
Prot: About the individual workflow, again, it's not a problem because what is individual now will eventually become kind of a standard workflow. Think about org, the beginning of org. You have Carsten Dominik, who is like, you know what, this outline mode isn't enough. I need more stuff on top. And eventually we have Org. In the beginning, I imagine org was basically Carsten's org, and it became this uh this package that everybody can use however they feel like.
Sacha: I used to maintain Planner Mode before Org Mode got super popular and I remember feeling very embarrassed when someone very, very kindly said "I appreciate the work that you do; incidentally, the latest update kind of deleted a lot of my notes." So this is like, when you make something that other people use, sometimes your mistakes will affect more people than just you. But I'm hoping now that now that the disks are in the spaces of terabytes instead of whatever, people are just backing up everything and version controlling everything and everything will be fine.
Prot: Yeah, of course, of course. Writing packages, of course, is a responsibility. The upside, though, is that because you know that it is a responsibility, you try to write cleaner code at the outset. Whereas if it's just for your own configuration, you're like, okay, this will work and I will fix it later.
29:59: Testing: Sacha: So I occasionally write ERT tests when I feel diligent. Sometimes I'm starting to write the test first and then write the code that makes a thing, but if you happen to have any parts of your workflow that you particularly like when it comes to testing things, I would love to hear about them because I haven't gotten to that part of your config yet
Prot: Yeah, so I don't have a lot going on for that. So it's simply ERT. But what I do with the tests is really basic. So ERT, M-x ert, and then I pick the test that I want. And I must say that when it comes to tests, I can be better myself. So there are some packages I write where they have good tests, but there are others that have zero tests. So I want to reach a point where everything has tests, but it takes a lot of work.
Sacha: Yeah. I mean, like every so often I feel like very, very diligent and I'm like, okay, let's do code coverage. So I can see things with undercover. Let's write a function and make sure there's a test associated with it. And let's write a keyboard shortcut that lets me jump from the thing to the test that's associated with it or to run it. And in fact, I still need to get embark to do all these things for me so I can be looking at a function and say just rerun the test for this, please.
Prot: Just to say one low-tech feature that has helped me a lot, low-tech, Is i use the docstring as a declaration of intent. So in the docstring, I say what is the function or the variable meant to do, like what is it meant to provide. And then if I look at the code and I'm like, ah, this doesn't work, I know that the doc string is what I wanted. It's never the code. So there is this idea that the code is a source of truth. For me, it's the opposite. It's like the doc. It's the specification. And then the code is… I was wrong. I was sloppy. I wasn't paying attention. I missed something or whatever. And the reason for that is the following. It's because with the code, you may have used the symbol wrongly, or you may be calling something that you don't mean to call, or there is another function. Or, for example, you use mapc instead of mapcar, so you don't get the return value you expect, that sort of thing. So something, basically you don't deal with sloppy problems. So you don't have confusion there. You know that, okay, the source of truth is the docstring. This is my intention.
Sacha: I should do that more often. Now that I've changed my yasnippet for inserting functions to automatically have the docstring, I feel a little guiltier when I delete the docstring, so I am compelled to instead fill it out. But if I specify it in more detail, as you do with it becoming the statement of intent, then I can be like, OK, let's try that. It's a good practice. And then I can write the test.
Prot: And the thing with docstrings is that, of course, you are
Sacha: Yeah? This is me.
Prot: motivated to just write the minimum necessary so that you don't get the warnings, right, from checkdoc. But if you write more, then you are rewarded yourself. It's something that helps you, future you, and of course other users, because you always have to consider yourself as basically a user. I don't remember why I wrote this six months ago, so of course having the docstring there, actually spell it out, helps me.
33:46: Learning and reminders: Sacha: I definitely have problems with working memory and long-term attention. Which actually touches on this other thing that I mentioned in my post, which is, in your experience coaching other people and also in your personal practice, what are you finding as good ways to keep reminding yourself, okay, these are the keyboard shortcuts I want to internalize, or this is the the workflow tweak that I wanted to try naturally. I was thinking, maybe I make an Org file or maybe I make a quick help thing or whatever. But it's always interesting to hear about other people's workflows.
Prot: What I find most useful is to not try to memorize too many things, but whenever you are in the flow of, oh, this is a process that I want to be doing, to actually implement it as a command or whatever, as a package or whatever, like basically don't try to memorize the steps and of course the key bindings. Try to build a function that does those for you. A command basically that does those for you. So for example, to be concrete, I mentioned earlier that for video purposes, I will enable line numbers. And I will also enable the line highlight. And I have another thing where I disable spacious padding. So the package I have. And all this, of course, I know the key binding. So it's F7 and F8 and F6 or whatever, right? But I'm like, I cannot remember all that. I will just write a function, and it will be prot-streaming-mode. And I enable prot-streaming-mode, and it does what I want it to do, and then I disable prot-streaming-mode, and I'm back to where I need to be.
35:31: Encapsulating workflows into functions: Sacha: Yeah, I have a prepare-for-screencast that does something similar, changes font size, etc, etc. Tt's so wonderful that in Emacs, you can
Prot: Exactly.
Sacha: just keep collapsing things into functions that do the thing that you wanted, and it has access to pretty much everything. I just need to remember to actually call the thing and remember what the thing was actually called. Aliases are very helpful, so it's orderless, but it's like…
Prot: Another thing that might help is long names. Because with long names, you have more chances to match it. For example, in this case, it could be called prot-streaming-mode, but you could also call it prot-streaming-and-video-demonstrations-mode. And of course it sounds ridiculous, but if you think about it, I might search for, I do M-x and I search stream. I find it. I search video. I find it, right. I search demo. I find it. So, if you care about something, you can name it that way, and then you find it more easily. Or, of course, with aliases, you do the same, right? prot-streaming-mode, alias, prot-video-mode, alias, you know how it is. But, yeah, either of those would work. Basically, benefit from the fact that you have completion, and I imagine you also have orderless.
Sacha: So definitely that. And then
Prot: So, yeah.
Sacha: for the free form notes thing, it just occurred to me.
37:05: Popping up notes: Sacha: So in addition to your posframe stuff in your config for quickly popping up an Emacs posframe for some commands, like, do you have some things? I suppose I could just use that directly for my notes and for the chat. Do you have any other of those "quickly pop up something so that you can do something with it and then make it disappear?"
Prot: No, I haven't built a lot on that. So I have some functions I do
Sacha: That's your main thing.
Prot: with that. Specifically, I have it for the timers. For me, that's very useful. And for org-capture, but I haven't elaborated on it. Of course, I could do that more. Another that I… By the way, it's not a poframe. Technically, what I have is just a new frame. But the idea is the same, right? It pops up and it disappears. And I can share the code for that. It's in the prot-window package, actually.
Sacha: I have it highlighted here in my…
Prot: So it's a small macro there.
Sacha: So this is the thing that I was telling you about earlier where it just extracts all the things that I've highlighted. It's very, very cool. It's in one of these, I'll grab it eventually. Which is good because I have to go over my config at some point.
38:21: Rediscovering things in my config: Sacha: There's so much in there that I've completely forgotten writing about. And so I'm like reading this now as I'm splitting it into different modules and saying, oh yeah, I automated that. I'm doing it manually again.
Prot: The other thing that might help is a prefix key. So I have done that with C-z. So it's a prefix key, and then either with which-key or with Embark, you know, the Embark… When Embark replaces C-h. I forget how it's called now. You can always see, OK, what do I have? Like, what are the groups? And then you can cluster things there. And it's very easy. Ever since defvar-keymap, ever since that became a thing, it's very easy to write prefix keymaps, because it has a keyword called prefix, and then with that prefix you define how the keymap should be called as a command.
Sacha: That's interesting. I should definitely look into that. Finds how it should be called. That's a command. So you can just add it to other key maps as needed. That sounds cool.
Prot: So consider this difference, like right now, you can take a defvar, which is a keymap, right? And you can bind it to a key, the keymap itself, without the quote, you can bind it to a key. So you will do define key in the global map, like whatever you want, and then bind it. What happens though with that is that you're binding the value of the keymap to the key, which means if you make changes to the keymap, your key doesn't know about them.
Sacha: I've been running into that. I get annoyed and I have to keep re-evaluating my definitions. So yeah, okay, that's what I do.
Prot: Whereas if you have the prefix, which is now a command, you have created an indirection. So now you define key to the symbol that you have specified. And that, of course, is that indirection, which now gets the up-to-date value of the keymap.
40:31: Catching up on Emacs developments: Sacha: So this is Emacs stuff that I have been missing out on, because for the past 10 years I've just been squeezing things into whatever moments I can have before somebody comes and says hello and says mom mom mom mom, and now that I have a little bit more focus time, I'm looking forward to finding out about all the cool stuff that has gone into Emacs and that I'm not currently taking advantage of. So things like, for example, I only scratch the surface of using Lispy, and I want to do other things as expressions because it's all magical. And if you have similar, like, oh yeah, this is a new thing in Emacs 30 or 31 that is super helpful and not everyone knows about it, I'd love to know about it. I mean, I know it's on Emacs News, but sometimes I'm like, whoosh, it goes past my radar and I don't have the time to dig in.
Prot: Yeah, right now I cannot think of something. But yeah, I will.
41:29: diffs: Prot: Oh, a very small thing that helps me a lot when I make any kind of edit. You know, there is this function diff buffer with file. So that's good. For me, what I always want is
Sacha: that sounds like a little tweak
Prot: diff-buffer with a current file. I don't want to diff a buffer with some random file. So what I have is a very small extension, a very small function, which is diff-buffer-buffer-file-name. So buffer-file-name is the variable for the current buffer's file. And then I do the buffer file name. And for me, that's very useful. Whenever I make an edit or I'm not sure what happened, I do that and I already see the diff. I use that a lot.
Sacha: that I would love to to pick up as well. There's all sorts of interesting workflow things that I am looking forward to discovering as I figure out the better way to watch videos and then also make videos, because one of the things I find is whenever you demonstrate something, sometimes, if you're really lucky, someone will say, oh yeah do you know about this thing that does the whole thing, which is great. One of my favorite reasons for sharing things is learning from other people. All right. You write this really long blog
Prot: Same. Like you put it out there and somebody will be like, hey, you could do it this way instead.
Sacha: post about this clever thing that you just figured out and then five minutes later, oh yeah, that's been built into Org since, you know, version 9.7.
Prot: Exactly, exactly.
43:08: Thinking about the community: Sacha: Which actually leads me to: what can we do? We've got about 20, 15 minutes left in this hour. Taking advantage of your very large context window for all things Emacs community, you know, those kinds of stuff that we are interested in, what are some of the things that we could do to make things even better? This is a very open question, of course, but yeah.
Prot: Even better, you mean Emacs in general or Org in particular? Because Org got a very nice feature lately, Org 9.8, which is the ability to preview images for any link type. So that's very useful. Before it was like the file type. Now it's any link type. And of course, if you ever want to do something with a custom link type, there you have it.
44:00: org-link-preview: Sacha: Which is good because I, in fact, have an override for a custom link type where I had done it before. So I just basically copied and pasted the image preview link so that I could have my SVGs either included in it as a whole or just preview. Anyway, so yes, I'm going to switch over to the new one. Link preview, update my code for SVGs.
Prot: Yeah, for example, now imagine this. Imagine you have a custom link type, which is called image or something, and you just give the image a name, nothing else. And internally, this link type knows to go in a specific directory and get the image from there, maybe even have copies of the image, so it can give you a copy that matches some parameter or whatever, like some user option maybe. You could have fancy things like this. I have been thinking about it, but I haven't written anything yet.
Sacha: I would probably like… Things like my audio waveforms could go in there very easily and things like that. I'm very curious about this idea of mixing more things into other places in Emacs. And one of the things that I've been meaning to dig into is how LECDraw does SVG interaction, because it uses mouse events to be able to drag things around and whatever. Because I think if we can get richer interactivity and more graphical elements, that could be really fun.
45:31: Prioritizing things to work on: Sacha: Anyway, but yes, so I've got basically three months of focus time before the kid goes on summer vacation and wants my attention at probably the majority of the day at an irregular interval. So it'll be a lot harder for me to schedule things then. I can set aside maybe 10 hours a week to work on Emacs-y things, including possibly working on infrastructure for the upcoming EmacsConf, or tweaking Emacs News or hosting meetups or whatever. Taking advantage of you as an external perspective, are there things that would be a good idea for me to particularly focus on? Things that you've been wishing you could say, Sacha, hey, just do this thing and it'll be awesome.
Prot: I think you already have a very good setup, actually. So I don't think there is much to be done in terms of adding things. Maybe the work here is to be removing things, and that's the more difficult part.
Sacha: No! Delegating things. Passing things to other people, maybe. Making it possible for other people to help.
46:52: Modelines: Sacha: Yeah, I don't use the mode-line as much. I ended up moving keycast to the header line because it's a little bit more visible in videos. Sometimes when closed captioning is on, it obscures the mode line. So I don't tend to look at the mode line for much, and I'm wondering what I'm missing out on. And I'll probably also want to add: am I streaming?
Prot: Yeah, not much. Not much is the answer, but maybe you could declutter it in that regard so that then it is useful. For me, where it really is useful is to know some things such as, of course, what is the buffer name? Is the view narrowed? That's, for me, really important. Maybe is it a read-only file? And am I running a keyboard macro?
Sacha: Is my microphone on?
Prot: Yes. Good, good. You see, there are all sorts of good ideas. And you can think of those as just one character, right? And you can have that one character with a face, which has, for example, a background. So is my microphone on? That's a green background. Am I streaming? That's a red background or whatever. And you just see the colors there and you know everything is all right.
Sacha: Although, actually, now that we're talking about it, I'm thinking maybe I should just revive websockets. So I made an obs-websocket.el thing before, and someone has… The benefits of making a package: someone has actually updated it to work with the new WebSocket protocols. I just have to get the whole thing set up again so I can communicate with OBS. I can use a different theme, most likely another Modus theme, when I'm streaming, so that it's a little bit more in my face: okay I'm looking at the correct colors, I am public.
Prot: That's the other thing. Yeah, that's good. That's good.
48:50: Themes would be nice to have per-frame: Prot: With themes, unfortunately, that's actually something I would like to have. We cannot have them per frame, which is strange because if you do set-face-attribute, you can specify a frame argument. But if you do something like custom-set-faces, you cannot.
Sacha: I'm sure that once you start messing around with Emacs internals, you might be able to figure out the way to do that.
Prot: Yeah, now that I say it, it shouldn't be too difficult. Yeah. Famous last words.
Sacha: Yeah, yeah, yeah. That's really fun. Okay, so that gives me stuff to work on.
50:11: Getting together: Prot: For me, yes. Even more frequently than once a month. Whatever works for you. For me, it works. That's the point. And also not in the context of coaching or whatever, but generally as a collaboration, I'm totally okay with that. Basically, more events for the community. I'm all for it.
Sacha: Yeah, because it is different. I very much had missed doing Emacs chats, and I'm so delighted that you've got Prot Asks. I'm looking forward to watching the one that you just released, because it's a community event, right? You get to know about interesting things about people. And there are a lot of things that come up through conversations that don't come up when you're just writing by yourself.
Prot: Yes, yes, yes. It's really that. It's really that. And for me, it's also another thing, which is it's more inviting. Like, it's like you are telling people, hey, show up like you can participate. Actually, we are friendly. Like, here we are. You see us. I think that kind of encouragement helps.
Sacha: So if you want to do, like, Emacs office hours on a regular basis, either something that you schedule in yours… Is it a YouTube thing where we can both schedule a live and then both have it, or not? I think they've got a collab thing now. I don't know.
Prot: I haven't explored it. So on the technical side, I really don't know. But in terms of intention, I'm all for it. So we can of course figure out the technicality.
Sacha: You have the bigger channel.
Prot: But I really don't know. We can do it twice a month, or even if you want, if you are really
Sacha: If you want to set it up, then Thursdays are probably good. Or if you want me to set it up, then I can do that. And then we can figure out the platform details and the non-YouTube way for people to join… probably IRC. We've got all this lovely infrastructure for EmacsConf, which I dust off every month for meetups. So that's certainly something we can slide right in there too. Okay, so if we do it once a month, that just gives me three sessions of practice, but if we do it like twice a month or more, I am also okay with that. I think we can squeeze that in and make that happen.
Prot: into it, once a week, a live stream once a week. And yeah, people can join, and we can always have a topic and talk about it and take it from there. We could also do it. Now, I don't know whatever makes more sense, but we could do it on my channel. And then, of course, with a prominent link to your channel, or we can do it one on your channel, one on my channel or always on your channel. Me, I don't mind at all. Like me, I'm in for the fun.
Sacha: We'll figure out the technical details and whatever off-stream. It could be interesting because then that gives people a friendly place to drop by and chat. And also because I know you're there and I'm there, it gets away from the talking to myself. When it's just me talking and then it's just like chat is silent, it just feels like I have this unfairly privileged position. So yeah, that's definitely something we're going to look into. We can structure that as one of these coaching thingies if I'm looking for excuses to use the Google Open Source Peer Bonus. I still haven't really made a justifiably good plan for it. So yes. Okay. Oh, this has been very helpful. I've got like all these tips. If you're okay with it, I am totally fine with posting this recording online. If you want, you can also post it. I think there's some kind of collab thing.
Prot: Me, I don't have a recording. So you can do whatever you want. So it's really up to you. Me, I don't mind. The reason I don't have recordings of my meetings is because I really have this policy of, you know, it's private. Your name is never known. Nobody has seen this. That's the idea. Of course, in your case, you're making it public. So, of course, that's fine.
Sacha: Yeah, my stance is always, well, I'm going to learn stuff, but A, I'm very forgetful, so I need to be able to search it and find it again. And B, other people can pick up stuff too. I might as well expand the learning and do the learning out loud. So all that is good. And then for next time, which will probably be in two weeks, or maybe earlier if I manage to get my act together,
54:44: Namespaces: Sacha: I'd like to see if I can get my stuff properly split up into different modules that have the different namespace. I really think I'm going to end up shifting to the sacha- namespace instead of all the my- stuff. I used to use the my- namespace prefix so that people could copy and paste things more easily into their code. But now I'm like, well, if I put it in sacha-, then I'm not polluting their namespace if they're loading the whole library.
Prot: Yes, yes, exactly. Exactly, exactly. That's a good thing.
Sacha: So that's on my to-do list.
Prot: And with naming things, of course, I also hinted that in the article I wrote in response to your blog post. It really helps to think about the names. Also, with what we said earlier about finding things like so don't try to be too terse, too economical with the names like make the most of it.
Sacha: I'm using nameless anyway to hide the prefixes. Got to get the hang of using the keyboard shortcuts to insert things.
55:46: Verbose function names: Sacha: Yeah, so I do like having very verbose function names and just practically full sentences in the thing. All that is very good. So that's my main thing. Aand then of course, getting into more ERT… I have this function that now that lets me try to jump to the test or the file that's related to this thing. So we'll see how it goes, especially as I move things into these different functions.
Prot: Okay, okay I'm not sure how you are doing that, but if I were to implement something like that myself, what I do with the ERT tests, it's always the prefix of the ERT file and then the name of the original function, double dash and then the name of the original function. So, for example, let's say, modus-themes-tests, right? So then it's modus-themes-tests–modus-themes-load-theme, for example.
56:45: Naming conventions for ERT tests: Sacha: Okay, so that's your naming convention.
Prot: That's a convention. That's a convention, yes.
Sacha: I should try that. I've just been basically naming things as function-name. And then I was, like, maybe I should be calling them function-name-test. Or in this case, you know, package.
Prot: Just to add something to this, because you also named this, so the nameless user. So there is built into Emacs this thing called shorthands.
57:14: shorthands: Sacha: Yeah, I read about that, but you did mention that some people have been going back and forth about whether it's worth using it or whether it confuses things more. I think just leaving the names as is and then just displaying it differently seems to be like an in-between step.
Prot: So that's what shorthand does. The name is, for example, modus-themes-test. And shorthand, effectively, is a buffer local variable which takes the small prefix and maps it to the larger prefix. So modus-themes-test can be mtt, for example.
Sacha: Okay. All right. So basically it's a more powerful nameless, more configurable, and it's built in. So I should check that out also.
Prot: Yeah, you can check it. It's not configurable, like it doesn't give you too many options. But the point is that for this simple case, at least for the tests, I find it useful because I don't want to have like a railroad of a function name, right? So I just want to be looking at something that I can understand. And basically, the prefix of the test is just there for it to have a prefix. And then I know what the function I am testing is.
58:46: "I don't write bugs.": Prot: I don't write bugs. No, no, no, of course, I'm kidding.
Sacha: That's going to go in the quotes. Okay, I don't write bugs. I write a lot of bugs. That's going to go to the blog post. It's going to be very large. So you never have to use bug-hunter because you just don't write bugs in the first place. Bravo. Good for you.
Prot: Why didn't people think about that? Now, of course, I'm kidding. So the way it works is that they are actually standalone packages. So there is a distinction, actually, in my configuration. So there are the modules, which is the configuration blocks, what would be done with. And then there are the libraries, which are actually packages, like I could just publish them right now. For example, for the mode line, there is prot-mode-line. That could be a package tomorrow, no problem. So if there is a bug there, I will go and deal with it the way I would deal with any package, like edebug, toggle-debug-on-error, whatever it is that I am doing. So there never is a scenario where the code is in all sorts of places, scattered across the file, and then, of course, it's very difficult to track it.
Sacha: But for your config, if it's in multiple files and you need to bisect it… Bisecting can get you to this load-file over here, this require over here is where things break down, but then you have to… okay, I want to load everything above that point and then bisect into the thing, which is slightly more annoying.
Prot: In practice, it's not difficult, because the way I
Sacha: I don't know. How does that work?
Prot: load my packages, so in the modules themselves. So I have this macro, which has a condition case in it. Of course, usePackage has the same, but with usePackage, you have to have everything as a package, whereas what I have here is even if it's not a package. So condition case, and basically if there is an error, it tells me where the error is, and then I can find it very easily. I have never had a scenario (of course I was joking, but actually I'm serious)… I've never had a scenario where I was confused as to what was happening. It was always very easy to find the error. If it's a bug… Yeah.
Sacha: Errors are fairly straightforward because it complains about it, but when it runs but it just produces the wrong behavior eventually, then that's the annoying part that I've been using bug hunter for.
Prot: The only scenario I think now that I had an issue like that was with the mode line, actually. Because with the mode line, if you give it like a wrong face or something, I don't remember, it will print like several messages for everything that changes on the mode line. So you will get like, well, invalid face, and there will be like, in square brackets, 100 times of this message. So That's the sort of thing that indeed is more tricky, but that was not because of my code. It was because of one small tweak that affects the mode line, and then it was about figuring out what the error is there, what's the bug there. But if you have the configuration split up in ways that are logical or thematic, if you want, whatever bug is always in one spot. It won't cut between files. So for example i have a module which is theme in the wider set but the theme also includes fonts. because fonts are, in Emacs terms, part of faces, themes deal with faces, that sort of thing. So whenever it's something related to appearance, I know that it's in the theme. It cannot be somewhere else because of how I have written it. Of course, depending on how you split things up, you will end up in a scenario where you have bugs that go across files. For example, a common one is where people will have, for example, evil mode, right? And then they will load everything, and then they will have a separate configuration module, which is for key bindings. And basically, that's a disaster, because whenever there is some problem, you don't know which key binding relates to which package, and you are always in a state that it's hard to predict. And basically, you have to do every key binding with eval after load, this package, this key binding kind of thing.
Sacha: Oh, that's going to be fun. I do have a bunch of key bindings in my file, so I'll just have to see how that all gets organized.
Prot: If you have them, organize them by package. Define them close to the context. Okay.
Sacha: That's actually mostly what I've been doing, mostly because I think of it, I think of the key binding when I'm adding the package to my config, so it's right there. I just realized I could probably just copy the top of my config file with requires or whatever to a setup file, which bug-hunter can then load. So I can still probably use
Prot: Okay, good.
Sacha: bug-hunter with that. Anyway, thank you so much.
Prot: Yeah, sure. I just wanted to ask the last thing. What is the kind of bug that you have encountered? What kind of bugs are we talking about here?
Sacha: Recently, in my shifting of everything to the new system, I also happened to realize that I had updated my Emacs and then stuff wasn't highlighting in the mini buffer. I eventually found out that it was because I needed to upgrade certain packages. But in the meantime, I was like, what do you mean? Okay, emacs -Q, sometimes it's working, sometimes it's not working. Okay, let's start narrowing it down. And that was fun. The other thing that I recently had to bisect was: I was exporting my really large config after having split things up into different modules. One of the lines was causing it to go into like a debugging thing, but it would not tell me what it actually debugged. You know, the backtrace would just not happen. So then I actually had to narrow to region and then export the specific sections of my file until I narrowed it down to, okay, my defvar custom link needs fixing. So I do this kind of bisection a lot. Ideally, whenever I can, I like to be able to just write an assertion so that Emacs can do the work of narrowing down when this happens but sometimes it's just, you know, you gotta pick your range and then execute the thing and see what happens. So I'm always looking for tools because I write a lot of bugs. I'm sure by the time I see you again, it may be either next week or next next week, I will have more bugs to share and more things to learn from. But this is very helpful and I am looking forward to updating you once I get all of the stuff checked off my to-do list.
Prot: Very good. Let me know how it goes.
Sacha: Yeah, yeah, awesome. Thank you so much.
Prot: And for the live streams, we see how it goes. Yeah. You will tell me. Yeah.
Sacha: And it's okay to post this recording if you want to?
Prot: Whatever you want. Whatever you want.
Sacha: Awesome, all right, see you around.
Prot: Take care, Sacha. Bye bye.
Oh, do I ever have a lot of ideas to follow up on. =) But I'm making myself get used to writing them down so that I can post these notes instead of trying to squeeze in just one more tweak… Anyway, plenty to explore!
Want to join us on Thu April 16 10:30 AM America/Toronto, 5:30 PM Europe/Athens? Check out the livestream we've penciled in for April 16 - come join us!
You can comment on Mastodon or e-mail me at sacha@sachachua.com.
-1:-- #YayEmacs 10: Emacs coaching with Prot: Emacs workflows and streaming (Post Sacha Chua)--L0--C0--2026-04-04T02:23:03.000Z
Raw link: https://www.youtube.com/watch?v=djE_pVlgDHg
The other day I had a coaching session with Sacha Chua. Sacha asked me if she could record and publish it, to which I agreed. More here: https://sachachua.com/blog/2026/04/yayemacs-10-emacs-coaching-with-prot-packaging-emacs-lisp//.
Our next meeting will be done live on the 16th of April 2026 at 10:30 America/Toronto, 17:30 Europe/Athens time: https://youtube.com/live/djE_pVlgDHg.
I will check with Sacha how she imagines doing this. Though I am the laissez faire type, so will adapt as we go.
[ Note that all my coaching sessions are private: I never share details of my meetings. This is an exception because Sacha asked me about it. ]
-1:-- Emacs live stream with Sacha Chua on 2026-04-16 17:30 Europe/Athens (Post Protesilaos Stavrou)--L0--C0--2026-04-04T00:00:00.000Z
jabber.el is an XMPP client for Emacs, originally written in 2003. Development slowed over the years, though contributors kept the package working across Emacs releases.
I took over as maintainer with the goal of modernizing the protocol support.
Now, jabber.el is the most XEP-complete text-based client in existence.
For those, like me, who count XEPs like Pokemon:
- OMEMO encryption (XEP-0384) via a C dynamic module wrapping picomemo
- OMEMO media sharing (XEP-0454)
- OpenPGP for XMPP (XEP-0373) using Emacs’ built-in EPG
- Stream Management (XEP-0198) with session resume
- Message Archive Management (XEP-0313)
- Message Carbons (XEP-0280)
- Delivery Receipts (XEP-0184) and Chat Markers (XEP-0333)
- Message Correction (XEP-0308), Replies (XEP-0461), Moderation (XEP-0424/0425)
- Chat State Notifications (XEP-0085)
- Client State Indication (XEP-0352)
- Blocking Command (XEP-0191)
- HTTP File Upload (XEP-0363)
- Direct TLS (XEP-0368) with dual SRV lookup
- Real Time Text (XEP-0301)
- PubSub (XEP-0060)
- Bookmarks (XEP-0402 with XEP-0048 fallback)
- SQLite message storage replacing flat-file history
- MUC Self-Ping (XEP-0410)
Almost caught them all
No other text-based XMPP client has this level of protocol coverage. jabber.el now rivals Dino and Gajim, the major graphical clients.
OMEMO requires a Signal Protocol implementation. That means C.
jabber-omemo-core.c is 763 lines wrapping picomemo through Emacs'
dynamic module API. The Elisp layer handles XMPP integration: PubSub
bundle publishing, stanza encryption, session persistence in SQLite, and
trust management.
It also provides AES-256-GCM for OMEMO media sharing (XEP-0454).
Emacs is not just the editor I write jabber.el in. It is the runtime, the test harness, and the application.
I develop in the same instance where I chat. Fix a bug, eval changes, and the fix is live in my running session. Seconds, not minutes.
Emacs provides most of what a chat client needs out of the box: SQLite for storage, GnuTLS for encryption, EPG for OpenPGP, EWOC for list display, Transient for menus.
Distribution is through NonGNU ELPA, which handles everything.
jabber.el is just an M-x package-install RET jabber RET away from
install.
XMPP is the only federated, open-standard messaging protocol that works in practice. I use it for all my daily communication. My friends are on WhatsApp, Discord and IRC – gateways bridge these networks transparently.
One client, one interface, every chat network I need.
More on the gateway setup I use in a future post.
-1:-- Bringing jabber.el Back From the Dead (Post Thanos Apollo)--L0--C0--2026-04-03T21:00:00.000Z
You can now import Anki decks into gnosis.
The entire Anki ecosystem, thousands of community-maintained decks across every subject, is accessible from Emacs. I can finally keep track of the AnKing deck changes.
If you can’t beat them, import them.
gnosis-anki-import reads .apkg files and converts them into gnosis
themata. It resolves Anki’s note type templates to extract
question/answer pairs, normalizes tags, and strips Anki system tags
like marked and leech.
Re-importing the same deck is safe. Each imported note keeps its Anki GUID, so gnosis detects duplicates and skips them. When AnKing pushes an update, I just re-import and only the new notes come in.
Tens of thousands of notes & themata, managed from a text editor. As it should be.
org-gnosis was merged into gnosis. Maintaining two packages with
two databases for what is fundamentally one system was, in retrospect,
an act of bureaucratic self-harm. Nodes, journal, and themata now
share a single gnosis.db. The migration imports existing org-gnosis
data automatically.
The old org-file export format is gone. Exports are now .gnosis
SQLite databases. Importing shows a diff buffer with NEW and CHANGED
entries before applying, so you see exactly what will change before
committing.
Combined with GUID tracking, this makes gnosis collections maintainable over time rather than one-shot imports. Share a deck with a friend, they modify it, you re-import. No data lost, no duplicates.
gnosis-tl module..org.gpg files no longer triggers redundant decryption.-1:-- Gnosis 0.10: Import Anki, Merge Everything Into One (Post Thanos Apollo)--L0--C0--2026-04-03T21:00:00.000Z
Back when I first really got into writing Emacs Lisp
code, one of the first things I got very used to and really fell in love
with was being able to
eval-last-sexp
(C-x C-e) the code I was writing, either to test it
right there in the buffer, or to cause it to be bound so I could use it
elsewhere. It was so different from any other mode of working I'd used
before and it was really addictive as a way of hacking on code.
Also quite quickly I got used to the fact that eval-last-sexp wasn't so
helpful with things like a defvar or a defconst, if I was changing them
up to try out new ways of doing things with the code I was working on; there
I had to remember to get used to using eval-defun (C-M-x).
Hardly a great problem, but something to keep in mind1.
Pretty quickly, as I worked on longer packages, I found myself wanting to,
in effect, unload a whole buffer of code and evaluate it again. From this
desire came nukneval.el.
The original version of this has been sat around since 2002 or so, perhaps a little earlier, and has served me well every time I've been messing with a new package. While I suspect there is (now, perhaps was then too?) a better way of doing things, the approach used in nukneval helped me learn some things and served me well (and still does). Now it's muscle-memory to run it.
The way it works is quite simple: go to the start of a buffer, read each
form, check if the car is of a given list of symbols, decide if it's
something I want to unbind, and then pick either makunbound or
fmakunbound and use that on the symbol. Finally, once the end of the
buffer has been hit: eval-buffer.
I've just released v1.3 as part of my slow wander through my old Emacs Lisp
packages, with this release cleaning up a deprecated use of setf to move
point, and also rewriting the code so it's a bit cleaner and also gives
better feedback.
Bozhidar Batsov recently wrote a good post covering these sorts of issues. ↩
-1:-- nukneval.el v1.3 (Post Dave Pearson)--L0--C0--2026-04-03T14:09:19.000Z
A wee bit over 5 years back I wrote a tiny package to quickly insert PHONY
target markers into a
Makefile.
While it's far from my most-used package, it's one that gets a call on
occasion, so it's one I still carry around in my Emacs
configuration.
Given I'm currently engaging in a slow background process of cleaning up
some of my Emacs Lisp packages, removing some obsoleted
practices, I've given
make-phony.el a little bit of
attention.
As well as dropping the use of setf to set point to the start of a
line, I also tweaked
the code a little so that it only inserts a PHONY if there isn't already
one there. While
that's hardly been a problem for me, it just felt like a neat bit of
cleaning up to how it works.
-1:-- make-phony.el v1.3 (Post Dave Pearson)--L0--C0--2026-04-03T09:23:40.000Z
The denote-sequence package is an optional extension to denote
that empowers users to write “sequence notes”, else “folgezettel”, in
the style of Niklas Luhmann.
Sequence notes are created in relation to other notes, as parent,
child, or sibling. denote-sequence communicates such relationships
by writing a “sequence” to the file name, in accordance with the
Denote file-naming scheme (technically, it uses the optional
SIGNATURE component of the file name, which is defined as a
free-form field for users to use as they see fit—so this is just one
application of it).
The exact presentation of such sequences is subject to the user option
denote-sequence-scheme. The package has hitherto supported two
schemes, the numeric and alphanumeric.
In the numeric scheme, each level of depth is delimited by the equals
sign. The sequence 1=2=3 thus has three levels of depth. It means
“the third child of the second child of the first parent”.
By contrast, the alphanumeric scheme relies on the alternation between
numbers and letters to communicate levels of depth. The above example
is thus expressed as 1b3.
alphanumeric-delimited schemeMany users have told me that the alphanumeric scheme looks cleaner.
Though I think it is hard to read when sequences get really long, like
2a13c6d2a. To this end, the new sequence scheme augments the
alphanumeric style with delimiters that are placed after the first
level of depth and every third level of depth thereafter. Thus:
2=a13=c6d=2a.
Users may find this easier to work with.
denote-sequence-convert commandThis command has been part of the package since its inception. It can convert from one sequence scheme to the others.
denote-sequence-convert has a “do what I mean behaviour” with regard
to which file or files it should operate on:
When called from inside a file with a Denote sequence, it operates on the current file.
When called from a Dired buffer, it operates on all the marked files.
When there are no marked files in the Dired buffer, it operates on the file at point.
The target sequence scheme for the conversion is whatever is assigned
to the user option denote-sequence-scheme. If, however,
denote-sequence-convert is called with a prefix argument (C-u by
default), then it will prompt for the target sequence scheme.
I just merged the code into trunk. Users who are building the package from source can try the new feature right away. Otherwise, it will be available in the next stable version of the package. I hope to have that ready some time in mid-April.
denote-sequence-1:-- Emacs: new sequence scheme for the ‘denote-sequence’ package (Post Protesilaos Stavrou)--L0--C0--2026-04-03T00:00:00.000Z
The other day, my partner and I went into the hospital as two and came out as three. This week, I became a father. From the second I cuddled this little fella, I felt like I'd known him my entire life. I love him so much.

Since going indie dev full-time, I've enjoyed a great degree of flexibility to work on personal projects. This has enabled me to share more via blog posts and YouTube videos, but also dedicate more time to projects like agent-shell. It is now my most popular Emacs package, receiving lots of attention from users (bug reports, pull requests, discussion, etc). If you've been in touch recently and haven't heard from me, now you know why. Fatherhood is new to me. I'll need a little time to adjust while finding my footing.
While my hope is to continue working on my indie projects, sustainability is now… errm, a tad more important. If you get value out of my work, please consider sponsoring. Better yet, if you use my tools at work, consider getting your employer to sponsor me instead. I also run a blogging service and offer a handful of iOS/macOS apps. If you're keen to journal or take quick notes on iOS, Journelly is my take on it. Bonus points for Emacs users, as it saves entries to an org file.
Now, please excuse me while I start crafting my son's first init.el…
ps. This post was stitched up from a handful of seconds here and there, in between all the sleep-deprived but loving activities currently rocking my world.
-1:-- …and then there were three (expect delays) (Post Alvaro Ramirez)--L0--C0--2026-04-03T00:00:00.000Z
Following on from yesterday's release,
I've bumped blogmore.el to v2.5.
The main change to the package is the thing I mentioned yesterday about the
toggle of the draft status. The draft toggle yesterday was pretty
simple, with it working like:
draft frontmatter, draft: true is addeddraft frontmatter, it is removedThis meant that if you had draft: false set and you went to toggle, it
would be removed, which is the same as setting it to draft: false.
Unlikely to generally be an issue, but I also couldn't let that stay like that. It bothered me.
So now it works as you'd expect:
draft frontmatter, draft: true is addeddraft: true is there, it is removeddraft: false is there, it is set to draft: trueMuch better.
Another change is that I fixed a problem with older supported versions of
Emacs. I didn't know this was a problem because I'm running 30.2 everywhere.
Meanwhile, thanks to package-lint-current-buffer from
package-lint.el, I have:
Package-Requires: ((emacs "29.1"))
in the metadata for the package. Turns out though that
sort
used to require two parameters (the sequence and the predicate), whereas now
it's fine with just one (it will accept just the sequence and will default
the predicate). So of course blogmore.el was working fine for me, but
would have crashed for someone with an earlier Emacs.
As for how I found this out... well I finally, for the first time ever, dived into using ERT to write some tests. While I've used testing frameworks in other languages, I'd never looked at this with Emacs Lisp. It works a treat and is great to work with; I think I'll be using this a lot more from now on.
Having got tests going I realised I should run them with GitHub actions,
which then meant I managed to discover
setup-emacs. Having
found this the next logical step was to set up a matrix test for all the
versions of Emacs I expect blogmore.el to work
on.
This worked fine, except... it didn't. While the tests worked locally, they
were failing for some Emacsen over on GitHub.
And that's how I discovered the issue with sort on versions earlier than
the one I'm using locally.
All in all, that was a good little period of hacking. New things discovered, the package improved, and a wider range of support for different versions of Emacs.
-1:-- blogmore.el v2.5 (Post Dave Pearson)--L0--C0--2026-04-02T19:53:09.000Z
Chris Mariana has an interesting post about making Git commit messages with Magit. Well crafted commit messages, he says, can tell a story about your project than can valuable in the future. The secret is in the “well crafted” part.
Rather than a bunch of messages like “Small edits” or “tweaked the foobar”, your commit message should describe what you actually did. Maiorana is a writer and his examples reflect a writing project rather than coding but everything he says applies regardless of what type of files you’re committing. Take a look at his post for examples of both good and bad commit messages.
The other nice thing about his post is that he shows how to use Git reporting to do some rough analysis. Magit, of course, has easy ways of doing this. Again, see Maiorana’s post for the details.
Finally, he offers some suggestions for writing better commit messages. These are aimed more at the story writer than the coder but, again, they can help someone writing code too;
I must admit to being guilty of writing terrible commit messages, especially for my blog posts. I’m very apt to use Maiorana’s example of “minor edit” instead of saying what I actually fixed. Sometimes, it just fixing a typo in which case “fixed typo” is fine but usually it’s something more extensive and a meaningful commit message helps me locate the commit I’m looking for.
-1:-- Your Commit Messages Should Tell A story (Post Irreal)--L0--C0--2026-04-02T14:41:53.000Z
I started using Emacs in the spring of 2022. I had been curious about it for a while and watched some videos by Protesilaos Stavrou and David Wilson that made it seem wroth trying. I thought I could try it out for a while to give it a chance and if it did not deliver, I could go back to Vim. At the time, I had been a Vim user for a couple of years and had simplified and improved the efficiency in my computing by using more CLI and TUI programs like NewsBoat (instead of gPodder and Liferea) and Calcurse (instead of Thunderbird Calendar) that I had then configured to use Vim keybindings. I felt that Vim's modal editing, even though I had become quite efficient, was a bit cumbersome even though people on YouTube boasted about how intuitive it felt to them (after "just" years of training their muscle memory), and I had trouble remembering some keybindings in Vim until I put a poster on the wall beneath my external screen with a keyboard and what every key did. I installed Emacs before a trip to Fredrikstad (maybe in the winter vacation or Easter vacation?) and on the ferry between Moss and Horten on my way back, I started working through the Emacs tutorial. Two years earlier, I did the same on the same ferry with the Vim tutor, but unlike the Emacs tutorial, I had to do the Vim tutor many times for the contents to really stick.
Even though I was exposed to the idea of Emacs as a cohesive environment before trying it through videos by Prot and David Wilson, I think a misconception I had early on was that it was a text editor like Vim, but with a bit more extensibility. It is not completely wrong because it is the better of the two/three best text editors in my present opinion, and that joke about it being an OS lacking only a decent text editor is obviously a joke for anyone that has used it for enough time to get to know it. For instance, I soon discovered that Emacs default keybindings made me faster than modal Vim keybindings since there are one or two key presses less for every edit. You don't have to press Esc to get into Normal Mode and you don't have to press i, o, a… to get into Insert Mode in addition to pressing keys for moving around. I had heard tales of awful key chords, but in reality, text editing used a modifier like Ctrl or Alt plus a key to move around, and I could just write to write. It felt a lot more natural and intuitive than Vim to me, even after using Vim for two years. Keybindings were also easier to remember in Emacs since they were mnemonic. (F for forward, b for backward, n for next, p for previous, d for delete, y for yank — and w for kill? Some years later, Mike Zamansky cleared that up in a video where he mentioned w was for whack, which made me laugh.)
Without the experience of a more cohesive workflow within Emacs, it is hard to imagine how useful an Elisp interpreter built around text editing can really be. Not having to bind keys to get your TUI programs to have the same keybindings as your text editor is one efficiency advantage of doing most things inside Emacs over (Neo)Vim + CLI and TUI programs, but the ability to extend or change functionality for anything you do with Elisp through well-documented functions and variables is the real superpower of doing most things within Emacs. People can tell you that, but to understand it, you have to experience it. It is not just a text editor with a scripting language for extensions, it is a programming language environment built around text editing. The use of a Lisp is also a great advantage since it is simple, easy and fast to learn and more efficient to use than other programming languages.
I did not have much time to explore Emacs in the beginning since I taught English while also studying IT, so in the start, I replaced Vim with Emacs as a text editor and kept using the rest of my CLI, TUI and GUI programs as I did before. I configured Emacs with settings from the Emacs from scratch video series by David Wilson. That worked well, and it improved my text editing speed, but I missed out on one of the main advantages of Emacs which is the cohesive work environment without context switching that Prot and David Wilson talked about until I found more time later to look into what else Emacs could do for me. I sort of knew all along that there was more to gain, and I did start to use Elfeed quite early on as a replacement for Newsboat, but otherwise, I kept using Emacs mainly as a text editor. There is nothing wrong with that, but to really gain the advantages of the cohesive work flow you can get by using Emacs, integrating other stuff into Emacs makes sense. Later on when I had more time, I did slowly replace one program at a time with Emacs modes or Emacs packages.
I think one of my main mistakes when learning Emacs was to rely too much on videos, blog articles and searches online instead of reading up more on the built-in documentation. The info reader felt a bit foreign with its somewhat strange keybindings and even though I realised C-h was there with a lot of other functionality as well, I did not really start using C-h k to find out what keybindings did, C-h m to find out what I could do in a mode, C-h v for looking up what a variable is or C-h f to look up functions, until a couple of years later. This was also related to not having much time to invest into learning Emacs in the start since I had started teaching at the vocational IT and Media production study line in the autumn of 2023 while still studying IT which made me extremely busy with making teaching materials since there exists no books for any of my subjects and at the time not much material was available on NDLA, a Norwegian digital learning portal used a lot in upper secondary (= US high) schools. If I had spent less time reading blogs and watching videos, but instead read up on the manuals and started using the built-in documentation more, early on, I think I might have come further faster. On the other hand, the great thing with blog posts and videos is that you get inspired by what and how other people use Emacs. Since configuring Vim is somewhat limited compared to programming Emacs, I was used to find most of the information I needed online and just occasionally dip into :help in Vim. Both the built-in documentation and online content are valuable, but I mainly relied on online content in the start which was a mistake.
-1:-- Emacs mistakes and misconceptions (Post Einar Mostad)--L0--C0--2026-04-02T12:23:00.000Z
: Updated screenshot. I finished reading all the pages and ended up with 202 highlights, so I'm going to have fun updating my config with those notes!
I've been trying to find a good workflow for highlighting interesting parts of PDFs, and then getting that into my notes as images and text in Emacs. I think I've finally figured out something that works well for me that feels natural (marking things.
I wanted to read through Prot's Emacs configuration while the kiddo played with her friends at the playground. I saved the web page as a PDF and exported it to Noteful. The PDF has 481 pages. Lots to explore! It was a bit chilly, so I had my gloves on. I used a capacitative stylus in my left hand to scroll the document and an Apple Pencil in my right hand to highlight the parts I wanted to add to my config or explore further.
Back at my computer, I used pip install pymupdf to install the PyMuPDF library. I poked around the PDF in the Python shell to see what it had, and I noticed that the highlights were drawings with fill 0.5. So I wrote this Python script to extract the images and text near that rectangle:
import fitz
import pathlib
import sys
import os
BUFFER = 5
def extract_highlights(filename, output_dir):
doc = fitz.open(filename)
s = "* Excerpts\n"
for page_num, page in enumerate(doc):
page_width = page.rect.width
page_text = ""
for draw_num, d in enumerate(page.get_drawings()):
if d['fill_opacity'] == 0.5:
rect = d['rect']
clip_rect = fitz.Rect(0, rect.y0 - BUFFER, page_width, rect.y1 + BUFFER)
img = page.get_pixmap(clip=clip_rect)
img_filename = "page-%03d-%d.png" % (page_num + 1, draw_num + 1)
img.save(os.path.join(output_dir, img_filename))
text = page.get_text(clip=clip_rect)
page_text = (page_text
+ "[[file:%s]]\n#+begin_quote\n[[pdf:%s::%d][p%d]]: %s\n#+end_quote\n\n"
% (img_filename,
os.path.join("..", filename),
page_num + 1,
page_num + 1, text))
if page_text != "":
s += "** Page %d\n%s" % (page_num + 1, page_text)
pathlib.Path(os.path.join(output_dir, "index.org")).write_bytes(s.encode())
if __name__ == '__main__':
if len(sys.argv) < 3:
print("Usage: list-highlights.py pdf-filename output-dir")
else:
extract_highlights(sys.argv[1], sys.argv[2])
After I opened the resulting index.org file, I used C-u C-u C-c C-x C-v (org-link-preview) to make the images appear inline throughout the whole buffer. There's a little extra text from the PDF extraction, but it's a great starting point for cleaning up or copying. The org-pdftools package lets me link to specific pages in PDFs, neat!
To set up org-pdftools, I used:
(use-package org-pdftools
:hook (org-mode . org-pdftools-setup-link))
Here's my quick livestream about the script with a slightly older version that had an off-by-one bug in the page numbers and didn't have the fancy PDF links. =)
You can e-mail me at sacha@sachachua.com.
-1:-- Extract PDF highlights into an Org file with Python (Post Sacha Chua)--L0--C0--2026-04-02T12:05:16.000Z
A quick follow-up to my earlier posts on eglot-python-preset and the beta multi-LSP release: both packages are now available on MELPA with full rassumfrassum↗ (rass) support included.
When I wrote the beta post in March, eglot-typescript-preset wasn’t on MELPA
at all, and eglot-python-preset’s MELPA version didn’t include rass support.
That’s no longer the case. You can install both with a standard use-package
:ensure t and get multi-LSP out of the box.
The main thing that landed since the beta: MELPA now ships the rass backend for
both packages. This means the rass LSP server option and all the preset
generation machinery are available without cloning repos or managing load paths
manually.
Both packages also now set up Eglot integration automatically when Eglot loads,
so you no longer need an explicit (eglot-python-preset-setup) or
(eglot-typescript-preset-setup) call in your config. Just install and go.
(use-package eglot-python-preset
:ensure t
:custom
(eglot-python-preset-lsp-server 'ty)) ; or 'basedpyright or 'rass
With the default ty backend, this gives you type checking for standard Python
projects and PEP-723 scripts with no further configuration. Switch to rass to
run ty and Ruff simultaneously:
(use-package eglot-python-preset
:ensure t
:custom
(eglot-python-preset-lsp-server 'rass)
(eglot-python-preset-rass-tools '(ty ruff)))
(use-package eglot-typescript-preset
:ensure t)
That’s it. The defaults configure typescript-language-server for TS/JS files
and use rass to combine language servers with Tailwind CSS support for CSS,
Astro, Vue, and Svelte buffers. If your project uses any of those frameworks,
the corresponding language server starts automatically when you open a file in
that mode.
To add linting alongside the TypeScript language server, switch the TS/JS backend to rass:
(use-package eglot-typescript-preset
:ensure t
:custom
(eglot-typescript-preset-lsp-server 'rass)
(eglot-typescript-preset-rass-tools
'(typescript-language-server eslint)))
The part I’m most pleased with is how much works without any rass-specific configuration on the TypeScript side. The default settings for CSS, Astro, Vue, and Svelte all use rass, combining each framework’s language server with Tailwind CSS support automatically.
For Astro, this means astro-ls runs alongside ESLint and
tailwindcss-language-server in a single Eglot session. Vue gets hybrid mode
where vue-language-server handles template features and
typescript-language-server with @vue/typescript-plugin provides type
checking, plus Tailwind. Svelte works similarly.
On the Python side, the rass backend lets you run ty for type checking and
Ruff for linting in the same buffer. PEP-723 scripts work too: the generated
rass preset includes the script’s cached uv environment so both tools resolve
imports.
All of this resolves executables from project-local directories first
(node_modules/.bin for JS/TS, .venv for Python), falling back to your PATH.
Both packages support per-project overrides via .dir-locals.el or
dir-locals-set-directory-class. The preset variables are declared as safe
local variables, so Emacs applies them without prompting. For example, if one
project uses Biome instead of ESLint:
;;; .dir-locals.el
((typescript-ts-mode
. ((eglot-typescript-preset-lsp-server . rass)
(eglot-typescript-preset-rass-tools
. (typescript-language-server biome)))))
-1:-- eglot-python-preset and eglot-typescript-preset: Now on MELPA (Post Mike Olson)--L0--C0--2026-04-02T00:00:00.000Z
I've just released a little update to
blogmore.el, adding
blogmore-toggle-draft as a command. This came about in connection with the
feature request that resulted in BlogMore v2.7.0 being
released.
While I don't personally use draft for my posts, I can see the utility of
it and if someone were to happen to use blogmore.el, it could be useful to
have this bound to a handy key combination.
As for how it works: that's simple. When run, if there is no draft:
frontmatter property, then draft: true is added. If draft: is there it
is removed. Yes, it does mean that it will also remove draft: false too
but... eh. Okay, fine, I might handle that case as a followup but I
couldn't really imagine someone wanting to keep draft: false in the
frontmatter.
If a post is ready to go, why bother with a header that means the same thing when it's not there?
-1:-- blogmore.el v2.4 (Post Dave Pearson)--L0--C0--2026-04-01T19:00:44.000Z
Some time in the late 1990s, after I'd been using GNU Emacs for a few
years1, I grabbed a copy of Writing GNU Emacs
Extensions.
While I'd obviously created and added to and tinkered with my ~/.emacs
some, I'd never written any non-trivial code. I feel it was around 1998 or
1999 that I really started to get into trying to write actual extensions,
thanks to that book.
I can't remember what the first complete package was. I think it was
actually 5x5.el2 but it might also have been
binclock.el. Honestly, it's so
long ago now that I don't have any good recollection and I don't have any
record3. All of which is to say, binclock.el is one of my oldest bits
of Emacs Lisp code, so it seemed fair that while I'm
cleaning things up, I should give it a tidy too.
For anyone curious: it's a very simple binary clock type of thing. It opens a very small window and, depending on your settings, shows the time in various ways, all of which are in some way a binary display.
I can't say I've honestly ever had it running for more than a few moments, as an amusement, but I do remember it being a really helpful body of code to work on to help get familiar with Elisp. So, 27 years on from when I first opened the buffer to create it, it's tweaked and tidied and hopefully ready for another 27.
Would be cool if I'm around long enough to give it yet another tweak then.
-1:-- binclock.el v1.12 (Post Dave Pearson)--L0--C0--2026-04-01T13:30:18.000Z
This bit of Emacs Lisp absolutely comes from a more
innocent time on the Internet. Looking at it, it seems I wrote the first
version, at least as a proper package, back in 2001. It's very possible that
I carried a non-package version of it around as part of dp-lib.el1 for
some time before, so it might date from the late 1990s. While we weren't
absolutely innocent back then, the idea of a slightly-risky-looking URL
wasn't quite so bad as it is now.
So obfusurl.el came about from my time on Usenet, where sometimes you'd
want to post a URL that would otherwise be a spoiler. This package was my
solution to that. It's a simple idea: keep the protocol and domain and so on
visible, just hide the remaining part. So rather than post:
https://blog.davep.org/about/
You'd post:
https://blog.davep.org/%61%62%6f%75%74/
I suppose this is still useful today, although I would expect a lot of people to be way less likely to want to attempt that click -- readable domain or not.
But, anyway, the code needed a tidy and cleanup for today's Emacs and Emacs
Lisp. So obfusurl.el v2.2 now
exists.
For a good chunk of my first decade of using Emacs, I carried a lot of personal code around in a rather large "library" file. ↩
-1:-- obfusurl.el v2.2 (Post Dave Pearson)--L0--C0--2026-04-01T09:13:57.000Z
Okay, that's it then; this is turning into a thing I
think. Second in an occasional series of
posts where I tidy up some of my old Emacs packages.
This time I dug out eg.el and cleaned up
some of the frowned-upon
behaviour.
For anyone who doesn't know it: eg.el is one of many Norton
Guide readers I've written over the
years. This particular one was
possibly the most fun as it was the most unlikely one to write and also, I
think it's fair to say, my most ambitious one as well. It also holds a
special place for me in that the bulk of it was written on
trains during multiple trips up to Scotland, before I finally
moved here (on the MacBook Air I
mentioned the other day).
As I look at it now, I sort of want to give it a proper revisit. I've written more Norton Guide code since (see AgiNG for example) and have learnt better ways to handle certain things, and I also have an even bigger Norton Guide collection to test against. All of this could make for an even better Emacs implementation.
I also suspect that, this time around, I can do a better job of handling the colour and retaining the original Norton Guide reader look and feel. But, for now, the code has been tidied up and should keep working for some time yet.
-1:-- eg.el v1.2 (Post Dave Pearson)--L0--C0--2026-03-31T20:10:58.000Z
: Hooray for learning out loud! Prot has already posted his responses.
Following up on Emacs Carnival March 2026: Mistakes and learning to reach out: I want to get better at learning with other people's help, so I'm going to experiment with engaging Prot as an Emacs coach. Our first session is this week. Time to lay the groundwork!
If I meet with Prot twice a month for three months, that's a budget of €60 (~CAD 100), which is a reasonable size for an experiment especially since I still have the budget set aside from the Google Open Source Peer Bonus and lovely folks already donated to cover the costs for EmacsConf. When I schedule something with someone, the accountability makes it easier to get stuff done and out the door. For this, a real person is much better than AI because:
My motivations:
I want to make better use of my focused time during the rest of the schoolyear. For the next three months, my schedule will be fairly predictable and I'll have regular chunks of focused time. Over the past two months, I've averaged around 10 hours of Emacs-related stuff per week (including 1.5 hours or so for Emacs News). I'm currently thinking about language learning and speech input. EmacsConf is on the horizon and will probably ramp up after September, but I can also think ahead of workflow improvements or ways to collaborate with other people. I might put together an Emacs News Highlights presentation. Also, I'm always looking out for ways to build the community.
Summer break during July and August will shake things up again, but I might be able to find some focused time early morning or evening. I'd like to be in a good position to make the most of those time fragments.
package-isolate and restart-emacsCurrent: ~1.5 Emacs posts a week aside from Emacs News, attending meetups, sporadically adding short video demos to posts
Average number of Emacs-related posts that aren't Emacs News(let* ((start "2026-02-01")
(end "2026-03-31")
(posts (my-blog-posts
start end
(lambda (o)
(and (member "emacs" (alist-get 'categories o))
(not (member "emacs-news" (alist-get 'categories o)))))))
(count (length posts)))
(my-weekly-average count start end))
Starting questions for Prot:
Could be fun. Let's experiment!
You can e-mail me at sacha@sachachua.com.
-1:-- Thinking about Emacs coaching goals with Prot (Post Sacha Chua)--L0--C0--2026-03-31T18:37:44.000Z
Given the recent spate of hacking on some Emacs Lisp, I've got a real taste for hacking on some more. Or, more to the point, revisiting some of the packages I have in Melpa and tidying them up where necessary.
The main thing I'll need to address is cutting back on all my old setf
ways. I liked that approach to
doing things, it made lots of sense and felt elegant; sadly the Emacs
maintainers didn't seem to agree.
So... kicking this off I've released v1.13 of
thinks.el. This is a bit of nonsense
I wrote back in the days when Usenet was still pretty busy and the place
to be (well, okay, back in 2000 when I was still hanging out on Usenet). The
package itself lets you quickly and easily...
. o O ( ...write some text and then mark it all and then run a command and )
( have it turned into something that looks a little like a thought )
( bubble. )
It has some variations on how the bubble looks, and also lets you use
customize to tweak the characters to use, and also has an "extra silly"
mode too.
Updating this wasn't too bad. Mostly just a case of turning some instances
of (setf (point) ...) into (goto-char ...), and also modifying one
instance of incf to be cl-incf.
Honestly, I don't know how useful this package is to anyone anymore. Most folk don't even know what Usenet is these days, and all the "social" places seem to favour non-monospaced fonts, meaning the bubbles would look pretty terrible anyway.
On the other hand, it seems a shame to not update it, and perhaps someone somewhere still uses it to make some pithy parenthetical remark, possibly about September never ending.
-1:-- thinks.el v1.13 (Post Dave Pearson)--L0--C0--2026-03-31T18:35:16.000Z
I've bumped blogmore.el to v2.3.
The main change in this release, which I've had on my mental to-do list for
a couple of days now, revolves around categories, tags and case.
I've got BlogMore set up so that it's pretty
relaxed about case when it comes to categories and tags; so when it comes to
generated files and URLs everything collapses to lowercase. However,
blogmore.el wasn't taking this into account.
While it wasn't a serious issue, it did have the side-effect that, if you
had a tag of lisp and a tag of Lisp, both would appear in the list of
tags you could complete from. Also, when you went to add a tag to the tags
frontmatter (via the blogmore-add-tag command), if you selected Lisp and
lisp was already there, you'd end up with both versions after the command
finished.
As mentioned earlier: BlogMore itself would collapse Lisp and lisp to
the same tag; the downside here is you'd see both tags shown in the post
itself. Not a real problem, just not very tidy.
So earlier I changed things up a little; first cleaning up when loading pre-existing values and then ensuring the newly-set tags are deduplicated.
This now means I can edit and update a post even faster, without needing to worry about accidentally duplicating tags. This in turn reduces the friction to writing a post for my blog. That is, after all, the whole point of the name of the package and the blogging tool it's connected to!
-1:-- blogmore.el v2.3 (Post Dave Pearson)--L0--C0--2026-03-31T14:31:41.000Z
Spell checking in Emacs is one of those things that should just work but requires a bit of setup to get right, especially if you want a specific dictionary like Canadian English. This post walks through getting hunspell running in Doom Emacs with the keybindings you need to actually use it.
Doom's spell module supports both aspell and hunspell. After looking around it seemed like hunspell had better dictionary support for non-US English variants, so it was my choice for Canadian spell checking.
Hunspell is available for Ubuntu, Fedora, and macOS via Hombrew. I'm on NixOS so I'd add the two packages I need and then run sudo nixos-rebuild switch to get them installed.
environment.systemPackages = with pkgs; [
hunspell
hunspellDicts.en-ca
];
Open ~/.config/doom/init.el and find the :checkers section. Enable the spell module with the +flyspell flag:
:checkers
syntax
(spell +flyspell)
The +flyspell flag uses Emacs' built-in flyspell, which works reliably with hunspell. I didn't use the +hunspell flag because after some looking around I'd need to generate a list of words.
Run doom sync and restart Emacs after changing init.el.
In ~/.config/doom/config.el, tell Emacs to use hunspell and set your dictionary:
(setq ispell-program-name "hunspell"
ispell-dictionary "en_CA")
Swap en_CA for en_GB, en_AU, etc. if you need a different variant. The dictionary name needs to match what hunspell has installed — you can check with hunspell -D in a terminal to see available dictionaries.
Reload your config with SPC h r r.
Flyspell doesn't run automatically everywhere — Doom enables it in text and org buffers by default, but you can toggle it manually with:
| Key | Action |
|---|---|
SPC t s |
Toggle flyspell in the current buffer |
When active, misspelled words are underlined.
Once flyspell is running, these are the keybindings you'll use most:
| Key | Action |
|---|---|
] s |
Jump to next misspelling |
[ s |
Jump to previous misspelling |
z = |
Show correction suggestions for word at point |
SPC z = |
Same, works in more contexts |
When you press z =, a list of suggestions pops up numbered. Press the corresponding number to accept a correction.
You can also right-click a highlighted word to get suggestions if you prefer the mouse.
If flyspell flags something that's correct like a name, a technical term, or an acronym you have a couple of options:
| Key | Action |
|---|---|
z g |
Accept word and add it to your personal dictionary |
z w |
Mark word as incorrect (force flag it) |
Words added with z g go into ~/.aspell.en_CA.pws (or equivalent for your dictionary) and won't be flagged again.
If you switch between languages in different files, you can change the active dictionary without touching your config:
M-x ispell-change-dictionary
Type the dictionary name (e.g. en_GB) and flyspell will re-check using that dictionary for the current session.
-1:-- Check Spelling in Emacs (Post Curtis McHale)--L0--C0--2026-03-31T13:00:00.000Z
Sacha Chua contacted me to schedule a coaching session later this week. She wrote about it here: https://sachachua.com/blog/2026/03/thinking-about-coaching-goals-with-prot/.
I maintain a strict privacy policy with everyone I meet. Specifically, I do not say anything about our meeting. But since Sacha has already published this information, I am happy to do this in the open.
What follows are some comments on her post.
writing tests, especially for things that are more interactive
What helps here is to think of the interactive part as the way to get the arguments. If the interactivity is more involved, then you want to think how it can be broken down into smaller routines. Each routine should eventually be reduced to a function that can be called non-interactively with a certain argument. This way, your tests are easier to reason about.
Consider this example:
(defun my-greet-person (name)
"Return Hello string to person with NAME."
(format "Hello %s" name))
The substantive part of the test would be something like this:
(let ((name "Sacha"))
(string= (my-greet-person name) "Hello Sacha"))
Now add interactivity to the function:
(defun my-greet-person (name)
"Return Hello string to person with NAME.
When called interactively, prompt for NAME. Else NAME is a string."
(interactive (list (read-string "Whom to greet: ")))
(format "Hello %s" name))
Even though this function can be called interactively, the test is the
same because the interactive simply sets the value of name.
There will, of course, be more complex scenaria. We can think how best to approach them. Though this is the general idea.
navigating code that might be scattered in literate config files or in Emacs Lisp files
What I find helpful:
bookmark-jump
or consult-buffer (from Daniel Mendler’s consult package).~/Git/.~/Git/emacs-community/ and ~/Git/Projects/. The
latter consists of everything I develop/maintain.xref-find-definitions as well as all the help
functions like describe-function which normally link to the file
where the definition is.If I get better at sharing what I’m working on, I might be able to connect with more people and bounce ideas around.
Getting better is nice. I think here the goal is to structure what you are sharing in a certain way. Then people can use it more easily. Once that happens, you will receive more feedback.
Also, accountability might help me nudge this over the threshold.
This is key. When we make a promise in earnest, we are motivated to deliver on it. The fact that you have published this adds to the effectiveness of it.
I’m curious about other people’s workflows for sharing. I like joining meetups, but I tend to share stuff only if no one else has anything planned, because I have my blog and my YouTube channel in case I want to share anything with a wider group of people. I just have to actually post things.
Each person is different and there is no one answer to rule them all. What I do, as someone who publishes on a number of topics, is to reach a point that is an honest representation of my current level. This point is not approaching perfection, as that is a trap. If it were about perfection, I would never publish anything!
Once I do what is within my current level, I am casual about it. In other words, I do not need to prove that I am worthy of it—I am already there and this is my current normal state. This makes the process of writing less emotionally challenging (well, not challenging at all). It also opens me to learn more. I am not defensive or argumentative because, fundamentally, I feel secure with what I have: I am not hiding something and do not worry about what others may think.
About your case, I get the impression that you are already improving your content. It starts by recognising that there is improvement to be had. Then, you write blog posts such as the one I am now commenting on, which show that you have put thought into your processes. In other words, you are mindful of your current state. Whatever I may point out during our meeting will thus be easier for you to incorporate in your thinking. Why? Because you already know the space, as it were, and so you will have a good intuition of where to put the new thing.
Streaming: Still need to get the hang of talking to myself or having half-conversations with chat: can be worked around by scheduling a session with Prot and opening it to the public
I am happy to do this in public. Either as a coaching session or some collaborative live stream. We can discuss the details.
At any rate, “practice makes perfect”. The only way to get used to talking to the camera is to do it enough times. I can talk at length, though I still find it hard to laugh when I am by myself, so I look dead serious in all of my monologues. Whereas, say, in the “Prot Asks” series I often laugh. This is because I have a natural response towards someone. Talking to the selfie camera does not create in me the same friendly emotions.
renaming things when I want to move them to a library
Before finding a name, you need to have a clear vision for the package: what is it meant to do. Then try to think about words that describe either the goal or the workflow. Use phrases, like what you have with “speech input”. Those work fine.
Come up with placeholder names if you are not sure. Then, once you are ready to share the package, do a final round of thinking to check if you can think of a more suitable name. Otherwise just use some descriptive phrase.
This concerns the prefix for the entire package. Though your code may
still consist of different areas of focus. For example, in my denote
package there is a subset of functionality related to “rename”
operations. All of those share a compound prefix of the name of the
package plus the name of the area they are specialising in like this
helper function: denote-rename-buffer--format. By the name alone, I
can tell that it relates to the “rename” operation and, specifically,
is ancillary to denote-rename-buffer.
I can provide concrete suggestions for your code.
duplicating small functions (ex: simplify string)
You may choose to put those in their own package. Though I personally do not mind a little bit of duplication/repetition when that is easier to maintain. The principle of not repeating yourself is good in general, though there are cases where trying to avoid it is not worth the effort.
figuring out how to make it possible for someone else to start using my stuff
For any non-trivial code you write, you want to treat it like its own
“package”. In other words, it exists in a file of its own, it has all
the require calls for its dependencies, defines defcustom
variables if it must, uses autoload where relevant, and has a
provide call at the end. Even if you never move it out of your
configuration, you have already done the work of clearing up your
thoughts/code. Others will already benefit from that, as they can now
copy the file with greater confidence in its utility.
Meta: what are people finding useful for coaching and behaviour change, like learning new keyboard shortcuts or workflows?
Each person has their own goals. Some enjoy a pair programming session. Others like me to check on their progress and to provide feedback. Plus, there is more than the purely Emacs component: I make comments about matters of perspective, whether it is about some piece of code or life in general.
Those granted, I do not collect any data about the people I meet. I do not ask them for testimonials or feedback. I prefer not to do that because I do not wish to ever have to handle private information. I like my meetings to be nice and simple. Plus, I do not want to manipulate or influence the behaviour of people.
Your literate config exports to individual .el files. I could probably do something similar to separate my functions from my personal config in order to make it easier for people to reuse parts of my config. Is it worth doing so? Do people tell you that they use those private Emacs Lisp files by loading them, or do they mostly rely on your published packages?
Most rely on my packages. I design those to be as flexible as possible and maintain them accordingly.
The individual .el files of my configuration are helpful to me. I
stay in the flow of designing my code in a package-ready way. If
anybody needs to use it, then they already have something that is
close to an actual package.
Do you have some tweaks to make it easier to jump to function definitions considering a literate configuration?
No, I have not had a need for this. When I choose to work on some part
of my configuration, I navigate to the relevant heading (with
something like consult-outline) and then use org-edit-special to
edit the source block.
You will show me what you have been doing, which may give me some ideas.
What’s your general process for migrating things from your config to a repository or package?
It all starts with splitting the code into many .el files. Make sure
one file is not entangled with other files. Or, at least, put in the
effort to list every other file as a dependency and write the
necessary require for it.
Have one such file for each area of focus. This way you can reason about what you have and what may be missing. A clear initial idea will determine the direction of the package long-term. The reason is that it establishes boundaries: what to do and what not to do.
From there, you can decide if some file is of value to other users. If
you think it is, then start implementing defcustom variables for it,
define the commands that users would want, and have autoload
directives for them if they are meant as points of entry.
-1:-- Emacs coaching with Sacha Chua (Post Protesilaos Stavrou)--L0--C0--2026-03-31T00:00:00.000Z
It really feels like BlogMore has kicked off a whole new thing when it comes to personal hacking. During the past few years I've developed a lot of Python applications and libraries, and have had a ton of fun doing so, but during that time I've not really done anything with writing stuff for Emacs.
To a large degree I think this says something about how stable Emacs is for me (I've been using it for a touch over 30 years at this point, you'd think I'd be kind of settled with it), but it's still always fun to have a reason to hack on some Lisp code. There's little doubt my Lisp -- especially Emacs Lisp -- has got a wee bit rusty.
So I'm having a lot of fun at the moment falling into the rabbit hole of
expanding on and tinkering with
blogmore.el. The reason I've just
made v2.2 is a good example of exactly this. There are no real user-facing
changes in the code, it was all things I just wanted to tidy up.
The main thing that has been bugging me for the past day is the repeating boilerplate that resulted from adding all the different current-blog-aware setting getter functions. There were 7 different functions, all looking like this:
(defun blogmore--post-maker-function ()
"Get the post maker function for the current blog."
(or
(blogmore--blog-post-maker-function (blogmore--chosen-blog))
blogmore-default-post-maker-function))
Exact same pattern, the only thing different being the name of the getter function being called on, and the name of the variable that contained the global default value.
So just a little earlier I cleaned this up using one of my favourite things
about Lisp: defmacro. There's something about macros that makes me really
like coding in Lisp, and which I cite as a really good thing when asked
why I like Lisp, but which I always seem to utterly fail to explain well.
Macros feel like one of those things you just have to experience for
yourself to really get1.
Now, thanks to this:
(defmacro blogmore--setting (setting)
"Generate a function to get the value of SETTING for the current blog."
`(defun ,(intern (format "blogmore--%s" setting)) ()
,(format "Get the %s for the current blog." setting)
(or (,(intern (format "blogmore--blog-%s" setting)) (blogmore--chosen-blog))
,(intern (format "blogmore-default-%s" setting)))))
all those 7 functions can collapse to this2:
(blogmore--setting post-template)
(blogmore--setting post-maker-function)
(blogmore--setting category-maker-function)
(blogmore--setting tag-maker-function)
(blogmore--setting post-link-format)
(blogmore--setting category-link-format)
(blogmore--setting tag-link-format)
Now the code is shorter, cleaner, and if I need to change anything I only need to change it in one place. Sure, the latter part especially is one of those "you could do that with a function too" things (have the work in one place), but here I can get the language to write me a whole load of functions, all of which refer to different functions and variables, each one based off just the one symbol.
The point of all of this being: v2.2 of blogmore.el is now out, it adds
nothing for the user (who I suspect is only me anyway), but I had an
absolute blast dusting off more of my Emacs Lisp knowledge and getting back
the urge to code even more Emacs Lisp.
All of this has even got me tidying up my ~/.emacs.d/ and has me
thinking I should go back through some of my older code and clean up all
that legacy nonsense.
-1:-- blogmore.el v2.2 (Post Dave Pearson)--L0--C0--2026-03-30T19:31:03.000Z
The recent post “You don’t not need the mouse” by noa ks speaks to a sentiment that I’ve had for some time. Using the mouse in Emacs can be a good, daresay delightful, experience. Unfortunately though, Emacs has antiquated default settings that presume we’re all still using a 90’s style 3-button workstation mouse. In addition, the overreliance on reusing menu keymaps for both the main and context menus results in poor user experience. I feel strongly that context menus populated this way feel more like an inventory than a thoughtful selection of commands relevant to context.
Thankfully, Emacs offers the mechanisms to sculpt mouse interactions to contemporary (circa 2026) expectations. Over the past three years, I’ve taken advantage of them to implement the following features:
Mode Line
Context Menu
use-region-p)Main Menu
Several months ago, I decided these mouse interaction changes should be generalized into a package that others could use. So began the Anju project.
Today I’m happy to announce that Anju v1.0 is now available on MELPA.
Learn more details about Anju in its User Guide.
As Anju is new, I’m always open to constructive feedback on it. Let me know what you think. Work on Anju is ongoing with the plan to keep adding improvements to it over time, in particular with supporting more context menus for different modes.
-1:-- Announcing Anju (Post Charles Choi)--L0--C0--2026-03-30T15:45:00.000Z
I've given blogmore.el a wee bump
to v2.1. This release fixes a small problem I noticed today when I tried to
use it to edit the tags for a post on my
photoblog: the code
to find and gather properties from posts didn't handle deeply-nested
directory hierarchies for the post markdown files. I should have noticed
this when I first wrote the code, but of course I was so busy testing
against my primary blog, which only goes one sub-level deep, that I never
noticed it wasn't going to work deeper.
So rather than using grep to look for things like foo/**/*.md I swapped
to a combination of find and grep. Which works, but is slightly (but
noticeably) slower.
Then I got to thinking that if I was doing this by hand, on the command
line, I'd be using ripgrep anyway.
Given this I might as well use it here. Of course, not everyone who might
use blogmore.el will have rg installed so it makes sense to look for
that and use it if it's available, otherwise fall back on find/grep.
There's still some low-priority cleaning up I want to do around this; an obvious change I want to make being one where I want to collapse all cases of the same word (Tree vs tree, etc) into one "hit"1. For now though, as always, it's working well enough for my needs and this change fixed an obvious issue I ran into.
-1:-- blogmore.el v2.1 (Post Dave Pearson)--L0--C0--2026-03-30T15:20:22.000Z
Anyone who’s been around Irreal for a while knows of my fascination with Lisp Machines. Part of my love or Emacs is that it’s (sort of) a reimagining of the Lisp Machines. Sadly, I never had a chance to work on an actual Lisp Machine but my fascination and love for them continues.
The idea of the Lisp machine is that although it had pretty much standard hardware components, it had specialized microcode that optimized it for running Lisp. For me, the real win was the software more than the hardware. That software still exists but is encumbered so it’s not available without a steep payment or a bit of piracy. That means that for those who, like me, love the idea of the Lisp Machine, the closest we’re going to get is Emacs.
Ketrainis over at Asianometry has an excellent video that recounts the history of the Lisp machines from their birth to their demise. They were hundreds—perhaps thousands—of times slower than today’s cheap laptop but they were built by and for hackers and everything was user customizable just as with Emacs. Even the microcode could be reprogrammed. Joe Marshall has a great post that describes his reprogramming the Lisp Machne microcode to break DES.
This customizability was a huge benefit that every Emacs user will identify with. If the software didn’t support what you needed it to do, you could simply change it—even at the microcode level—to get the behavior you needed.
Ketrainis’ video is 45 minutes, 21 seconds longs so you’ll definitely need to schedule some time but if you have any interest in Lisp Machines, it’s worth your while.
-1:-- Lisp Machines! (Post Irreal)--L0--C0--2026-03-30T14:13:41.000Z
It's not too late to write about mistakes and misconceptions as part of the Emacs Carnival for March and not too early to think about the theme of "Newbies/Starter Kits" which Cena will be hosting for April. Who knows, maybe those ideas can become part of the newcomers presets. It could be fun to explore something like notes for Emacs beginners and see where you end up.
Also, I'm looking forward to seeing if these tips for reloading Emacs Lisp code can help me avoid little bugs from leftover code.
Enjoy!
Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!
You can e-mail me at sacha@sachachua.com.
-1:-- 2026-03-30 Emacs news (Post Sacha Chua)--L0--C0--2026-03-30T13:57:50.000Z
Pull up git log --oneline on a project—or open the Magit log with l l from the status buffer and you might see two slightly different stories.
Either you’ll see this:
f31e4a2 WIP c8b29f1 some minor edits 9aa55d3 chugging through 8f72cc0 some tweaks 3d19c44 some more tweaking
Or you’ll see something like this:
f31e4a2 Finished the confrontation scene w/ Megan c8b29f1 Fleshed out Ethan's backstory in chapter two 9aa55d3 Bar fight scene, in 8f72cc0 Further developed Ethan's voice 3d19c44 Initial commit, project structure and outline
Both sets of commits represent the same amount of work. The only difference is the two minutes it took to write a real commit message instead of a placeholder.
There’s been a lot of discussion in recent years about what to name your branches, which can be anything you want. However, has anyone taken a hard look at some of the hard-coded git terminology, like the word “commit”? In my Git For Writers handbook, I compared commits to “save states” or “snapshots.” I’d always felt the word “commit” sounded too permanent and fixed, but that’s not the case at all.
The word commit has a richer meaning than at first appears. Derived from a Latin root meaning to entrust, it’s the same as committing something to memory, or entering a transaction into a ledger, more like a notation than a final verdict. That’s exactly what you’re doing when you commit your work in git: entrusting a session or chunk of work to the record. Nothing is declared finished or locked in, but you are merely saying: this is what I did today or this is where I was when I stopped today.
Every commit is like a moment in the life of your project. When you string those moments together with clear, meaningful messages, something useful emerges. You can look back and see not just what you wrote, but how you wrote it.
Think of all the information you might glean from a well-documented commit log.
The journey is not visible in the finished project itself, which is fine; the reader need not know how you got there. But as the writer, you ought to know how it happened.
Here is an example commit log for a short story project, read from oldest to newest.
a1f3309 Initial commit, project structure and notes b22e117 Opened the story, established the narrator's tone cc49852 Drafted the first scene, setting and introduction d78f441 Pushed through the second scene, conflict introduced e830192 A turn, starting to click f91b004 Rough draft complete g04a215 First revision pass, tightened the opening h17d331 Cut the second paragraph of scene one entirely i2a8c62 Addressed group critique notes, softened the ending j30f577 Final proofread pass k498d00 Submitted to Mudflap Review
You may also consider tagging the final commit with something like “first_submission,” or “version1.”
Reading that log tells you the story about the story. You can see where you found the piece (“starting to click”). You can see the revision arc. You can see when external feedback came in and what changed. You even notated when it was submitted, which might be unnecessary, but that would depend on your chosen level of depth.
The moment right after a writing session is a great time to consider what you accomplished. The session is fresh in your mind. You know exactly what happened. And if you’re unsure, you can look at the unstaged changes.
Something I really like about Magit is how can easily split and stage individual hunks. This can help you group changes, or exclude certain edits, under one specific commit.
Instead of typing “did some stuff” and moving on, take a few seconds and answer the question: what actually happened in this session?
You may consider:
Create a decent commit message about it.
Here are some examples:
For years, I wrote pretty limp commit messages, like “Some minor editing” and “more minor editing,” etc. Looking back, this tells you almost nothing about your process. You’d have to look at individual diffs to actually see what happened.
A good commit message costs almost nothing to write but can really help elucidate the dizzying twists and turns of a long writing project, and can also help keep you on track when you get lost in the weeds.
Beyond the messages, the data inside each commit tells you something about what kind of work you were doing.
Run this command to see the number of lines changed in each commit:
git log --oneline --stat
See where your patterns lie.
In the early drafting phase, you may see more insertions. In the revision phase, you may see the ratio shift as more deletions appear.
If you’re seeing a revision pattern when you should be in full drafting mode, that’s useful data. It might mean you’re editing too early, or that’s just your style and not a big deal. The numbers and graphs can help make your otherwise invisible habits visible.
Commit at natural stopping points. Every paragraph is perhaps too frequently. But once a week is perhaps too much. Commit when you feel good about what you did on a section or specific area. Think about that metaphor of entering a record into the ledger.
In a previous post on this topic, I suggested that doing a commit every 250 words changed was a pretty good metric. This is highly subjective. If your case, it might be closer to 500 or 1000 words changed. But I’d say 250 is still a good low end. You probably wouldn’t want to commit to fewer words, unless perhaps you solved a critical problem or hit a milestone and want to note that in the history. It’s up to you.
Use the word-diff for a review of what you just did. This is one of the most valuable git master-level techniques I’ve employed over the years. Before committing, take a moment to review your changes at the word level—what you deleted and what you added:
git diff --word-diff HEAD~1
In Magit, press d r from the status buffer. Magit’s diff view highlights added and removed words, which gives you the same at-a-glance picture of what changed.
With this information, you can write a stellar commit message.
Write the message now, don’t wait.
Be honest in the message. If the session was hard, say so. If you had the best session in months, notate that. Your future self will appreciate it.
Use notes if you need more details. Commit messages are best kept to one line, but notes can give you more space to ramble on.
Use tags for major milestones. A commit message records the daily work. A tag records the landmark.
git tag -a rough_draft -m "Rough draft finished, 62,000 words"
When you look back on the project, tags let you jump directly to the moments that mattered most.
Basically, let your commits tell the story. I think you’ll get a lot of value out of this.
If you have any comments or questions about this workflow, or if you have some similar strategies, be sure to leave me a comment below.
The post Let the commits tell the story appeared first on Chris Maiorana.
-1:-- Let the commits tell the story (Post Chris Maiorana)--L0--C0--2026-03-30T11:00:51.000Z
It took me a decade to try Emacs again, for reasons totally unrelated to computer programming (task management!), and a lot of effort by my pal Sascha Fast. In hindsight, I realize I had to learn a couple of things first.
Emacs is clumsy and old. I didn’t know that a GUI Emacs version existed, could display images and scroll somewhat smoothly, coming from an IT department with SUN terminals where we used Emacs on the command-line to edit some .c source files. By modern standards, it’s lean and snappy and can do interesting things using your OS’s window manager for multi-frame workflows (like displaying a ‘Speedbar’ for symbol navigation). In 2026, Emacs will learn to efficiently draw at 60+ FPS to a canvas, multiple in parallel even, to display movies and play video games. I’m not joking!
Text is not enough. Text, I realized eventually, is plenty! The customizable Org Agenda got me hooked: filter through tasks, display a magic ‘UI’ that fits my needs, amazing. That’s all possible because Emacs deals with (not necessarily file-backed) buffers all the way down. With directory listings, server management, and using Emacs to complement and complete computing all around, the game has changed. Instead of TUI, I read for … EUI?
I’ll only use this for to-do’s. I absolutely didn’t. 7 years (oh god) later I’m still discovering new excused to use Emacs for new things. Starting with task management, I also made this my Writeroom-like writing workspace, used it for copy-editing books and innumerable blog posts in various projects, tweaked and learned to love a custom key binding mechanism, moved light scripting over, then more and more programming and web development tasks. Email, chat, eventually LLM chat interfaces and Agentic Engineering; everything is being swallowed by the Universal Paperclip machinery that is this weird Lisp interpreter.
I will copy and paste config snippets most of the time. I used StackOverflow, Sacha Chua’s blog, Xah Lee’s blog and Elisp reference as stepping stones. I still grab stuff from other people’s configurations and test-drive them. But I also found a strange joy in writing Lisp, and got into reading Structure and Interpretation of Computer Programs and finally understood the mechanics and beauty of the composition so much better. In a way, it helped me think more clearly about functions, composition, and what makes good software.
I didn’t sign up for all that happened at first. But I got sucked in, and as I mentioned in passing so many times, Emacs is an isle of computing freedom in an environment of ever tighter sandboxes, locking-down and dumbing down computers, making the operating system UI ugly as sin – it’s not great to be in love with computers unless you find your way onto a capable Linux machine. Or, like me, who’s stuck on a Mac for work, use Emacs to maintain their sanity. (That’s probably not a line any longer-term Emacs user ever said.)
Get new posts delivered to your inbox — subscribe.
-1:-- Emacs Mistakes and Misconceptions That Held Me Back in 2019 (Post Christian Tietze)--L0--C0--2026-03-30T05:44:01.000Z
Creating Emacs color themes is a topic I hadn’t thought much about in recent years. My first theme (Zenburn) has been in maintenance mode for ages, and Solarized mostly runs itself at this point. But working on my ports of Tokyo (Night) Themes and Catppuccin (Batppuccin) made me re-examine the whole topic with fresh eyes. The biggest shift I’ve noticed is that multi-variant themes (light/dark/high-contrast from a shared codebase) have become the norm rather than the exception, and that pattern naturally leads to reusable theming infrastructure.
The task has always been simultaneously easy and hard. Easy because deftheme
and custom-theme-set-faces are well-documented and do exactly what you’d
expect. Hard because the real challenge was never the mechanics – it’s knowing
which faces to theme and keeping your color choices consistent across hundreds
of them.
Note: In Emacs, a face is a named set of visual attributes – foreground color, background, bold, italic, underline, etc. – that controls how a piece of text looks. Themes work by setting faces to match a color palette. See also the Elisp manual’s section on custom themes for the full API.
The traditional way to create an Emacs theme is to write a deftheme form,
then set faces one by one with custom-theme-set-faces:
(deftheme my-cool-theme "A cool theme.")
(custom-theme-set-faces
'my-cool-theme
;; The `t` means "all display types" -- you can also specify different
;; colors for different displays (GUI vs 256-color terminal, etc.)
'(default ((t (:foreground "#c0caf5" :background "#1a1b26"))))
'(font-lock-keyword-face ((t (:foreground "#bb9af7"))))
'(font-lock-string-face ((t (:foreground "#9ece6a"))))
;; ... 200+ more faces
)
(provide-theme 'my-cool-theme)
This works fine and gives you total control. Many excellent themes are built exactly this way. In practice, a lot of new themes start their life as copies of existing themes – mostly to avoid the leg-work of discovering which faces to define. You grab a well-maintained theme, swap the colors, and you’re halfway there.
That said, the approach has a couple of pain points:
list-faces-display is your friend
here, but it only shows faces that are currently loaded.One more gotcha: some packages use variables instead of faces for their colors
(e.g., hl-todo-keyword-faces, ansi-color-names-vector). You can set those
with custom-theme-set-variables, but you have to know they exist first. It’s
easy to think you’ve themed everything via faces and then discover a package
that hard-codes colors in a defcustom.
How big of a problem the face tracking is depends on your scope. If you only
care about built-in Emacs faces, it’s pretty manageable – that’s what most of
the bundled themes do (check wombat, deeper-blue, or tango – they
define faces almost exclusively for packages that ship with Emacs and don’t
touch third-party packages at all). But if you want your theme to look good in
magit, corfu, vertico, transient, and a dozen other popular packages,
you’re signing up for ongoing maintenance. A new version of magit adds a face
and suddenly your theme has gaps you didn’t know about.
I still do things this way for Tokyo Themes and Batppuccin, but the more themes I maintain the more I wonder if that’s overkill.
Here’s something worth pointing out: any theme that ships multiple variants is already a framework of sorts, whether it calls itself one or not. The moment you factor out the palette from the face definitions so that multiple variants can share the same code, you’ve built the core of a theming engine.
Take Tokyo Themes as an example. There are four variants (night, storm, moon, day), but the face
definitions live in a single shared file (tokyo-themes.el). Each variant is a
thin wrapper – just a deftheme, a palette alist, and a call to the shared
tokyo--apply-theme function:
(require 'tokyo-themes)
(deftheme tokyo-night "A clean dark theme inspired by Tokyo city lights.")
(tokyo--apply-theme 'tokyo-night tokyo-night-colors-alist)
(provide-theme 'tokyo-night)
That’s the entire theme file. The palette is defined elsewhere, and the face
logic is shared – which is exactly how you solve the variant duplication problem
mentioned earlier. In theory, anyone could define a new palette alist and call
tokyo--apply-theme to create a fifth variant. The infrastructure is already
there – it’s just not explicitly marketed as a “framework.”
This is exactly how the theming features of packages like Solarized and Modus evolved. They started as regular themes, grew variants, factored out the shared code, and eventually exposed that machinery to users.
Some theme packages went a step further and turned their internal infrastructure into an explicit theming API.
solarized-emacs started as a
straight port of Ethan Schoonover’s Solarized palette, but over time it grew
the ability to create entirely new themes from custom palettes. You can use
solarized-create-theme-file-with-palette to generate a new theme by supplying
just 10 colors (2 base + 8 accent) – it derives all the intermediate shades
and maps them to the full set of faces:
(solarized-create-theme-file-with-palette 'dark 'my-solarized-dark
'("#002b36" "#fdf6e3" ;; base colors
"#b58900" "#cb4b16" "#dc322f" "#d33682" ;; accents
"#6c71c4" "#268bd2" "#2aa198" "#859900"))
This is how Solarized’s own variants (dark, light, gruvbox, zenburn, etc.) are
built internally. I’ll admit, though, that I always found it a bit weird to ship
themes like Gruvbox and Zenburn under the Solarized umbrella. If you install
solarized-emacs and find a solarized-gruvbox-dark theme in the list, the
natural reaction is “wait, what does Gruvbox have to do with Solarized?” The
answer is “nothing, really – they just share the theming engine.” That makes
perfect sense once you understand the architecture, but I think it’s confusing
for newcomers. It was part of the reason I was never super excited about this
direction for solarized-emacs.
The modus-themes take a different approach. Rather than generating new theme files, they offer deep runtime customization through palette overrides:
(setq modus-themes-common-palette-overrides
'((bg-main "#1a1b26")
(fg-main "#c0caf5")
(keyword magenta-warmer)))
You can override any named color in the palette without touching the theme
source. The result feels like a different theme, but it’s still Modus under the
hood with all its accessibility guarantees. The overrides apply to whichever
Modus variant you load, and modus-themes-toggle switches between variants
while keeping your overrides intact. Protesilaos’s
ef-themes share the same
architecture.
If you want to create something brand new rather than customize an existing theme family, there are a couple of frameworks designed for this.
autothemer provides a macro that
replaces the verbose custom-theme-set-faces boilerplate with a cleaner,
palette-driven approach:
(autothemer-deftheme
my-theme "A theme using autothemer."
;; Display classes: 24-bit GUI, 256-color terminal, 16-color terminal
((((class color) (min-colors 16777216)) ((class color) (min-colors 256)) t)
(my-bg "#1a1b26" "black" "black")
(my-fg "#c0caf5" "white" "white")
(my-red "#f7768e" "red" "red")
(my-green "#9ece6a" "green" "green"))
;; Face specs -- just reference palette names, no display class noise
((default (:foreground my-fg :background my-bg))
(font-lock-keyword-face (:foreground my-red))
(font-lock-string-face (:foreground my-green))))
You define your palette once as named colors with fallback values for different display capabilities (GUI frames and terminals support different color depths, so themes need appropriate fallbacks for each). Then you reference those names in face specs without worrying about display classes again. Autothemer also provides some nice extras like SVG palette previews and helpers for discovering unthemed faces.
base16-emacs is part of the larger Tinted Theming ecosystem. The idea is that you define a scheme as 16 colors in a YAML file, and a builder generates themes for Emacs (and every other editor/terminal) from a shared template. You don’t write Elisp at all – you write YAML and run a build step.
This is great if you want one palette to rule all your tools, but you give up fine-grained control over individual Emacs faces. The generated themes cover a good set of faces, but they might not handle every niche package you use.
| From Scratch | Meta-Theme / Framework | |
|---|---|---|
| Control | Total – every face is yours | Constrained by what the framework exposes |
| Consistency | You enforce it manually | The framework helps (palette-driven) |
| Coverage | You add faces as you discover them | Inherited from the base theme/template |
| Maintenance | You track upstream face changes | Shifted to the meta-theme maintainers |
| Multiple variants | Duplicate or factor out yourself | Built-in support |
| Learning curve | Just deftheme |
Framework-specific API |
I guess relatively few people end up creating theme packages, but here’s a bit of general advice for them.
If you want total control over every face and you’re willing to put in the maintenance work, roll your own. This makes sense for themes with a strong design vision where you want to make deliberate choices about every element. It’s more work, but nothing stands between you and the result you want.
If you mostly like an existing theme but want different colors, customizing a meta-theme (Modus, Solarized, ef-themes) is a good bet. You get battle-tested face coverage for free, and the palette override approach means you can tweak things without forking. Keep in mind, though, that the face coverage problem doesn’t disappear – you’re just shifting it to the meta-theme maintainers. How comprehensive and up-to-date things stay depends entirely on how diligent they are.
If you’re creating something new but don’t want to deal with the boilerplate, use a framework. Autothemer is the best fit if you want to stay in Elisp and have fine control. Base16/Tinted Theming is the pick if you want one palette definition across all your tools.
I’m still a “classic” – I like rolling out my themes from scratch. There’s something satisfying about hand-picking the color for every face. But I won’t pretend it doesn’t get tedious, especially when you maintain several themes across multiple variants. Every time a package adds new faces, that’s more work for me.
If I were starting fresh today, I’d seriously consider Autothemer or building on top of a meta-theme (extracted from my existing theme packages). The time you save on maintenance is time you can spend on what actually matters – making your theme look good.
On the topic of maintenance – one area where AI tools can actually help is
extracting the relevant faces from a list of packages you want to support.
Instead of loading each package, running list-faces-display, and eyeballing
what’s new, you can ask an LLM to scan the source and give you the face
definitions. It’s also handy for periodically syncing your theme against the
latest versions of those packages to catch newly added faces. Not glamorous
work, but exactly the kind of tedium that AI is good at.
That’s all I have for you today. Keep hacking!
Later on you’ll see that’s a pretty easy problem to address. ↩
-1:-- Creating Emacs Color Themes, Revisited (Post Emacs Redux)--L0--C0--2026-03-30T05:25:00.000Z
After kicking off blogmore.el, and
then tinkering with it more and
more, I've found it really quite
helpful while writing posts. One thing I have noticed though -- given I use
BlogMore for this blog and my
photoblog -- is that I wanted to be able to use
the package for working with more than one blog.
So today I found myself with some time to kill and the result is that
blogmore.el v2.0 has now been released. This version allows for setting up
multiple blogs, each with their own settings for where posts live, how their
paths are formatted, and so on.
To handle this I've also added the blogmore-work-on command so that the
active blog can be quickly changed.
All of this can be configured using Emacs' customize feature.

This has all changed since v1.x, where most of the customize options have
now been renamed to include -default- in their name. The idea here is that
what was the value for a setting previously is now the default value if a
given blog hasn't had that setting defined.
For any given blog you wish to work with, you configure a name (for your own reference) and the path to the posts. Optionally you can also set lots of other values too.

If a value is left on Default, then the corresponding default setting will
be used; if it's set, then that value is used for that specific blog.
The defaults out of the box match how I do things with my blogs, of course,
so the configuration is pretty straightforward. As of the time of writing my
use-package
for blogmore.el looks like this:
(use-package blogmore
:vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
:init (add-hook 'blogmore-new-post-hook #'end-it)
:custom
(blogmore-blogs
'(("blog.davep.org" "~/write/davep.github.com/content/posts/")
("seen-by.davep.dev" "~/write/seen-by/content/posts/")))
:bind
("<f12> m b" . blogmore-work-on)
("<f12> m p n" . blogmore-new)
("<f12> m p e" . blogmore-edit)
("<f12> m s c" . blogmore-set-category)
("<f12> m a t" . blogmore-add-tag)
("<f12> m u d" . blogmore-update-date)
("<f12> m u m" . blogmore-update-modified)
("<f12> m l p" . blogmore-link-post)
("<f12> m l c" . blogmore-link-category)
("<f12> m l t" . blogmore-link-tag))
In the above you can see that I've set only the blog title and posts path
for each blog in blogmore-blogs; the remaining values are all implied
nil and so will be defaulted. The full list of values for any given blog
are:
(BLOG-NAME
POSTS-DIRECTORY
POST-TEMPLATE
POST-MAKER-FUNCTION
CATEGORY-MAKER-FUNCTION
TAG-MAKER-FUNCTION
POST-LINK-FORMAT
CATEGORY-LINK-FORMAT
TAG-LINK-FORMAT)
where:
BLOG-NAME is the descriptive name to use for the blog.POSTS-DIRECTORY is the directory where the blog's posts are stored.POST-TEMPLATE is a template for new posts. If nil,
blogmore-default-template is used.POST-MAKER-FUNCTION is a function that takes a filename and returns a
string to be used in the post's URL. If nil,
blogmore-default-post-maker-function is used.CATEGORY-MAKER-FUNCTION is a function that takes a category name and
returns a string to be used in the category's URL. If nil,
blogmore-default-category-maker-function is used.TAG-MAKER-FUNCTION is a function that takes a tag name and returns a
string to be used in the tag's URL. If nil,
blogmore-default-tag-maker-function is used.POST-LINK-FORMAT is a format string for the post's URL, where %s is
replaced with the value returned by the post maker function. If nil,
blogmore-default-post-link-format is used.CATEGORY-LINK-FORMAT is a format string for the category's URL, where
%s is replaced with the value returned by the category maker function. If
nil, blogmore-default-category-link-format is used.TAG-LINK-FORMAT is a format string for the tag's URL, where %s is
replaced with the value returned by the tag maker function. If nil,
blogmore-default-tag-link-format is used.While I very much doubt any of this is useful to anyone else, it's at least flexible for my purposes and can probably be configured to someone else's purpose should they happen to be using BlogMore and Emacs.
-1:-- blogmore.el v2.0 (Post Dave Pearson)--L0--C0--2026-03-29T19:50:41.000Z
There are, I suppose, a few Lisp programmers using Emacs who resist paredit but most of the rest of us have long since succumbed to its charms. It can be a bit difficult to get used to but once you do, it’s a tool you don’t want to live without.
There is a problem though. Since Paredit’s introduction 20 years ago, Emacs has added new default keybindings some of which conflict with the existing Paredit keybindings. I can remember having to remap some of Paredit’s keybindings to avoid this.
Bozhidar Batsov has a nice post that discusses these conflicts and how to deal with them. My use of Paredit is mostly restricted to a small subset of its commands so I haven’t experienced the full impact of these conflicts. Even for those commands I do use—like slurp and barf—the conflict doesn’t bother me because I never use the arrow keys to move the cursor.
If you use more of the Paredit commands, take a look at Batsov’s post for his suggestions for resolving them. Or, you could switch to Fuco1’s smartparens, which avoids these conflicts while providing the same functionality and extending it to other file types.
One thing for sure, if you use paredit-splice-sexp you’ll want to resolve its conflict because Emacs uses Meta+s as the search map prefix and that is too useful to forego.
In any event if you’re a Paredit or Smartparens user, you should definitely take a look at Batsov’s post.
-1:-- Paredit Keybinding Conflicts (Post Irreal)--L0--C0--2026-03-29T14:14:09.000Z
Most theme families these days ship both light and dark variants. For example,
Tokyo Themes has tokyo-day
(light) alongside tokyo-night, tokyo-storm, and tokyo-moon (all dark).
Batppuccin has batppuccin-latte
(light) and batppuccin-mocha, batppuccin-macchiato, batppuccin-frappe
(dark). But switching between them manually gets old fast. Here are a few ways
to automate it.
If you’re using Emacs Plus
on macOS (which many Mac users do), you get the hook
ns-system-appearance-change-functions. This is not part of core Emacs –
it’s a patch
that Emacs Plus applies on top of the NS build. The hook fires
whenever macOS switches between light and dark mode, passing the symbol light
or dark as an argument. All you need is:
(defun my-apply-theme (appearance)
"Load theme based on APPEARANCE (light or dark)."
(mapc #'disable-theme custom-enabled-themes)
(pcase appearance
('light (load-theme 'tokyo-day t))
('dark (load-theme 'tokyo-night t))))
(add-hook 'ns-system-appearance-change-functions #'my-apply-theme)
That’s it. When you flip the system appearance (or the OS does it automatically
based on time of day), Emacs follows along. The mapc line disables any
currently active themes first – without it, load-theme stacks the new theme
on top of the old one, which can cause weird color bleed between the two.
This approach is nice because it keeps Emacs in sync with every other app on your system. If you’re on macOS with Emacs Plus, this is probably the simplest option.
Note: This only works with Emacs Plus’s patched NS build. If you’re using a vanilla Emacs build, or you’re on Linux/Windows, read on.
If you want OS appearance tracking without writing the hook yourself, or you need it on a platform other than macOS, check out auto-dark. It detects OS-level dark/light mode changes on macOS, Linux (via D-Bus/GNOME), Windows, and even Android (Termux):
(use-package auto-dark
:ensure t
:config
(setq auto-dark-themes '((batppuccin-mocha) (batppuccin-latte)))
(auto-dark-mode))
The value is a list of two lists – dark theme(s) first, light theme(s) second.
The extra nesting is there because you can stack multiple themes per mode (e.g.,
a base theme plus an overlay). For a single theme per mode, the format above is
all you need. auto-dark polls the system appearance every few seconds and
switches accordingly. It also provides auto-dark-dark-mode-hook and
auto-dark-light-mode-hook if you want to run extra code on each switch.
If you want theme switching based on time of day regardless of your OS, take a look at circadian.el. It can switch themes at fixed times or based on your local sunrise/sunset:
(use-package circadian
:ensure t
:config
(setq circadian-themes '((:sunrise . batppuccin-latte)
(:sunset . batppuccin-mocha)))
(circadian-setup))
You can also use fixed hours if you prefer:
(setq circadian-themes '(("8:00" . batppuccin-latte)
("20:00" . batppuccin-mocha)))
For sunrise/sunset to work, set calendar-latitude and calendar-longitude in
your config. circadian.el uses Emacs’s built-in solar calculations, so no
external services are needed.1
If you don’t want an extra dependency, you can do something basic with
run-at-time:
(defun my-set-theme-for-time ()
"Switch theme based on current hour."
(let ((hour (string-to-number (format-time-string "%H"))))
(mapc #'disable-theme custom-enabled-themes)
(if (<= 8 hour 19)
(load-theme 'tokyo-day t)
(load-theme 'tokyo-night t))))
;; Run now and repeat every hour
(run-at-time t 3600 #'my-set-theme-for-time)
It’s crude compared to circadian.el, but it works and you can tweak the
schedule however you like.
ns-system-appearance-change-functions hook is all you need.auto-dark has you covered.circadian.el is the polished option;
the DIY run-at-time approach works if you want to keep things minimal.What about me? Well, I’m on macOS these days and I do enable the auto-switch between light/dark mode there. So, normally I’d pick the first option, but there’s a small catch - I really dislike light themes for programming and I’m using only dark variants day and night, so I don’t really need theme auto-switching in Emacs.
Note: One thing to keep in mind: if you’re using any of these, remove any static
load-theme call from your init file – let the auto-switching mechanism handle
theme loading, otherwise the two will fight on startup.
As usual, there’s no shortage of ways to solve this in Emacs. Are you doing auto-switching of themes yourself? Which is your favorite approach?
That’s all I have for you today. Keep hacking!
Am I the only one impressed by the fact that the calendar package can calculate things like sunrise and sunset? ↩
-1:-- Automatic Light/Dark Theme Switching (Post Emacs Redux)--L0--C0--2026-03-29T08:30:00.000Z
I have been busy improving my annotation package! Simply Annotate, the latest release is 0.9.8 and I have put in a bunch of new features, so it felt like a good time to step back and show what the package actually does at this point, because honestly, quite a lot has changed since I last wrote about it.
There are annotation packages out there already, annotate.el being the most established. And they are good! But I kept running into the same friction: I wanted threaded conversations directly on my code, I wanted multiple display styles I could combine, and I wanted the whole thing to be a single file with no dependencies that I could drop onto an air-gapped machine and just use (yup, that again!)
So I built my own!, this is Emacs, after all.
At its core, simply-annotate lets you attach persistent notes to any text file (or Info manual, or dired buffer) without modifying the original content. Annotations are stored in a simple s-expression database at ~/.emacs.d/simply-annotations.el. The entire package is a single elisp file, requires Emacs 28.1+, and has zero external dependencies.
Basic setup is two lines:
(use-package simply-annotate
:bind-keymap ("C-c a" . simply-annotate-command-map)
:hook (find-file-hook . simply-annotate-mode))
Or if you prefer require:
(require 'simply-annotate)
(global-set-key (kbd "C-c a") simply-annotate-command-map)
(add-hook 'find-file-hook #'simply-annotate-mode)
Open a file, select some text, press C-c a j, and you have your first annotation. M-n and M-p step through them.
I am always fiddling around with styles, themes, backgrounds e.t.c, so I thought I would build this tinkering enthusiasm into this package. Simply-annotate has five display styles, and you can layer them together:
You can combine them, so (tint fringe-bracket) gives you a gentle background wash with a clear fringe bracket showing exactly where the annotation spans. Cycle through styles with C-c a '.
Toggle inline display with C-c a / and annotation content appears as box-drawn blocks directly in your buffer:
some annotated code here
▴
┌─ ✎ [OPEN/NORMAL] ──────────────
│ This function needs refactoring.
│ The nested conditionals are hard
│ to follow.
└─────────────────────────────────
New in 0.9.8 is the inline pointer, that little ▴ connecting the box to the annotated text. It is indented to the exact column where the annotation starts, so you always know what the comment refers to.
The fun bit is that the pointer is just a string, and it supports multiline. So you can customise it to whatever shape you like:
;; Simple arrow (default)
(setq simply-annotate-inline-pointer-after "▴")
(setq simply-annotate-inline-pointer-above "▾")
;; Heavy L-bracket (my current favourite)
(setq simply-annotate-inline-pointer-after "┃\n┗━▶")
(setq simply-annotate-inline-pointer-above "┏━▶\n┃")
;; Speech bubble tail
(setq simply-annotate-inline-pointer-after " ╰┐")
(setq simply-annotate-inline-pointer-above " ╰┐")
;; Decorative diamond
(setq simply-annotate-inline-pointer-after "◆")
(setq simply-annotate-inline-pointer-above "◆")
There is a full list of copy-paste options in the Commentary section of the elisp file. Set to nil to disable the pointer entirely.
So I think this is where simply-annotate really differs from other annotation packages. Every annotation is a thread. You can reply to it with C-c a r, and replies can be nested under any comment in the thread, not just the root. It prompts with a hierarchical completing-read menu showing the comment tree.
Each thread has:
C-c a s)C-c a p)C-c a t)The comment tree renders with box-drawing characters so the hierarchy is always clear:
┌— ® [OPEN/NORMAL] —
| james dyer (03/29 08:27)
| This is the original comment
| L james dyer (03/29 08:27)
| | here is a reply to this comment
| | L james dyer (@3/29 08:27)
| | and a reply within a reply!!
| L james dyer (03/29 08:28)
| Here is another reply to the original comment
└────────────────────
For team collaboration:
(setq simply-annotate-author-list '("Alice" "Bob" "Charlie"))
(setq simply-annotate-prompt-for-author 'threads-only)
(setq simply-annotate-remember-author-per-file t)
Annotations exist at three levels: file (whole-file overview), defun (function or block description), and line (individual elements). There is also an all pseudo-level that shows everything at once, which is the default.
Cycle levels with C-c a ] and C-c a [. The header-line shows counts per level (FILE:2 | DEFUN:5 | LINE:3) with the active level in bold, so you always know where you are, my idea here is to lean towards a coding annotation tool to help teach code or help to remember what has been implemented, so the levels start at a broad file overview and enables you to switch instantly to a more granular level.
The org-mode listing (C-c a l) gives you a foldable, navigable overview of all annotations in the current file, grouped by level. Press n and p to step through headings, RET to jump to source.
New in 0.9.6, the tabular listing (C-c a T) opens a fast, sortable table using tabulated-list-mode (a feature in Emacs I am starting to leverage more). Columns for Level, Line, Status, Priority, Comments, Tags, Author, and the first line of the comment. Click column headers to sort. This is brilliant for getting a quick birds-eye view of all the open items in a file.
For the global view, simply-annotate-show-all gathers annotations from every file in the database into a single org-mode buffer.
Enable simply-annotate-dired-mode and dired buffers show fringe indicators next to files that have annotations. You can see at a glance which files have notes attached:
(add-hook 'dired-mode-hook #'simply-annotate-dired-mode)
Info manuals are also fully supported. Annotations are tracked per-node, and the listing and jump-to-file commands navigate to Info nodes seamlessly.
Press C-c a e and you can edit the raw s-expression data structure of any annotation. Every field is there: thread ID, status, priority, tags, comments with their IDs, parent-IDs, timestamps, and text. C-c C-c to save. This is the escape hatch for when the UI does not quite cover what you need.
Rather than writing paragraphs about how simply-annotate compares to other packages, I have put together a feature matrix in the README. The short version: if you want threaded conversations, multiple combinable display styles, annotation levels, a smart context-aware command, and zero dependencies in a single file, this is the package for you. If you need PDF annotation, go with org-noter or org-remark, they are excellent at that.
(use-package simply-annotate
:bind-keymap ("C-c a" . simply-annotate-command-map)
:hook (find-file-hook . simply-annotate-mode))
(with-eval-after-load 'simply-annotate
(add-hook 'dired-mode-hook #'simply-annotate-dired-mode))
The package is available on GitHub on melpa at simply-annotate or https://github.com/captainflasmr/simply-annotate. There is also an Info manual if you run M-x info and search for simply-annotate.
-1:-- Simply Annotate 0.9.8: Threaded Conversations on Your Code (Post James Dyer)--L0--C0--2026-03-29T08:08:00.000Z
I promised I’d take a break from building Tree-sitter major modes, and I meant it. So what better way to relax than to build… color themes? Yeah, I know. My idea of chilling is weird, but I genuinely enjoy working on random Emacs packages. Most of the time at least…
For a very long time my go-to Emacs themes were Zenburn and Solarized – both of which I maintain popular Emacs ports for. Zenburn was actually one of my very first open source projects (created way back in 2010, when Emacs 24 was brand new). It served me well for years.
But at some point I got bored. You know the feeling – you’ve been staring at the same color palette for so long that you stop seeing it. My experiments with other editors (Helix, Zed, VS Code) introduced me to Tokyo Night and Catppuccin, and they’ve been my daily drivers since then.
Eventually, I ended up creating my own Emacs ports of both. I’ve already published emacs-tokyo-themes, and I’ll write more about that one down the road. Today is all about Catppuccin. (and by this I totally mean Batppuccin!)
There’s already an official Catppuccin theme for Emacs, and it works. So why build another one? A few reasons.
The official port registers a single catppuccin theme and switches between
flavors (Mocha, Macchiato, Frappe, Latte) via a global variable and a reload
function. This is unusual by Emacs standards and breaks the normal load-theme
workflow – theme-switching packages like circadian.el need custom glue code to
work with it. It also loads color definitions from an external file in a way that
fails when Emacs hasn’t marked the theme as safe yet, which means some users
can’t load the theme at all.
Beyond the architecture, there are style guide issues.
font-lock-variable-name-face is set to the default text color, making variables
invisible. All outline-* levels use the same blue, so org-mode headings are
flat. org-block forces green on all unstyled code. Several faces still ship
with #ff00ff magenta placeholder colors. And there’s no support for popular
packages like vertico, marginalia, transient, flycheck, or cider.
I think some of this comes from the official port trying to match the structure of the Neovim version, which makes sense for their cross-editor tooling but doesn’t sit well with how Emacs does things.1
Batppuccin is my opinionated take
on Catppuccin for Emacs. The name is a play on my last name (Batsov) + Catppuccin.2
I guess you can think of this as @bbatsov’s Catppuccin… or perhaps Batman’s
Catppuccin?
The key differences from the official port:
Four proper themes. batppuccin-mocha, batppuccin-macchiato,
batppuccin-frappe, and batppuccin-latte are all separate themes that work
with load-theme out of the box. No special reload dance needed.
Faithful to the style guide. Mauve for keywords, green for strings, blue for functions, peach for constants, sky for operators, yellow for types, overlay2 for comments, rosewater for the cursor. The rainbow heading cycle (red, peach, yellow, green, sapphire, lavender) makes org-mode and outline headings actually distinguishable.
Broad face coverage. Built-in Emacs faces plus magit, vertico, corfu, marginalia, embark, orderless, consult, transient, flycheck, cider, company, doom-modeline, treemacs, web-mode, and more. No placeholder colors.
Clean architecture. Shared infrastructure in batppuccin-themes.el, thin
wrapper files for each flavor, color override mechanism, configurable heading
scaling. The same pattern I use in
zenburn-emacs and
emacs-tokyo-night-theme.
I didn’t really re-invent anything here - I just created a theme in a way I’m comfortable with.
I’m not going to bother with screenshots here – it looks like Catppuccin, because it is Catppuccin. There are small visual differences if you know where to look (headings, variables, a few face tweaks), but most people wouldn’t notice them side by side. If you’ve seen Catppuccin, you know what to expect.
The easiest way to install it right now:
1
2
3
4
(use-package batppuccin-mocha-theme
:vc (:url "https://github.com/bbatsov/batppuccin-emacs" :rev :newest)
:config
(load-theme 'batppuccin-mocha t))
Replace mocha with macchiato, frappe, or latte for the other flavors. You
can also switch interactively with M-x batppuccin-select.
I remember when Solarized was the hot new thing and there were something like five competing Emacs ports of it. People had strong opinions about which one got the colors right, which one had better org-mode support, which one worked with their favorite completion framework. And that was fine! Different ports serve different needs and different tastes.
The same applies here. The official Catppuccin port is perfectly usable for a lot of people. Batppuccin is for people who want something more idiomatic to Emacs, with broader face coverage and stricter adherence to the upstream style guide. Both can coexist happily.
I’ve said many times that for me the best aspect of Emacs is that you can tweak it infinitely to make it your own, so as far as I’m concerned having a theme that you’re the only user of is perfectly fine. That being said, I hope a few of you will appreciate my take on Catppuccin as well.
This is an early release and there’s plenty of room for improvement. I’m sure there are faces I’ve missed, colors that could be tweaked, and packages that deserve better support. If you try it out and something looks off, please open an issue or send a PR.
I’m also curious – what are your favorite Emacs themes these days? Still rocking Zenburn? Converted to modus-themes? Something else entirely? I’d love to hear about it.
That’s all from me, folks! Keep hacking!
The official port uses Catppuccin’s Whiskers template tool to generate the Elisp from a .tera template, which is cool for keeping ports in sync across editors but means the generated code doesn’t follow Emacs conventions. ↩︎
Naming is hard, but it should also be fun! Also – I’m a huge fan of Batman. ↩︎
-1:-- Batppuccin: My Take on Catppuccin for Emacs (Post Bozhidar Batsov)--L0--C0--2026-03-29T07:00:00.000Z
One of Lisp’s features that seem like magic to those of us brought up with C-like languages is the ability to change the code of a running process, reload it, and continue running with the new code. The amazing thing is that the process is not restarted. It simply continues running but with the new code.
Emacers do this all the time, often without realizing what they’re doing. They make a change, to their init.el, say, evaluate it, and continue executing their current Emacs instance. Sometimes, this is simply changing a parameter value but you can also change a function definition in the same way.
If you’re new to Emacs you may wonder how this magical spell is invoked even though you’ve done it several times. It’s simply a matter of evaluating the new code and continuing. Except when it isn’t. There are some edge cases that can trip you up. In Lisp it’s devar values. Emacs adds defface and defcustom. The values defined by these commands are not changed by a code update. This is on purpose. The idea is that you don’t want to mess with a user’s, say, custom values when you change the code.
Bozhidar Batsov has a nice post that discusses all this with particular attention on how to deal with devar and the other edge cases. For example, I always thought the the way to change devar variables was to evaluate a setq of the variable with the new value but you can also simple invoke eval-defun (Ctrl+Meta+x) to the devar to update the value. This also works for defcustom and defface.
The other nice thing I didn’t know about is the restart-emacs command that restarts Emacs and—with desktop-save-mode—reloads everything, including the new defvar etc. values. Take a look at Batsov’s post for more details.
-1:-- Restarting Running Elisp Code (Post Irreal)--L0--C0--2026-03-28T15:08:43.000Z
Emacs Asia-Pacific (APAC) virtual meetup will not be happening this month. See you in the next meetup! Stay updated: https://emacs-apac.gitlab.io/#stay-updated.
-1:-- CANCELED Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, March 28, 2026 (Post Emacs APAC)--L0--C0--2026-03-28T05:30:00.000Z
I’m pretty much done with the focused development push on neocaml – it’s reached a point where I’m genuinely happy using it daily and the remaining work is mostly incremental polish. So naturally, instead of taking a break I decided it was time to start another project that’s been living in the back of my head for a while: a proper Tree-sitter-based F# mode for Emacs.
Meet fsharp-ts-mode.
I’ve written before about my fondness for the ML family of languages, and while OCaml gets most of my attention, last year I developed a soft spot for F#. In some ways I like it even a bit more than OCaml – the tooling is excellent, the .NET ecosystem is massive, and computation expressions are one of the most elegant abstractions I’ve seen in any language. F# manages to feel both practical and beautiful, which is a rare combination.
The problem is that Emacs has never been particularly popular with F# programmers – or .NET programmers in general. The existing fsharp-mode works, but it’s showing its age: regex-based highlighting, SMIE indentation with quirks, and some legacy code dating back to the caml-mode days. I needed a good F# mode for Emacs, and that’s enough of a reason to build one in my book.
I’ll be honest – I spent quite a bit of time trying to come up with a clever name.1 Some candidates that didn’t make the cut:
In the end none of my fun ideas stuck, so I went with the boring-but-obvious
fsharp-ts-mode. Sometimes the straightforward choice is the right one. At
least nobody will have trouble finding it.2
I modeled fsharp-ts-mode directly after neocaml, and the two packages share
a lot of structural similarities – which shouldn’t be surprising given how much
OCaml and F# have in common. The same architecture (base mode + language-specific
derived modes), the same approach to font-locking (shared + grammar-specific
rules), the same REPL integration pattern (comint with tree-sitter input
highlighting), the same build system interaction pattern (minor mode wrapping CLI
commands).
This also meant I could get the basics in place really quickly. Having already
solved problems like trailing comment indentation, forward-sexp hybrid
navigation, and imenu with qualified names in neocaml, porting those solutions
to F# was mostly mechanical.
The initial release covers all the essentials:
.fs, .fsx, and .fsi filesMyModule.myFunc)beginning-of-defun, end-of-defun, forward-sexpC-c C-d)dotnet build outputIf you’re currently using fsharp-mode, switching is straightforward:
1
2
(use-package fsharp-ts-mode
:vc (:url "https://github.com/bbatsov/fsharp-ts-mode" :rev :newest))
The main thing fsharp-ts-mode doesn’t have yet is automatic LSP server
installation (the eglot-fsharp package does this for fsharp-mode). You’ll
need to install FsAutoComplete yourself:
$ dotnet tool install -g fsautocomplete
After that, (add-hook 'fsharp-ts-mode-hook #'eglot-ensure) is all you need.
See the migration guide in the README for a detailed comparison.
Working with the ionide/tree-sitter-fsharp grammar surfaced some interesting challenges compared to the OCaml grammar:
Unlike OCaml, where indentation is purely cosmetic, F# uses significant whitespace (the “offside rule”). The tree-sitter grammar needs correct indentation to parse correctly, which creates a chicken-and-egg problem: you need a correct parse tree to indent, but you need correct indentation to parse. For example, if you paste this unindented block:
1
2
3
4
5
let f x =
if x > 0 then
x + 1
else
0
The parser can’t tell that if is the body of f or that x + 1 belongs
to the then branch – it produces ERROR nodes everywhere, and
indent-region has nothing useful to work with. But if you’re typing the code
line by line, the parser always has enough context from preceding lines to
indent the current line correctly. This is a fundamental limitation of any
indentation-sensitive grammar.
OCaml’s tree-sitter-ocaml-interface grammar inherits from the base grammar, so
you can share queries freely. F#’s fsharp and fsharp_signature grammars are
independent with different node types and field names for equivalent concepts.
For instance, a let binding is function_or_value_defn in the .fs grammar
but value_definition in the .fsi grammar. Type names use a type_name:
field in one grammar but not the other. Even some keyword tokens (of, open,
type) that work fine as query matches in fsharp fail at runtime in
fsharp_signature.
This forced me to split font-lock rules into shared and grammar-specific sets – more code, more testing, more edge cases.
F# script (.fsx) files without a module declaration can mix let bindings
with bare expressions like printfn. The grammar doesn’t expect a declaration
after a bare expression at the top level, so it chains everything into nested
application_expression nodes:
1
2
3
let x = 1
printfn "%d" x // bare expression
let y = 2 // grammar nests this under the printfn node
Each subsequent let ends up one level deeper, causing progressive
indentation. I worked around this with a heuristic that detects declarations
whose ancestor chain leads back to file through these misparented nodes and
forces them to column 0. Shebangs (#!/usr/bin/env dotnet fsi) required a
different trick – excluding the first line from the parser’s range entirely
via treesit-parser-set-included-ranges.
I’ve filed issues upstream for the grammar pain points – hopefully they’ll improve over time.
Let me be upfront: this is a 0.1.0 release and it’s probably quite buggy. I’ve
tested it against a reasonable set of F# code, but there are certainly
indentation edge cases, font-lock gaps, and interactions I haven’t encountered
yet. If you try it and something looks wrong, please open an
issue – M-x
fsharp-ts-mode-bug-report-info will collect the environment details for you.
The package can currently be installed only from GitHub (via package-vc-install
or manually). I’ve filed a PR with
MELPA and I hope it will get merged
soon.
I really need to take a break from building Tree-sitter major modes at this
point. Between clojure-ts-mode, neocaml, asciidoc-mode, and now
fsharp-ts-mode, I’ve spent a lot of time staring at tree-sitter node types and
indent rules.3 It’s been fun, but I think I’ve earned a vacation from
treesit-font-lock-rules.
I really wanted to do something nice for the (admittedly small) F#-on-Emacs community, and a modern major mode seemed like the most meaningful contribution I could make. I hope some of you find it useful!
That’s all from me, folks! Keep hacking!
Way more time than I needed to actually implement the mode. ↩︎
Many people pointed out they thought neocaml was some package for neovim. Go figure why! ↩︎
I’ve also been helping a bit with erlang-ts-mode recently. ↩︎
-1:-- fsharp-ts-mode: A Modern Emacs Mode for F# (Post Bozhidar Batsov)--L0--C0--2026-03-27T15:00:00.000Z
Today’s topic came up while I was going over the list of open Prelude issues after doing the recent 2.0 release.
Paredit and
smartparens are structural
editing packages that keep your parentheses balanced and let you
manipulate s-expressions as units – essential tools for anyone writing
Lisp. Paredit has been around since 2005 and its keybindings have
become muscle memory for a generation of Lisp programmers (yours truly
included). Smartparens inherits the same keymap when used with
sp-use-paredit-bindings.
The problem is that some of those keybindings conflict with standard Emacs key prefixes that didn’t exist when paredit was written – or that have grown more important over time.
Before getting to solutions, let’s look at each problematic command – what it does, where paredit puts it, and what it shadows.
M-sparedit-splice-sexp (or sp-splice-sexp in smartparens) removes
the enclosing delimiters around point, “splicing” the contents into
the parent expression:
;; before (point on "b"):
(a (b c) d)
;; after splice:
(a b c d)
The conflict: Emacs uses M-s as the search-map prefix (since
Emacs 23). Paredit’s splice binding shadows M-s o (occur), M-s .
(isearch-forward-symbol-at-point), and any M-s-prefixed bindings
from packages like consult (consult-line, consult-ripgrep, etc.).
If you use a completion framework like Vertico + Consult, this one
really hurts.
M-?paredit-convolute-sexp (or sp-convolute-sexp) swaps the nesting
of two enclosing forms. Specifically, it takes the head of the outer
form and moves it inside the inner one:
;; before (point on "c"):
(a (b c d))
;; after convolute -- "a" moved from outer to inner:
(b (a c d))
The conflict: Emacs uses M-? for xref-find-references (since
Emacs 25). If you use LSP (Eglot or lsp-mode), paredit’s convolute
binding shadows “find all references” – one of the most useful LSP
features.
C-<right>paredit-forward-slurp-sexp (or sp-forward-slurp-sexp) expands
the current sexp forward by pulling the next sibling inside the
closing delimiter:
;; before:
(a b) c
;; after slurp -- "c" pulled inside:
(a b c)
C-<left>paredit-forward-barf-sexp (or sp-forward-barf-sexp) is the
opposite – it pushes the last element out past the closing delimiter:
;; before:
(a b c)
;; after barf -- "c" pushed out:
(a b) c
The conflict for both: C-<right> and C-<left> override
right-word and left-word. Fine if you’re in a Lisp buffer and
know what you’re doing, but surprising if you expected word-level
movement.
M-<up>paredit-splice-sexp-killing-backward splices (removes delimiters)
and also kills everything before point within the sexp:
;; before (point on "c"):
(a b c d)
;; after splice-killing-backward -- "a b" killed, parens removed:
c d
M-<down>paredit-splice-sexp-killing-forward does the same but kills
everything after point:
;; before (point on "b"):
(a b c d)
;; after splice-killing-forward -- "c d" killed, parens removed:
a b
The conflict for both: M-<up> and M-<down> clash with
org-metaup/org-metadown in Org mode, paragraph movement in some
configs, and window manager shortcuts on some Linux desktops.
The good news is that both Matus Goljer (a.k.a. Fuco1, the smartparens
author) and Magnar Sveen (a.k.a. Magnars, the author of
expand-region, multiple-cursors and many other popular packages)
have solved these conflicts in their own configs. Their approaches are
worth borrowing.
The examples below use smartparens. For paredit, replace
smartparens-mode-map with paredit-mode-map and sp-* commands
with their paredit-* equivalents.
M-s)Matus’s approach is to rebind to M-D (meta-shift-d). The mnemonic
is nice – M-d kills a word, M-D “kills the delimiters.” This is
probably the most widely copied alternative:
(define-key smartparens-mode-map (kbd "M-s") nil)
(define-key smartparens-mode-map (kbd "M-D") #'sp-splice-sexp)
Magnar’s approach is to rebind to s-s (super-s). Clean if you’re on
macOS where Super is the Command key:
(define-key smartparens-mode-map (kbd "M-s") nil)
(define-key smartparens-mode-map (kbd "s-s") #'sp-splice-sexp)
You can use both – M-D everywhere, s-s as a macOS bonus.
M-?)Convolute-sexp is one of paredit’s more obscure commands. If you use
LSP or xref regularly, freeing M-? for xref-find-references is
a net win:
(define-key smartparens-mode-map (kbd "M-?") nil)
If you actually use convolute-sexp, rebind it to something under a less contested prefix.
C-<arrow>)Magnar moves these to Super:
(define-key smartparens-mode-map (kbd "C-<right>") nil)
(define-key smartparens-mode-map (kbd "C-<left>") nil)
(define-key smartparens-mode-map (kbd "s-<right>") #'sp-forward-slurp-sexp)
(define-key smartparens-mode-map (kbd "s-<left>") #'sp-forward-barf-sexp)
Matus keeps the C-<arrow> bindings (accepting the conflict). This
one’s really a matter of taste – if word-level movement with
C-<arrow> matters to you, move them. If you’re a Lisp programmer
who slurps more than they word-move, keep them.
M-<up> / M-<down>)Matus uses C-M-<backspace> and C-M-<delete>. Magnar uses
s-<up> and s-<down>. Both work well.
If you’re using smartparens (rather than paredit), there’s actually a
simpler option – just use smartparens’ own default keybinding set
instead of the paredit compatibility bindings. Set
sp-base-key-bindings to 'sp (or just don’t set it at all) and
call sp-use-smartparens-bindings instead of
sp-use-paredit-bindings.
The default smartparens bindings already avoid most of the conflicts above:
| Command | Paredit binding | Smartparens binding |
|---|---|---|
| splice | M-s |
M-D |
| convolute | M-? |
(unbound) |
| slurp | C-<right> |
C-<right> |
| barf | C-<left> |
C-<left> |
| splice-killing-backward | M-<up> |
C-M-<backspace> |
| splice-killing-forward | M-<down> |
C-M-<delete> |
The two big wins are splice moving to M-D (freeing search-map)
and convolute not being bound at all (freeing xref-find-references).
The slurp/barf conflict with word movement remains, but that’s a
trade-off most Lisp programmers are happy to make.
I don’t use most of the commands shadowed by Paredit, so I didn’t
even think about the conflicts much before today. Given that I’m a
macOS user these days I like Magnar’s approach to solving the
conflicts. But I’m also sooo used to pressing M-s… Decisions,
decisions…
I definitely think everyone should free up M-?, given the default is quite
important command. For me this was never much of a problem in the past (until
the LSP era) as I’ve always used Projectile’s wrappers around xref commands –
projectile-find-references (s-p ? or C-c p ?) instead of
xref-find-references, and projectile-find-tag (s-p j or C-c p j) instead
of xref-find-definitions. Projectile scopes these to the current project
automatically, which is what I usually want anyway.
I don’t really care about any commands with arrows in them, as I’m using an HHKB keyboard and it’s not really fun to press arrows on it…
Paredit’s defaults made perfect sense in 2005. Twenty years later,
Emacs has grown search-map, xref, and a whole ecosystem of packages
that expect those keys to be available. If you’ve been living with
these conflicts out of habit, take five minutes to rebind – your
future self will thank you.
That’s all I have for you today. Keep hacking!
-1:-- Paredit’s Keybinding Conflicts (Post Emacs Redux)--L0--C0--2026-03-27T08:00:00.000Z
Please note that planet.emacslife.com aggregates blogs, and blog authors might mention or link to nonfree things. To add a feed to this page, please e-mail the RSS or ATOM feed URL to sacha@sachachua.com . Thank you!