Raymond Zeitler: Emacs Carnival July 2026 -- Programming

I don't think of Emacs as an IDE or even a programming environment unless I'm writing Lisp to automate Emacs. Instead, I consider Emacs to be a text editor with some useful extras.

Syntax highlighting (font-lock-mode) is a nice extra. It might seem strange that I consider syntax highlighting to be an extra. If you've used a text editor 40 years ago, you'll understand why -- text editors didn't have the feature. Syntax highlighting was the purview of the compiler tool. My first experience with syntax highlighting was with Borland Turbo Pascal; I used Borland Turbo C soon after. (I think Borland's Turbo Assembler, TASM, had the feature, too.)

According to packages-selected-packages, these are the programming modes I have configured:

  • basic-mode
  • csv-mode
  • gnuplot
  • matlab-mode
  • powershell
  • python and python-mode
  • Swift mode

I don't program in Swift. Perhaps the package is a dependency for another mode. Or maybe I was ambitious one day and thought I'd try another programming language.

Is csv a programming language? If you automate the generation of spreadsheets. it's nice to have some help. But to be honest, I've not used it very often.

Easy access to shell is another nice extra. I have two functions to make use of it when I write batch files. They execute either the current line (exeln) or the entire buffer (shellfn) with some sort of shell command; they are shown below. The Windows binary uses cmdproxy.exe from MinGW -- it works out-of-the-box. But I use the Windows start program for shellfn because it invokes the correct executable based on the file's extension. Thus, Python runs a .py file; cmd, a .bat; Octave, a .m file.

;; This is bound to C-c x
;; Many thanks to Sebastián Monía for suggesting many improvements
(defun exeln (arg)
  "Execute  current line as a shell command.
With prefix ARG, run asynchronously."
  (interactive "P")
  (funcall (if arg
               #'async-shell-command
             #'shell-command)
           (thing-at-point 'line t)))

;; This is bound to C-c s
(defun shellfn ()
  "Invokes the shell using the current buffer file name as a parameter."
  (interactive)
  (shell-command (concat "start " (buffer-file-name))))

I've tried to use source blocks in Org, but I prefer putting each code snippet into its own dedicated file. Why? One reason is because I don't want the code to execute with org-mode active. Another reason is that indentation doesn't work properly. Switching modes fixes both issues, but it gets annoying very quickly. This is a topic for another post.

I look forward to seeing how other folks use Emacs for programming.

-1:-- Emacs Carnival July 2026 -- Programming (Post Raymond Zeitler)--L0--C0--2026-07-06T02:22:09.964Z

Irreal: Using Emacs Input Methods For Foreign Languages

Protesilaos Stavrou (Prot) has a very nice video on writing foreign languages with Emacs. The main problem for Latin languages are the diacritical marks. Other languages, such as Greek, Russian, and Chinese, have completely different alphabets. In either case, Emacs has us covered.

Prot writes Greek, English, French, and (possibly) Spanish so being able to handle all these languages easily is important to him. His video shows us how he does it. The video is in two parts:

  1. Using the Emacs input method mechanism and
  2. Multilingual spellchecking.

In the first part, Prot demonstrates how to switch between the normal input method—that is, the characters your keyboard normally sends—and another input method that maps to characters from another language.

He uses French as an example and shows one way (the postfix method) to add the diacritical marks that French uses. I use the same method for Spanish and like it. It’s easy and natural. The only thing that takes a bit of getting used to is using an accent character in a non-diacritical way. For example, when writing with the spanish-postfix method, e' results in é but if you want to write something like “he’s” you have to input he''s .

The second part of the video considers multilingual spellchecking. For that, Prot uses jinx, which is pretty much like the normal spell checking in Emacs except you can check multiple languages. It will underline spelling errors as usual and you can have it offer suggestions for correcting those errors.

I wish I’d seen Prot’s video before I started studying Spanish. It would have saved me a lot of time figuring things out on my own. If you have a need to use Emacs to write in another language, you should definitely take a look at Prot’s video.

-1:-- Using Emacs Input Methods For Foreign Languages (Post Irreal)--L0--C0--2026-07-05T14:25:03.000Z

Sacha Chua: Une navigation simplifiée sur mon blog grâce à EWW (et Emacs !)

Quick English translation: You can now navigate my blog with n and p (eww-next-url, eww-previous-url) in the EWW browser in Emacs.

(C'est aussi une traduction en tagalog après la version française pour Amin, qui est en train de l'apprend󠆻re !)

Pour le carnaval d'Emacs en mai, Omar Antolin a écrit un article qui recommande vivement le navigateur EWW sous Emacs. Grâce au commentaire de technomancy concernant celui-ci, j'ai appris qu'il peut naviguer vers la page suivante et la page précédente avec les raccourcis clavier « n » (eww-next-url) et « p » (eww-previous-url) si la page inclut les liens dans son en-tête comme ça :

<link rel="next" href="https://sachachua.com/blog/2026/06/2026-06-29-emacs-news/">
<link rel="prev" href="https://sachachua.com/blog/2026/07/semaine-du-22-au-28-juin/">

J'ai donc ajouté cette fonctionnalité à mon site en modifiant ma configuration du générateur de site statique 11ty. D'abord, j'ai ajouté les données à tous les articles dans mon eleventy.config.js.

  eleventyConfig.addCollection('_posts', function(collectionApi) {
    const posts = collectionApi.getFilteredByTag('_posts');
    for (let i = 0; i < posts.length; i++) {
      const next = i > 0 ? posts[i - 1] : null;
      const prev = i < posts.length - 1 ? posts[i + 1] : null;
      posts[i].data.navLinks = {
        prev: { url: prev?.url, title: prev?.data?.title },
        next: { url: next?.url, title: next?.data?.title }
      };
    }
    return posts;
  });

Ensuite j'ai modifié l'etiquette d'en-tête.

const navLinks = data?.page?.url && data?.navLinks ? data?.navLinks : {
  next: { url: data.pagination && data.pagination.pageNumber < data.pagination.hrefs.length - 1 && data.pagination.hrefs[data.pagination.pageNumber + 1] },
  prev: { url: data.pagination && data.pagination.pageNumber > 0 && data.pagination.hrefs[data.pagination.pageNumber - 1] }
};
const nextLink = navLinks?.next?.url ? `<link rel="next" href="${navLinks?.next?.url}" />` : '';
const prevLink = navLinks?.prev?.url ? `<link rel="prev" href="${navLinks?.prev?.url}" />` : '';

J'avais déjà les liens vers l'article suivant et l'article précédent, il m'a juste suffi de les ajouter à l'en-tête. Si vous lisez un article spécifique sur mon blog, vous pouvez y naviguer de cette façon. Voilà !

output-2026-07-05-07:40:52.gif
Figure 1: Navigating with n (eww-next-url) and p (eww-previous-url) in eww
in Tagalog

Para sa Carnival ng Emacs noong Mayo, sumulat si Omar Antolin ng isang artikulo tungkol sa EWW browser na kasama sa Emacs. Dahil sa komento ni technomancy, nalaman ko na pwede palang mag-navigate sa susunod at nakaraang pahina gamit ang mga keyboard shortcut na "n" (eww-next-url) at "p" (eww-previous-url) kung kasama sa header ng pahina ang mga link katulad nito:

<link rel="next" href="https://sachachua.com/blog/2026/06/2026-06-29-emacs-news/">
<link rel="prev" href="https://sachachua.com/blog/2026/07/semaine-du-22-au-28-juin/">

Gusto kong idagdag 'yung feature na 'to sa site ko. Gumagamit ako ng static site generator (11ty) para gawin yung site ko, kaya dinagdag ko 'to sa kanyang configuration:

  eleventyConfig.addCollection('_posts', function(collectionApi) {
    const posts = collectionApi.getFilteredByTag('_posts');
    for (let i = 0; i < posts.length; i++) {
      const next = i > 0 ? posts[i - 1] : null;
      const prev = i < posts.length - 1 ? posts[i + 1] : null;
      posts[i].data.navLinks = {
        prev: { url: prev?.url, title: prev?.data?.title },
        next: { url: next?.url, title: next?.data?.title }
      };
    }
    return posts;
  });

Pagkatapos noon, pinalitan ko yung header tag ko.

const navLinks = data?.page?.url && data?.navLinks ? data?.navLinks : {
  next: { url: data.pagination && data.pagination.pageNumber < data.pagination.hrefs.length - 1 && data.pagination.hrefs[data.pagination.pageNumber + 1] },
  prev: { url: data.pagination && data.pagination.pageNumber > 0 && data.pagination.hrefs[data.pagination.pageNumber - 1] }
};
const nextLink = navLinks?.next?.url ? `<link rel="next" href="${navLinks?.next?.url}" />` : '';
const prevLink = navLinks?.prev?.url ? `<link rel="prev" href="${navLinks?.prev?.url}" />` : '';

Mayroon na 'kong mga link sa susunod at nakaraang artikulo. Kailangan ko lang silang isama sa header. Kung nagbabasa ka ng isang artikulo sa blog ko, kaya mo nang mag-navigate sa ganoong paraan. Ayan!

output-2026-07-05-07:40:52.gif
Figure 2: Navigating with n (eww-next-url) and p (eww-previous-url) in eww
View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- Une navigation simplifiée sur mon blog grâce à EWW (et Emacs !) (Post Sacha Chua)--L0--C0--2026-07-05T12:56:17.000Z

Meta Redux: Clojurists Together Update: May and June 2026

Some of you might know that Clojurists Together are supporting my work on nREPL, CIDER and friends this year. Normally I send them a bi-monthly progress report, but I saw some other people who got funding for their OSS work publish those reports as blog posts for the broader public and I thought to try this for a change.

The past two months were super productive. I had a lot of inspiration during this period and I managed to tackle a lot of long-standing ideas and issues across the entire nREPL/CIDER ecosystem. Funnily enough, I also managed to grow the ecosystem with a couple of brand new projects, but more about those later.

The big highlights from my perspective:

  • CIDER 1.22 is out
  • CIDER 2.0 is essentially ready and needs more user testing
  • Sayid is reborn
  • Two brand new projects saw the light of day: port and neat
  • Piggieback 0.7.0 is out (and Weasel got modernized while I was in the area)
  • clj-refactor and refactor-nrepl got some love as well

Below you’ll find more details about the work I did, project by project.

CIDER

CIDER 1.22 (“São Miguel”) landed in mid-June, wrapping up the 1.x series. Its main features:

  • a registry for jack-in tools, so third parties can plug new build tools and Clojure dialects into cider-jack-in
  • a “default session” escape hatch from sesman’s project-based dispatch
  • keyword-argument versions of the low-level request APIs, alongside a proper decoupling of the nREPL client layer from CIDER’s UI

It also fixed a long list of small annoyances: severe editor lag in unlinked buffers, several TRAMP and SSH tunnel problems, request id leaks, and a bunch of broken menu entries.

Right after that I switched the development version to 2.0 and most of the planned work is already done. The headline items so far:

That last one deserves a special mention: evaluation results that are images now render inline out of the box, and file/URL results offer their content on demand, six years after the feature had to be disabled over its safety problems. There was also a big cleanup pass: consolidated configuration options, the REPL history browser renamed to cider-history to end a long-standing naming clash, theme-aware faces instead of hardcoded colors, refreshed docs and a regenerated refcard. CIDER 2.0 is available from MELPA snapshots and I’d love for more people to take it for a spin before the final release.

cider-nrepl

Lots of cider-nrepl releases, driving the CIDER work above:

  • cider-nrepl 0.60.0 added the ops backing the new protocol exploration commands (cider/who-implements, cider/type-protocols, cider/protocols-with-method).
  • cider-nrepl 0.61.0 brought ClojureScript test support, a ClojureScript macroexpansion fix, formatting that honors the project’s cljfmt configuration, and a pprint backed by orchard.pp.
  • cider-nrepl 0.62.0-alpha1 and 0.62.0-alpha2 hardened the content-type and slurp middleware (URL scheme allowlist, size caps, graceful fetch errors) and cleaned up the response protocol, which is what made it safe to turn rich content on by default in CIDER 2.0.

Along the way the project’s build was migrated from Leiningen to tools.deps, which required a new MrAnderson release (see the blog posts below).

Orchard

Orchard, the library that powers much of cider-nrepl’s functionality, kept pace:

  • Orchard 0.42.0 and Orchard 0.43.0 continued the inspector polish, added symbol classification to orchard.meta, a programmatic listener API for the tracer, and protocol/multimethod introspection in orchard.xref. The project also moved to tools.deps and its CI now covers JDK 26.

Sayid

Sayid, the omniscient Clojure debugger, had been dormant for years and I finally gave it the revival it deserved:

  • Sayid 0.2.0 was the big modernization pass: new mx.cider/sayid coordinates, a documented nREPL middleware API, a consolidated op surface (37 ops down to 26) and fixes for the most annoying Emacs client breakages.
  • Sayid 0.3.0 followed with usability work: no more frozen Emacs during the reload workflow, simpler query commands and help buffers generated from the keymaps.

port

port is a brand new project I started in May: a minimalist Clojure interactive programming environment for Emacs, built on prepl instead of nREPL. It went from nothing to three releases in the course of the month:

  • port 0.1.0
  • port 0.2.0
  • port 0.3.0, which added eldoc with active argument highlighting, a wire-level message log for debugging and a roughly 10x speedup in handling large prepl responses.

I don’t have any particular plans for the future of this project - it was just something that I wanted to experiment with for a while. I see it as an interesting option for people looking for some middle ground between inf-clojure and CIDER.

neat

neat is the other new arrival: a small, language-agnostic nREPL client for Emacs. neat 0.1.0 has the essentials in place: a pure-elisp bencode codec, a comint-based REPL, and a source-buffer minor mode with eval, completion, eldoc, xref and doc lookup, tested against Clojure, Babashka and Basilisp. It’s early days, but it’s a nice testbed for exercising the nREPL protocol outside CIDER.

This project also means I’ve dropped any plans to try to make CIDER a language-agnostic development environment. Going forward CIDER will focus only on Clojure-like languages, and everything else will be covered by neat.

Piggieback and Weasel

The nREPL org saw some ClojureScript-flavored action:

  • Piggieback 0.6.2 and Piggieback 0.7.0. The 0.7.0 release makes load-file evaluate the editor’s buffer contents instead of re-reading from disk, tears down ClojureScript REPLs when their sessions close (no more leaked Node processes) and surfaces ClojureScript status in the describe response.
  • Weasel 0.8.0 modernized the WebSocket REPL: the client now uses the platform’s native WebSocket, so it runs in any modern JavaScript runtime (browsers, Node 22+, Deno, Bun, workers), and the minimum requirements moved to Clojure/ClojureScript 1.12.

I also backfilled proper GitHub releases for the historic tags of both projects, so their release history is finally browsable.

Improving the ClojureScript support in CIDER has long been a major objective for me, and these small changes were some initial steps in that direction.

refactor-nrepl and clj-refactor

refactor-nrepl got three releases: 3.12.0, 3.13.0 and 3.14.0, the last one making the AST-based indexing much faster and more reliable. clj-refactor.el received a round of maintenance on master as well, and will get a new release after I wrap up the work on CIDER 2.0.

I’m still pondering the future of both projects, as I plan to move the most useful refactor-nrepl features (those that don’t carry a lot of complexity) to CIDER and cider-nrepl eventually, and I’m not sure that the flagship AST-powered refactorings are very competitive these days (compared to clojure-lsp and static project-wide analysis a la clj-kondo in general).

I’ll write a bit more about this and I’d certainly appreciate more feedback from the users of clj-refactor on the subject. It’s funny that I’ve been maintaining the project for ages, but I’ve never really used it (mostly due to its brittleness in the past). I think I managed to address some of the biggest problems recently, but perhaps this happened too late and the project has lost its relevance by now.

Blog posts

I wrote a few articles related to the work above:

Wrapping up

Big thanks to Clojurists Together, Nubank and the other organizations and people supporting my Clojure OSS work! I love you and none of this would have happened without you. Sadly, the amount of financial support my projects receive has eroded massively over the past 4 years and I’ve kind of lost hope that this negative trend will eventually be reversed. It was never easy to maintain many popular OSS projects, but the job certainly hasn’t got any easier or more rewarding in recent years…

Overall, a super productive two months. Hopefully the next two are going to be just as productive, although I have to admit I’ve plucked most of the low-hanging fruit already. Then again, I’ve said this many times in the past, so one never knows…

-1:-- Clojurists Together Update: May and June 2026 (Post Meta Redux)--L0--C0--2026-07-05T08:57:30.000Z

Bicycle for Your Mind: Kosshi Improves

KosshiKosshi

Product: Kosshi - macOS/iOS Outliner
Price: $24.99 from App Store

The developer of Kosshi has been busy. He is working on fixing bugs and incorporating some of the suggestions that are coming from the users of the program. These are seven which got my attention.

Move Content

move comtentmove comtent

You can move content around the program with a keyboard command (⇧⌘M). That is useful because you don’t have to use the mouse/trackpad and drag and drop content to places. Press the keyboard command for Move and choose from a drop down menu of places you can move the content to. Useful when you are writing and the structure needs changing.

Navigate to a Section

navigate to sectionnavigate to section

You can obviously move to a section in the outline you are working in or a section in some other outline by clicking on the sidebar. But there is an easier way now. Press (⇧⌘O) and you get a drop-down list of sections in Kosshi. It also has a preview in the drop-down menu. Type part of the name and the program narrows the options. Select, hit return and you are there. Much easier than monkeying with the mouse/trackpad.1

Command Palette

command palettecommand palette

Kosshi has a command palette (⇧⌘A). Gives you instant access to any command you want with the keyboard command also displayed along with the command. Helps you learn the command and not worry about the command palette. This makes the task of getting familiar with the program easier and also enhances access to the commands you need.

Custom Keyboard Commands

keyboard commands customizedkeyboard commands customized

The keyboard commands are customizable. You can set them to be anything you want them to be. This lets you repeat the commands you are used to from other outlining programs and makes the learning curve in Kosshi smoother. I love it.

Screen Width and the Effect on Full Screen

screen width and full screenscreen width and full screen

Kosshi lets you set the width of the line in your outlines and so now you can specify the size of the Kosshi window to be quite large and have the Editing window be in the middle of the screen with a lot of real estate around it. It is a look I am used to from Ulysses and Emacs. I love it. This also means that the full-screen view for people with larger screens is usable. The lines don’t cover the whole screen and the full-screen view is perfectly usable.

Line Above

line aboveline above

You can now add a line above the line you are on by pressing a keyboard command.

One of the many things I am liking about Kosshi is the ability to interact and work with my outlines without accessing the mouse/trackpad. Makes the act of writing in it so much easier that I can replicate the keyboard commands I am used to from other programs. Like (⌥↩︎) for the new line above which I also have in OmniOutliner.

Move to the Parent of the Row You Are On

move to parent rowmove to parent row

When you are on a row and you want to go to the parent row, you press a keyboard command (⌥⌘↑) to get to it. When you are working on large outlines this command is an useful addition.

Conclusion

It is always a pleasure to see a program evolve, and Kosshi is evolving. The developer, Junichi Sato, is working to improve the product, by both fixing bugs and adding usability features. It is coming along nicely.

I am thrilled with the changes that are happening with Kosshi. It is now the default outliner in my workflow. I love being in it and working with it.

Kosshi is heartily recommended.

macosxguru at the gmail thingie.


  1. As you have probably noticed. I hate monkeying with the mouse/trackpad.↩︎

-1:-- Kosshi Improves (Post Bicycle for Your Mind)--L0--C0--2026-07-05T07:00:00.000Z

Protesilaos: Emacs beginner live stream with @linkarzu on 2026-07-05 20:00 Europe/Athens

Raw link: https://www.youtube.com/watch?v=ozszlnEU_3I

[ The video will be recorded. ]

I will do a live stream together with Christian Arzu, a NeoVim user, who is now trying out Emacs. In this meeting we will go over the basics. The idea is that I will do some handholding at this early stage to set Christian up with a basic configuration. We will also take any comments from the chat.

For some background, read my comments on an article that Christian posted the other day about his expectations: https://protesilaos.com/codelog/2026-07-04-emacs-for-beginners-with-linkarzu/. My article also includes commentary on some of the feedback Christian gets for his premium offerings.

-1:-- Emacs beginner live stream with @linkarzu on 2026-07-05 20:00 Europe/Athens (Post Protesilaos)--L0--C0--2026-07-05T00:00:00.000Z

Irreal: Breaking Version Changes Coming To MELPA

Jonas Bernoulli (tarsius) has a long post announcing new channels and versioning on MELPA. Why a long post? And, really, why should we care? It turns out that the process is much more complicated than you’d think it would be and will, eventually, require nontrivial action on the part of MELPA users.

The first question is, Why is MELPA introducing new channels and why are they changing the versioning? The new (experimental) channels are a vehicle for testing the new versioning. The two new channels are Snapshots—that corresponds to the current Regular releases—and Releases—that corresponds to the current Stable releases. The plan is for these new channels to replace the Regular and Stable channels.

The reasons for changing the versioning are technical and you can read about them in Tarsius’ post but the reason we care is that when the official switchover happens, MELPA users are going to have to take certain actions to ensure their ELPA distributions are consistent with the new versioning. Otherwise, the new versioning could make a new version of a package look older than previous versions. Doubtless, when the time comes the MELPA team will provide a script to automate the changeover.

As I said at the beginning, there’s a lot of complicated details involved so you should definitely read Tarsius’ post. He recommends that you get in front of things by changing to the new channels now. His post explains everything you have to do. The official changeover probably won’t be for another year so you have time to prepare.

Update [2026-07-04 Sat 13:33]: Fixed title typo.

-1:-- Breaking Version Changes Coming To MELPA (Post Irreal)--L0--C0--2026-07-04T13:56:12.000Z

Meta Redux: Projectile 3.1

Hot on the heels of Projectile 3.0 comes Projectile 3.1!

Three days apart, yes. There’s a story there. A big chunk of what’s in 3.1 was originally meant for 3.0, but 3.0 was already turning into a monster of a release and I decided to cut it into two, so I’d actually be able to reason about each of them. So think of 3.1 less as “the next release” and more as “the second half of 3.0 that I was too scared to ship all at once”.

There was also a bit of calendar mischief involved. I really wanted a release on the 1st of July - that’s July Morning, which is a bit of a thing here in Bulgaria1 - and then another one on the 4th of July. Partly because it’s the 250th US Independence Day, and partly because it happens to be my wedding anniversary. When else am I going to get a round number like that to line up? So here we are.

Unlike 3.0, 3.1 has no breaking changes. Nothing was removed, no command or option changed its name. What it does instead is knock out a pile of long-standing ideas and feature requests, most of which pull in the same direction: making Projectile leaner under the hood and a lot more extensible from your own config.

Projectile learns what you actually work on

projectile-find-file now ranks the files you visit most often and most recently at the top of the completion list. It’s the kind of thing you don’t notice until you go back to a Projectile without it and suddenly your muscle memory is off. It works with any completion UI (Vertico, the default one, whatever) and under every indexing method, and it’s on by default. If it’s not your thing, set projectile-enable-frecency to nil.

Named project tasks

The six lifecycle commands (compile, test, run, …) were never enough for real projects, which tend to accumulate a dozen little “run this incantation” commands. So there’s projectile-tasks now - an arbitrary set of named commands you can attach to a project. The nice part is you can put them in .dir-locals.el and share them with your team through the repo:

((nil . ((projectile-tasks . (("lint"   . "make lint")
                              ("deploy" . "make deploy STAGE=prod"))))))

Then s-p c x (projectile-run-task) prompts you for a task and runs it, with a prefix argument if you want to tweak the command first. This one subsumes about four separate feature requests I’d been staring at for years.

Finding files by kind

This is my favourite one. Lots of frameworks organize files into well-known kinds - Rails has models, controllers, views and helpers; Django has models, views and urls; and so on. Projectile can now describe those kinds declaratively, and it gives you two commands for free: s-p j to jump to a file of a particular kind, and s-p J to hop between related files (from a model to its controller to its views and back).

The best part is that it’s not hardcoded. Rails and Django ship out of the box, but the whole thing is driven by a :file-kinds entry on the project type, so you can teach Projectile about your own framework’s layout in a few lines. This is the “more extensible” theme in a nutshell - I’d much rather ship a mechanism than a hardcoded list.

Running the test at point

If you’re on Emacs 29+ with tree-sitter, s-p c . runs just the test your cursor is in, rather than the whole suite. It figures out the enclosing test from the parse tree and builds the right command. pytest, go test and jest are supported out of the box, and - you guessed it - you can register rules for other test runners yourself.

Other-window and other-frame, the Emacs way

Instead of a small army of -other-window/-other-frame command variants, there are now s-p 4 4 and s-p 5 5 prefixes, modeled exactly on Emacs’s own C-x 4 4 / C-x 5 5. Press s-p 4 4 and the next Projectile command opens its buffer in another window - even commands that never had a dedicated variant. The old commands still work, of course.

More solid under the hood

A few things got quietly sturdier:

  • projectile-invalidate-cache has always been Projectile’s most notorious footgun. If you opt into projectile-auto-update-cache-with-watches, Projectile watches your project and keeps its file cache in sync as files come and go, so you rarely have to think about invalidation at all. It’s off by default and deliberately conservative - when in doubt it just rebuilds.
  • The glob patterns in your dirconfig now follow .gitignore-style rules, and they behave identically under native and hybrid indexing. This kills a whole family of “why is this file showing up” bug reports that go back the better part of a decade.
  • Project and project-type detection now probe marker files with a single directory listing instead of a stat per marker, which turns dozens of sequential round-trips into one over TRAMP.

I should have done all of those a long time ago, but better late then never, right?

Wrapping up

There are no breaking changes, but a handful of defaults and behaviors did shift (auto-discovery is on by default now, for one), so give the Upgrading to Projectile 3.1 guide a quick read before you upgrade. The full list of changes lives in the changelog, as always.

With 3.1 out the door, Projectile is basically where I always wanted it to be. I’ve got a few ideas for follow-up releases, but the pressure is off - this is the release where the big items I’d been carrying around for years finally got done. Another win for my burst-driven approach to maintaining my projects: nothing happens for a while, and then a whole lot happens at once.

Keep hacking!

  1. July Morning is a Bulgarian tradition where people head to the Black Sea coast to greet the sunrise on the 1st of July. It started as a hippie / rock-and-roll thing in the 80s and stuck around. Highly recommended, if you ever get the chance. 

-1:-- Projectile 3.1 (Post Meta Redux)--L0--C0--2026-07-04T05:30:00.000Z

Wai Hon: One-Line-Per-Day Diary in Org-mode

Recently, I came across these plain text systems and concepts:

They resonated me because they match very closely with how I keep my daily diary in Org-mode!

The One-Line-Per-Day Format

I keep my diary in a one-line-per-day format. Under this system, each day is represented by a single line using Org-mode’s description list syntax:

  • Level-1 heading: The Year (e.g., * 2026)
  • Level-2 heading: The Week, containing the week number, date range, and a brief weekly highlight (e.g., ** W25 (06-15 - 06-21) - Weekly highlight)
  • List items: One line per day, starting with an inactive timestamp and a description separator (::) followed by the day’s events.

Here is an example illustrating a complete recent week:

* 2026

** W26 (06-22 - 06-28) - Run 3 times. Mele X1! CPU PL1/PL2/Multpilers. TPM: LUKS + SSH.

- [2026-06-22 Mon] :: Team dinner.
- [2026-06-23 Tue] :: Running. Daycare fire false alarm. Primes day Mini PC research.
- [2026-06-24 Wed] :: Buy Mele X1.
- [2026-06-25 Thu] :: Running. Benchmark Mele X1. Good single core performance. Emacs startup time ~0.4s.
- [2026-06-26 Fri] :: Bike to pick up. Dinner: YAYOI.
- [2026-06-27 Sat] :: TIL: Performance Copilot (pcp). Arch: migrate to systemd + sd-encrypt. TPM: LUKS unlock + SSH Agent.
- [2026-06-28 Sun] :: Running. Order Square Paper. Termux workaround with non-local network.

Daily Entries

At the end of every day, I go to the top of diary.org, add a line for today with the things that worth remembering.

To make writing daily entries frictionless, I use a simple Emacs Lisp helper. I use the following function to insert today’s date formatted as an inactive timestamp:

(defun my/insert-current-date ()
 "Insert today's date in Org inactive format."
 (interactive)
 (insert (format-time-string "[%Y-%m-%d %a]")))

I bind this command to a key combination for quick access:

(keymap-global-set "C-c s d" #'my/insert-current-date)

With this helper, starting a daily log is as simple as typing - (or using M-RET to start a list item), calling the function, typing ::, and entering the daily highlight.

Weekly Highlight

At the end of each week (Sunday), I pick the things worth highlighting from my daily entries and put them in the weekly heading. This allows me to look back at my past weeks, know what happened, and feel like my days count.

Why This Works Well

This format has greatly improved both writing and searching:

  • Self-Contained Search Results: Since the date and the content of the entry reside on the same line, searching with consult-line or ripgrep returns the full context directly. A search for a keyword like “sourdough” immediately displays the date it happened without needing to check parent headings.
  • Compact File Structure: The outline remains clean and fast to fold and navigate, with only weeks and years as headings.
  • Lower Friction: Writing a single line per day lowers the cognitive barrier to journaling. Even on busy days, it is easy to jot down a quick summary.

Conclusion

This layout is a lightweight and sustainable way to keep a daily log. By using Org-mode’s description lists and automating timestamp insertions, the diary remains clean, highly readable, and easily searchable for years to come.

Appendix: Screenshot

This is how the diary.org looks like:

-1:-- One-Line-Per-Day Diary in Org-mode (Post Wai Hon)--L0--C0--2026-07-04T03:00:00.000Z

Protesilaos: Emacs for beginners with Christian Arzu (@linkarzu on YouTube)

This Sunday I will do a live stream with Christian Arzu. The details are not finalised yet, so I will make the announcement separately.

Christian is a NeoVim user who is Emacs-curious. He is trying out Emacs and needs someone to guide him at this early stage, which I am happy to do.

Last year I had a ~2.5-hour interview with Christian Arzu from the @linkarzu YouTube channel: https://protesilaos.com/codelog/2025-08-01-linkarzu-chat-emacs-neovim-philosophy/.

What follows are some comments on Christian’s article Expectations from Prot’s Emacs Coaching Sessions (2026-07-03).

Install Emacs and what version to use on the MacOS

The premise of our meeting is that I will do the handholding. You will thus install the emacs-plus package version 30. There are always a zillion excellent options, though handholding demands that we pick one.

Emacs 30 is the latest stable version of Emacs. Emacs 31 will be released in the near future, at which point you can upgrade.

In general, Emacs is a very conservative project (which I think is the right approach): there are no major breaking changes between versions, so even an older stable version will most probably be perfectly fine.

Vanilla Emacs or Doom Emacs?

Vanilla 100%, which is why there is no “Prot Emacs” distro. You have to try this in earnest for at least one week.

The disruption vanilla Emacs creates is good for you long-term:

  • it forces you to slow down;
  • it makes it clear that your old habits do not apply;
  • now you have to gradually learn from the ground up.

I come from a Neovim background, so is Doom Emacs a good choice or not and why.

Doom Emacs is fine in its own right: it takes care of everything. With vanilla Emacs you have to piece together a system that works for you, so you will better understand what each part does and why it is there.

This will, in turn, empower you to make more informed decisions as you continue in your Emacs journey. Plus, you will be exposed to more Emacs Lisp, which is what unlocks the superpower of Emacs’ extensibility.

Tutorial and basic keys

So should I do the whole thing or should I start with a few basics and go from there. Like a cheat-sheet or something?

The tutorial is mandatory. Do it again if you have to.

Other than that, you are on a custom keyboard which has the potential to make things much easier for you.

  • I assume you already have the arrow keys available at some convenient spot (mine are LAYER + h, j, k, l (qwerty layout)).
  • Bonus points is you also have Home, PgDn, PgUp, End bound somewhere (mine are LAYER + y, u, i, o). Those are enough for basic motions.

Additionally, make sure you can access the Ctrl and Alt (Meta) keys. Emacs relies on them for practically everything.

What do I want Emacs for?

I still don’t know what I want, but I think that I want to use the life organizing abilities that Emacs provides through org.

I think Org is a good reason to start with Emacs.

Writing your tasks and notes in Org will also help you practice general skills, such as to open a file, create a bookmark, switch to a buffer, move around to edit text, search/grep for something.

Beside that, you want some core functionality that improves every aspect of Emacs. This concerns the minibuffer, which is the interface where you can provide input such as to choose a command to run, open a new file from anywhere in the filesystem, switch to a specific buffer, and more.

If you commit to using Emacs for Org, you will find other interesting uses, such as to have your RSS reader in Emacs. When you read an RSS entry, you can link to it with Org, so you can create a task/note about it. Same idea for eventually integrating your email in Emacs: you use Org to connect messages you receive to appointments, for example.

And if you have done all that, well, you will be searching for reasons to bring your programming and virtually everything text-related into Emacs. You will understand why it makes sense to do things this way.

You’re a fraud, why do you ask for money, isn’t YouTube Ads enough?

This is about Christian’s premium offerings for which he got backlash:

I do 100% agree that I owe these interviews to my guests, I can’t thank them enough. Prot and many of the other guests have dedicated years of their life into researching and figuring stuff out, to then share it with all of us in an easy way to understand. That is just priceless, all of that work and dedication, that is usually tied to paid education and years of experience, just being shared with us. So I admire and have so much respect for the guests.

Christian does a lot of decent work with those videos. If you have watched any of my videos, you know that their production quality is poor, the audio is awkward, and even my hairstyle is unpredictable! 🙃 Christian’s videos are high quality. The guests, where relevant, are also carefully chosen, making for insightful conversations.

For example, in my interview with Christian I had the opportunity to cover many topics that I would otherwise not mention the way I did. This is no mere repackaging of information: Christian helped me share something new in a format that is fun and approachable.

Even without guests though, these are publications about tools we care about. It is a nice, casual experience. We have something to discuss and we share our interests together.

In short, Christian makes a positive contribution to the community.

I am in favour of Christian asking for donations and even paywalling some of his contributions. Same for everybody else. Nobody is entitled to content and the free stuff out there is already more than enough.

Emacs is a great piece of software. Though what makes it even better is the community around it. Through the community we preserve knowledge and get extensions for Emacs that make our computing life a bit better.

I personally dislike the snake oil merchant, the one who promises magical solutions to fix your life. But even then, the real work is to encourage people to be prudent by applying judgement. Denouncing someone is not constructive long-term: they do what they can while we focus on doing the work we care about.

That granted, learn to ignore negative people (which can also be the naysaying inner voice). They will complain if you are arrogant. They will complain if you are modest. They will ridicule you if you are creative. They will ridicule you if you are conventional. They will call you names if they find you pretty. They will call you names if they find you ugly. They have nothing better to do, whereas you have plenty of things to contribute. The best middle finger is consistent, high quality work—and this is, paradoxically, how the negative person provides the impetus for something decent to happen.

-1:-- Emacs for beginners with Christian Arzu (@linkarzu on YouTube) (Post Protesilaos)--L0--C0--2026-07-04T00:00:00.000Z

Irreal: Automatically Signing And Encrypting Email With Mu4e

Nicolas Cavigneaux has an excellent post on signing and optionally encrypting emails sent with mu4e. The idea is to sign each email you send and if you have a GPG key for every recipient, encrypt it was well. As Cavigneaux says, it’s pretty easy to sign and/or encrypt messages with mu4e but you have to remember to do it and most people, including him, don’t always remember.

The solution, of course, is to automate the process so that you don’t have to remember. That turns out to be pretty easy to do. Cavigneaux has the necessary code in his post. The basic flow is to check if you have everyone’s encryption key and, if so, encrypt the message. Regardless of whether or not everyone’s encryption key is available sign the message. There are calls available for both the signing and encryption. The process is kicked off with the send-message-hook every time you send a message.

There was a time when encrypting all your messages was the thing to do but difficulties with key distribution have pretty much put an end to the encrypt all emails movement. Most people don’t have keys and those who do will probably be annoyed to receive mundane emails that are encrypted. For those sensitive emails being sent to people you know have keys, you pretty much automatically remember to encrypt them.

That leaves signing. It mostly doesn’t hurt anything1 so it probably doesn’t matter if you always sign your messages, especially if you have reasons to suspect that their contents might come into dispute. I’ve stopped doing even that because, as I say, no one has any idea what it means.

So my takeaway is that if you’re paranoid about certifying the content of your emails or you regularly deal with people who want to ensure themselves that they’re communication with you, it may make sense to automatically sign your emails. Except in special circumstances, I don’t see any reason to encrypt all your emails.

Footnotes:

1

Although I do remember being queried about those unreadable binary blobs at the end of my emails.

-1:-- Automatically Signing And Encrypting Email With Mu4e (Post Irreal)--L0--C0--2026-07-03T15:06:33.000Z

Sacha Chua: Re: React to Sacha and Prot Newbies and Starter Kits Emacs Video - linkarzu

: All right, quick video form of this post is at Yay Emacs 35: Reacting to Linkarzu's reaction to my video with Prot about newbies and starter kits - YouTube in case anyone wants. That's how it works, right? =)

Hey hey hey, now I'm linkarzu-famous. =) Linkarzu (Christian Arzu) posted a reaction video to the first part of YE24: Sacha and Prot Talk Emacs - Newbies/Starter Kits. Here's his vid:

YouTube might be holding my comment for moderation because I tried to add too many links to it. I also realized my timestamps were off in my YT comment, so here it is along with other stuff I've just added.

On my Newbies/Starter Kits chat with Prot

It's definitely more of a meta-discussion (how can we make the newcomer experience better?) than something directly focused on helping newbies, but I hope you're getting something out of it. Think of it like a live coaching session for me so that I can figure out what to prioritize on my TODO list to make the newcomer experience better, with some ideas and questions thrown out there in case other people want to work on things too.

Learning Emacs in order to organize your life

02:20:13 What do I want to do with Emacs? I think, like I've said it before, organize my life a little bit better. Try Org, pretty much. I don't care about Emacs for editing Markdown files. That wouldn't make sense, you know, because I can do that in Neovim quite well. And I don't want to replicate that in Emacs. That's just going to be a waste of time. Something that I don't do in Neovim. This is something that the professor said as well: Just use Emacs for something that you don't do in Neovim right now.

If you want to learn Emacs so you can use it to organize your life, there are more direct paths than my video about newbies/starter kits. You might be just fine with the basic tutorial (Ctrl+h t within Emacs), the Org Mode compact guide, maybe another Org Mode tutorial that matches the way you think, and some time experimenting with the basics until you figure out the kinds of things you'd like to improve. The idea is to quickly get to the point where this is useful, and then start using some of the time/energy saved to learn more. A simple progression might start with something like this:

  • Opening: Use Emacs to open and close your todo.org. No keyboard shortcuts needed, just open the file, type, and use the toolbar or menu bar to save. If you're in a console Emacs and you don't want to use the mouse, you can use F10 to open the menu.
  • Leaving Emacs open: Realize you can save time by just leaving Emacs open with the file instead of opening/closing it all the time. This is probably more of a mindset change for Vim users. Set up your window manager so that you can switch to Emacs with a convenient keyboard shortcut. I use super+1.
  • Themes? If the default theme gets on your nerves, figure out how to change it. M-x customize-themes is a good starting point. If you're not sure what that means, go through the tutorial (Help - Emacs Tutorial).
  • Keyboard shortcuts: Get annoyed with using the toolbar or menu bar to save. Get the hang of C-x C-s (save-buffer). Start to get your mind used to the idea of keyboard shortcuts being different in different apps. Try not to give in to the temptation to make this C-s like in other apps. C-s is isearch-forward, which you will probably eventually find really useful, and if you move that you will end up needing to move whatever you are moving it to. Use sticky notes to remind yourself of the handful of keyboard shortcuts you're learning.
  • Basic Org Mode tutorial: Read an Org Mode tutorial, maybe this one. Start with * TODO ... headings. You can manually type them. Change TODO to DONE. Again, this can be pretty manual.
  • Org markup: Learn how to open links, make subheadings (**, ***), etc.
  • Shift: Get annoyed with manually typing TODO keywords. Experiment how to use shift left and shift right. (Might not work on console Emacs, depending on what keyboard shortcuts your terminal supports.)

Feel free to switch steps around depending on where the friction is. Depending on what you want to do from here, you might want to learn about scheduling things and displaying an agenda or setting up capture (which gets even more useful as you do more things within Emacs, since it can automatically pick up links to whatever you're looking at).

Also, along the way, it could be worth flipping through the StarterKits page on the EmacsWiki to see if one of those options matches the way you think (totally optional), or maybe chat with Prot so he can help translate what you want into the keywords you can use to find stuff or the priority to learn things in. Meetups are great too.

Learning Emacs with people

  • 00:33:20 I need a daddy that holds my hand and guides me through the process. I'm lost. And chat is even way looser. Even more lost, you know, because they're like, try Doom, try Evil and try this and try the other one, you know, and run Neovim inside Kitty and no, run Emacs inside Kitty or no, use the GUI. No. So it's pretty confusing.
  • 01:28:45 You know what we should do, okay? We should pay Prot for his coaching sessions. He's in Greece, right? He's in Cyprus. To give something back to him. But I don't know if he's okay in transmitting this live because all of these cheap ass MFers watching that would be watching this live stream will not pay Prot. So that's bad business, brother, because the live streams are just going to stay there. But if I do it for myself, if I pay to him, we have the one-on-ones and I don't post them and I learn Emacs, you know, that is not… That's going to leave you guys behind.

Yup, mentorship/coaching is totally a great way to learn Emacs. Prot is okay with people livestreaming or posting a recording of the coaching session. This livestream is actually one of those instances - I set it up as a coaching session with him! =) Amin Bandali has also posted some of his sessions with Prot (FFS code review and Emacs extensibility with Protesilaos, FFS code review with Protesilaos - bandali). I love your recent livestreams about exploring Emacs. Learning out loud is fantastic. It lets other people help out, and you help lots of people along the way. If you're comfortable with the idea, I think livestreaming or posting a recording of a coaching session with Prot would be wonderful. You've mentioned wanting to use Emacs to organize your life, so it's of course totally okay to chat privately. That way you don't have to worry about leaking any private information. Either way works!

This thing about balancing learning from resources and learning from people is an interesting one to think about. On one hand, we don't want a flood of generic requests from help from people who haven't bothered to look things up for themselves. On the other hand, because Emacs is so large and so many things are possible (and also oddly-named), it really helps to be able to talk to people. It's like the way you could learn how to play the piano or speak a different language by yourself, but a piano teacher could help you pick the right pieces for your level, and a tutor can help you with the nuances and pronunciation feedback that a dictionary or a textbook can't. I think learning how to learn from both resources and people is definitely a good skill worth working on during the early days, which could include:

  • taking notes and sharing them - great way to solidify your knowledge and pay it forward
  • learning how to skim tutorials and references to pick up ideas and terminology without feeling like you're progressing too slowly
  • learning how to break the things you want into bite-sized chunks so that they can actually fit into your brain; use sticky notes and text files to help you
  • connecting with people, learning how to ask questions

Reddit links

00:35:32 I'm not an Emacs user. I'm an Emacs, I don't know, tester or trier, whatever. If I come to the Emacs subreddit, for me, it's just a waste of time because I will not be able to find anything. I don't know where to find stuff. Maybe if I go to the about page, but who does that? Okay. Who in their sane mind comes here to the about page?

… Getting started, maybe here. Get Emacs. Emacs resources. Okay, but if I go to the EmacsWiki… How to edit Elisp area. Like there's a thousand links here and that is just the first link, brother. Then I have this other one. WikEmacs. OK, I trust it, so I'm just going to hit continue. WikEmacs.org. That one doesn't even load. Emacs reference. Not found. OK, so you see what I'm saying? This one has a thousand links and I just by looking at the amount of them, I'm like, nope, don't want to look at that. Maybe this one. The book, OK, the book, this is the book that Aaron recommends.

… So I think the suggestion that Prot is sharing there is quite useful, to be honest. Have them pinned here. I have something like that in my subreddit, you know, things that I want people to see when they get there. So when they get to the server, the first thing that I want them to see is this: The Discord information that the podcast has been moved to a different YouTube channel. This is useful as well. … So yeah, this is actual information that I want people to know. If those would be updated on the Emacs subreddit, I think it would be a good idea.

Oooh, good catch. I've messaged the r/emacs mods about the dead links, suggested direct links to Emacs Newbie and Starter Kits on the EmacsWiki, and suggested adding Doom Emacs and Spacemacs if Better Defaults is listed. The sidebar isn't very visible (most people miss it, especially on mobile), but every little bit helps. Usually what happens is a newbie posts about their question on r/emacs and someone replies with some helpful resources. An Automod or a sticky might help. I sent your video + timestamp to the r/emacs mods.

Timestamps

  • 44:01 So how does she add those timestamps? … Oh, she typed some magic there. She typed something and then a timestamp was added.
  • 01:30:25 We could see what she typed there. Let's see. Is she in normal mode? Does she use modal navigation or something? Let's see. OT. She typed OT and then she… OT and then she presses a key, which probably expands the snippet or something.

I have an abbreviation "ot" that expands to the timestamp after I press space, comma, other punctuation, tab, whatever. This is convenient for me to type because it's home-row on Dvorak. Here's the relevant part of my config:

(setq-default abbrev-mode 1)
(define-abbrev global-abbrev-table "ot" ""
  (lambda () (insert (format-time-string "[%Y-%m-%d %a %H:%M]"))))

Big picture: I added this abbreviation for timestamps because I wanted a quick way to keep track of highlights, things to clip, possible chapter markers, etc. I could calculate it as a relative time using org-timer (there's a built-in feature), but wall-clock time is easier to use in calculations in case I want to adjust it later on. So, for example, I now have a little bit of code (sacha-stream-org-convert-timestamps-to-youtube-offsets in my config) that replaces all the timestamps in a selected region with the offsets based on the start time of the livestream that includes those timestamps. I can export the selection into a plain-text format that I can paste into the YouTube video description for quick chapter markers. Then I can bulk-add comments with those timestamps into the VTT transcription produced by WhisperX (subed-vtt-insert-chapter-comments in subed), move them earlier or later to match the actual times, copy the corrected chapter markers into YouTube (subed-section-comments-as-chapters), and use those chapter markers when publishing the transcript (using Org Mode and a custom link type). This is because I don't usually have the patience to listen to my whole video again and I don't expect people to have the patience to listen to my whole video either, so I want people to be able to quickly jump to the parts that might be interesting for them. =) I'm not sure this is a workflow you can easily pick up if you're starting from scratch (… haven't confirmed that it actually works for anyone other than me…), but I'm mentioning it to give kind of the big picture of why I have that snippet and what else it enables. Because Emacs!

Timestamps are very handy. I even have some code that schedules a YouTube livestream for the Org timestamp at point (sacha-stream-org-schedule-livestream-for-entry-at-point), using the title and body of the Org subtree and uploading the thumbnail from the Org entry :THUMBNAIL: property (or a default property). It inserts the YT embed. I have another function for setting up a Google Calendar entry so I can invite the guest (sacha-emacs-chat-schedule). I mess up times and timezones all the time, so the less I have to manually click on stuff, the better.

My evil plan

1:31:36 Now why is she interested in doing all this, brother? This is a pretty good person, actually. Why is she so concerned about the experience for newcomers in Emacs? On the Neovim side of things, it's like, brother, you're just on your own. "F* yourself, go and watch some videos, and if you get it, awesome." Now, there's really amazing people as well on the Neovim side of things. I'm just talking shit, but I'm honestly curious, like… She's really concerned about new people joining into the Emacs church. Is this a church really? Like, okay, do we have to pay after once we're part of the church? Like, do we need to give like 10% of our income to the Emacs church?

Hah, it's all part of my Evil Plan. (Not to be confused with evil-mode.) Sure, Emacs isn't a good fit for everyone. I think the people who seem to really click with it and with other people who use it are the ones who enjoy tinkering and who can (mostly) find the balance between getting stuff done and tweaking their setup. =) If this might be your jam, I hope you can get past the initial hump and get to the point where it gets to be fun and useful! Sometimes it takes several tries for it to stick. We have lots of stories of people who didn't get Emacs the first time around, but who eventually figured it out later. I love that there are so many people who've used Emacs to make a TODO system that actually works for them. (It's usually Org Mode, but sometimes it's something else, that's all cool.) I love that people can do little tweaks to remove friction or make new things possible step by step.

So, the evil plan:

  1. If people learning Emacs can connect with resources and people who can help them enjoy figuring things out, then…
  2. they'll get to the point where they can come up with ideas and make things better for themselves.
  3. This often turns out to be useful for other people too,
  4. and then people can bounce ideas around and make things even better.
  5. So, years down the line, I'll want to do something crazy with Emacs and someone will already have written a function for doing it. ;)

See? I'm just planning ahead. Bwahaha! Also, I love seeing the kinds of cool things people come up with and share, even if I might not personally need it (yet). It's fun. I hope you get the hang of it. I think that could lead to lots of interesting conversations. Even if you decide to use something else, that's cool too. The important thing is that you're figuring out stuff that works for you! =)

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- Re: React to Sacha and Prot Newbies and Starter Kits Emacs Video - linkarzu (Post Sacha Chua)--L0--C0--2026-07-03T12:24:23.000Z

Emacs Redux: Automating Emacs Screenshots

I maintain a bunch of theme ports these days: Zenburn, Solarized, Tokyo Night, and most recently Batppuccin. Every one of them wants screenshots in the README, ideally one per variant, so people can compare the flavors at a glance.

I’d been putting this off forever, because taking the screenshots by hand is such a chore. Load a theme, resize the frame, arrange a nice-looking buffer, take a screenshot, crop it, then repeat for every single flavor. And the results are never quite consistent: the font is a little different, the window is a slightly different size, the crop is off by a few pixels. Multiply that by four flavors across several themes and you can see why I kept finding better things to do.

So recently I finally did what I should have done from the start and taught Emacs to take the screenshots for me. What started as a five-minute hack turned into a surprisingly deep little rabbit hole, so I figured it was worth a write-up.

The core idea is simple: spin up a throwaway emacs -Q, load the theme, show a sample buffer in a frame of a fixed size, and capture just that window. Because every step is scripted, the screenshots come out identical in layout, and regenerating the whole set after a color tweak is a single command.

Four Batppuccin flavors, generated automatically

A clean, disposable frame

The first half of the job is pure Emacs Lisp. I want a frame with no distractions (no tool bar, menu bar, or scroll bars), a nice font, a fixed size, and of course the theme loaded:

(setq inhibit-startup-screen t)
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq-default cursor-type nil)          ; hide the cursor for a clean shot
(set-face-attribute 'default nil :family "Fira Code" :height 150)

(add-to-list 'custom-theme-load-path "/path/to/theme")
(load-theme 'batppuccin-mocha t)

(set-frame-size (selected-frame) 92 36)
(find-file "sample.el")

I load all of this into a throwaway Emacs:

emacs -Q --eval '(load "setup.el")'

A couple of things bit me here that are worth calling out:

  • emacs -L some/dir puts a directory on the load-path, but load-theme searches custom-theme-load-path. Those are two different lists, so remember to add the theme’s directory to the latter.
  • If your sample file lives in a project with a .dir-locals.el, opening it can pop up an “unsafe local variables” prompt that blocks the whole script. Setting enable-local-variables and enable-dir-local-variables to nil sidesteps that.

I use an Emacs Lisp file as the sample, by the way. It highlights nicely and, being the mother tongue, needs no third-party major mode, which keeps the whole setup dependency-free.

Now there’s a pretty Emacs frame on screen. The other half of the problem is turning it into a PNG.

Option 1: let Emacs export itself

The cleanest approach doesn’t involve a screenshot at all. If your Emacs is built with Cairo (as the GTK build on Linux typically is), it has a wonderful function called x-export-frames that renders a frame straight to an image:

(with-temp-file "shot.svg"
  (insert (x-export-frames nil 'svg)))

It can emit svg, pdf, postscript, or png. No external tools, no window-manager wrangling, no “don’t touch the mouse while it runs”. Emacs simply hands you the picture. If you’re on a Cairo build, stop reading and use this.

Alas, I’m on macOS, where Emacs uses the NS toolkit and x-export-frames isn’t available (you get a friendly void-function). So I had to go the screenshot route.

Option 2: screenshot the window

macOS ships with screencapture, which can grab a rectangular region of the screen. The trick is knowing exactly where the Emacs frame is. Conveniently, Emacs knows: frame-edges reports the outer pixel coordinates of the frame:

(let ((e (frame-edges nil 'outer-edges)))
  (with-temp-file "/tmp/geom"
    (insert (format "%d %d %d %d" (nth 0 e) (nth 1 e) (nth 2 e) (nth 3 e)))))

The shell side reads those coordinates and captures the rectangle:

read L T R B < /tmp/geom
screencapture -x -R "$L,$T,$((R - L)),$((B - T))" out.png

And that’s basically it. Except it isn’t.

The part nobody warns you about

Here’s the catch with region capture: screencapture -R grabs whatever happens to be on screen at those coordinates. If any other window is sitting on top of the Emacs frame, you’ll cheerfully capture that instead. And since I like to keep working while the script churns through a dozen flavors, this happened all the time. I’d end up with a screenshot of my terminal, my browser, or my other Emacs.

I ended up stacking a few tricks. First I float the frame above everything else with the z-group frame parameter, (set-frame-parameter nil 'z-group 'above). Then, since an Emacs launched from a background shell isn’t the active application and starts out buried behind the other windows, I pull it forward with (ns-hide-emacs 'activate).

Neither of those is bulletproof on its own, so the real safety net is checking the result. After each capture I sample a pixel from a corner that should be the theme’s background and compare it to the color Emacs reports for the default face. If they don’t match, I know I grabbed the wrong window, so I wait a moment and try again. That one check is what makes the whole thing safe to run while I keep working. Asking ImageMagick for a single pixel is enough:

magick out.png -crop '1x1+24+420' +repage -format '%[pixel:p{0,0}]' info:

Capturing a specific window by its ID would sidestep the stacking problem entirely, but on recent macOS enumerating window IDs requires Screen Recording permission for whatever process asks, and I couldn’t get that from a plain script. Floating the frame and verifying the result turned out to be simpler and more reliable.

A few finishing touches

A couple of smaller tweaks made the output look properly polished. GUI Emacs on macOS colors the native title bar according to the frame’s ns-appearance parameter, so I set it to light for light themes and dark for dark ones, and add ns-transparent-titlebar so the bar blends into the buffer background. It’s a tiny detail, but it makes the whole window feel like one piece. (It’s also the same trick that fixes an unreadable title bar under light themes, but that’s a story for another day.)

The other tweak was really a bugfix. Every so often a shot came out with a stray glyph or two at the top of the buffer, some redisplay artifact I never bothered to fully diagnose. Forcing a (redraw-display) right before I read out the frame geometry made it go away.

Other roads not taken

If you want to run this on a server or in CI, the story gets even better on Linux. You can start a virtual X display with Xvfb, run the Cairo Emacs build against it, and use x-export-frames: fully headless, perfectly reproducible, and with none of the “is the right window on top?” nonsense. That’s the setup I’d reach for if I ever wanted to generate these in a GitHub Action.

There are also plenty of other capture tools depending on your platform: ImageMagick’s import and the venerable scrot on X11, grim on Wayland, gnome-screenshot, and so on. Most of them can target a specific window, which is handy. One thing that won’t work is emacs --batch: batch mode has no GUI frame, so there’s nothing to photograph. You genuinely need a real, on-screen frame (or Cairo’s off-screen rendering).

Where this is headed

For now this lives as a small tools/ script inside my Batppuccin repo: a sample.el to display, an Emacs Lisp file to set up the frame, and a shell script to drive the capture and verification. It has already saved me a ton of tedium. Regenerating four flavors’ worth of screenshots is one command, and they come out identical every time.

But all of my theme ports have the same need, and the logic is almost entirely theme-agnostic. So I suspect I’ll eventually pull it out into a little standalone project: point it at a theme directory, hand it a sample file, and let it produce a consistent gallery for any theme. If that sounds useful to you as well, let me know. It might just nudge me into actually doing it.

Until then, I hope some of these ideas save you a bit of manual cropping. Automating the boring parts is what Emacs is all about.

-1:-- Automating Emacs Screenshots (Post Emacs Redux)--L0--C0--2026-07-03T07:00:00.000Z

Joar von Arndt: Automatically Find PGP Keys in Mu4e


I have found a peculiar delight in encryption, and particularly in using pgp-based asymmetric cryptography. This is especially useful for securing email communications, but doing so requires remembering to run M-x mml-secure-sign or mml-secure-encrypt in Mail Utilities For Emacs (Mu4e) before sending the message. For this reason I have been wanting to write a quick method that automatically adds signing capabilities and encryption only when the recipient has a public key. Thankfully, I did not have to do so because of Nicolas Cavigneaux’s recent post Sign Always, Encrypt When Possible where he showcases his functions for doing exactly that. It prompted me to tweak it slightly, and to add the functionality to automatically discover public keys that I have yet to import.

Cavigneaux’s post sets up an elegant arrangement: email should always be signed with your pgp key, and you should “upgrade” from there to encryption only if your correspondent can handle encrypted mail and has shared their public key with you. This is done by using Emacs’ built-in Easy Privacy Guard (epg) tooling to compare the owners of the imported public keys on the machine with the list of recipients in an email.1 If you have the public key for all recipients, the message will be automatically encrypted — otherwise it will merely be signed to prove that you are the author.

This was exactly what I had wanted, and I quickly incorporated this functionality into my own configuration. Testing it out, I felt a bit uneasy sending encrypted mail automatically. Because it is not the default, I felt that fully automated encryption made me unsure whether the mail has been encrypted or not — even when I know I have a public key. I thus made the slight modification of prompting for both encryption and signing:

(defun kudu/message-sign-encrypt-if-all-keys-available ()
    "Add MML tag to encrypt message when there is a key for each recipient,
sign it otherwise."
    (if (bounga/message-all-epg-keys-available-p)
        (if (y-or-n-p "Encrypt? ")
            (mml-secure-message-sign-encrypt)
          (when (y-or-n-p "Sign? ")
            (mml-secure-message-sign)))
      (when (y-or-n-p "Sign? ")
        (mml-secure-message-sign))))

In practice I will probably always be pressing Y when prompted, but this adds a level of certainty that the code is running correctly. It reminds me of an advertising gimmick for housewives in the 1950s: when cake mix first became available, it was not appreciated because it was perceived as being “too easy” — it did not feel like baking a cake yourself. And so the recipe was tweaked to simply require an extra egg to be added. In practice this was a trivial change, but it meant that you felt more responsible for the finished work.

But there is an additional step I want to automate that Cavigneaux alluded to, but did not implement. He writes:

[T]he policy is only as good as my keyring. Encryption depends on having imported the right public keys; nothing here fetches them for me.

I have recently been having more correspondence with people using the Swiss-based email provider Protonmail. Proton provides support for the Web Key Directory (wkd) method of providing keys, where the dns settings of a domain point to a file containing a public key for a specific user. This allows the email itself to “provide” its own public key for encryption. The public key of any @protonmail.com or @pm.me address can be quickly imported in a second using gpg --locate-external-keys email@pm.me. Proton will similarly use wkd to get your public key, and so you will receive encrypted mail as well. This is still easy to set up for any other email provider, as long as you can edit the domain settings.2

Sadly, epg does not come with any options for Gnupg’s --locate-keys or --locate-external-keys flags. This might be a good addition. In the meantime, we can write a quick and dirty function to do the job for us in the background when opening any new email:

(defun kudu/message-locate-keys ()
  "Tries to find the public keys of 'bounga/message-recipients' via WKD through --locate-keys."
  (dolist (recipient (string-to-list (bounga/message-recipients)))
    (let ((recipient-email (cadr recipient))
          (proc (make-process
                 :name "gpg-locate-keys"
                 :command (list epg-gpg-program "--no-tty" "--locate-keys" (cadr recipient))
                 :connection-type 'pipe
                 :filter (lambda (proc string)
                           (process-put proc 'output
                                        (concat (or (process-get proc 'output) "") string)))
                 :sentinel (lambda (proc event)
                             (when (eq (process-status proc) 'exit)
                               (let ((output (process-get proc 'output))
                                     (email (process-get proc 'email)))
                                 (if (and output (string-match-p "imported: [1-9]" output))
                                     (message "Public key imported for %s" email))))))))
      (process-put proc 'email recipient-email))))

We use --locate-keys because the stricter --locate-external-keys will check the domain even if a key already exists in the local keyring. --locate-keys will instead instantly find local keys and finish early, while only missing keys will be checked. This is done asynchronously through make-process so that it does not cause the ui to freeze as we wait for network requests to finish. gpg seems to check very quickly, so it is not a large performance hit regardless.

We want to have imported any new keys before sending our email because Cavigneaux’s functions are run on 'message-send-hook. We therefore run kudu/message-locate-keys much earlier:

(add-hook 'mu4e-view-rendered-hook 'kudu/message-locate-keys)

This adds any correspondents’ public keys on any opened email, and so does not have to go through your entire contacts list. A downside is that it does not check for a public key when you are the person sending an unsolicited email, but it will when you get their reply.

This is not the world’s most elegant function, and if someone more familiar with epg has any comments regarding ways to improve it I will happily edit this post for improvements. Just send me a (pgp encrypted!) email. Run echo 'am9hcnhwYWJsb0B2b25hcm5kdC5zZQ' | base64 --decode | xargs gpg --locate-external-keys to import my key. If you have wkd set up, I will automatically fetch your public key. If not, this is a great opportunity to fix that!

I will also note how wonderful Emacs and epg make working with encrypted files, messages, and/or text more generally. I must admit that I used a plain-text .authinfo for an embarrassingly long time to store some of my secrets, thinking it would be bothersome to use the encrypted .authinfo.gpg for auth-sources. If you are like me, do not worry — the peace of mind from being able to write down personal information and simply leave it scattered on your filesystem is well worth just having to type your password.

Footnotes:

1

From Cavigneaux’s post, the function to get all message recipients. Returns a list formatted like (("First recipient" "first@domain.tld") ("Second recpient" "second@domain.tld") ...):

(defun bounga/message-recipients ()
  "Return a list of all recipients in the message, looking at TO, CC and BCC.
Each recipient is in the format of `mail-extract-address-components'."
  (mapcan (lambda (header)
            (let ((header-value (message-fetch-field header)))
              (and
               header-value
               (mail-extract-address-components header-value t))))
          '("To" "Cc" "Bcc")))
2

So no @gmail.com, @outlook.com, or @yahoo.com addresses. You can still use these to service your mail; wkd does not interact with any email-related dns records.

-1:-- Automatically Find PGP Keys in Mu4e (Post Joar von Arndt)--L0--C0--2026-07-02T22:00:00.000Z

Irreal: Magit 4.6 Is Released

Jonas Bernoulli is announcing the release of Magit 4.6. It includes, he says, over 300 commits since the last release 6 months ago. You can check Bernoulli’s post for the details but an overview is that the main changes were in

  • Blob handling. Working with blobs should be easier and make more sense with this release.
  • Experimental syntax highlighting in diffs.
  • Improved hook handling that allows for the calling of Elisp hooks from Git hooks.
  • Improved commit message processing. List of changed definitions in a diff are now available for composing a git commit message.

As I’ve said before, Magit and Org Mode are responsible for luring many new users into the Emacs event horizon so these improvements are important. Moreover, they raise again the need for supporting this effort. Bernoulli spends full time on Magit on a few other open source projects and depends on contributors for his livelihood Take a look at his support page to see how you can help out.

-1:-- Magit 4.6 Is Released (Post Irreal)--L0--C0--2026-07-02T14:46:33.000Z

Protesilaos: Emacs: write with input method (e.g. French) and Jinx for spelling

Raw link: https://www.youtube.com/watch?v=UjCbNOqUgTs

In this video demonstration I show the tools I use to write in French using Emacs. One is the built-in framework for input methods, which allows us to compose characters to express the full range of the language we are typing in (e.g. French or Greek). The other is a spell-checking package called jinx, which is developed by Daniel Mendler.

Below is the code I showed in the video:

(use-package emacs
  :demand t
  :bind
  ( :map global-map
    ;; The `toggle-input-method' sets the latest selected input
    ;; method, or the one defined in `default-input-method'.  Once an
    ;; input method is set, `toggle-input-method' will switch back to
    ;; the standard Emacs input.
    ("<f2>" . toggle-input-method))
  :config
  ;; Call the command `describe-input-method' to get a description of
  ;; what the given input method supports in terms of key sequences as
  ;; well as the key layout it has.
  (setq default-input-method "french-postfix"))



;; You need to have libenchant available on your system. For example,
;; Debian provides the package `libenchant-2-dev'.
(use-package jinx
  :ensure t
  :demand t
  :bind
  ( :map global-map
    ("M-$" . jinx-correct) ; or bind `jinx-correct-all'
    ("C-M-$" . jinx-languages))
  :config
  ;; Here you can specify a string with space-separated dictionaries.
  ;; I install the aspell dictionaries, such as the Debian package
  ;; `aspell-fr' for French and `aspell-el' for Greek (Éllinika).
  ;; With `aspell' installed on the system, do `aspell dicts' on the
  ;; command-line to get a list of available dictionaries.
  (setq jinx-languages "en fr el es")

  ;; I want to have Jinx in programming modes but I do not want it to
  ;; check anything that is a comment or string, because then it
  ;; underlines too many things which are not useful. We can do the
  ;; same for other modes, though I think this is fine.
  (setq jinx-exclude-faces
        '((prog-mode font-lock-comment-face font-lock-string-face)))

  (global-jinx-mode 1))
-1:-- Emacs: write with input method (e.g. French) and Jinx for spelling (Post Protesilaos)--L0--C0--2026-07-02T00:00:00.000Z

Nicolas Cavigneaux: emacs-mailto: Open macOS mailto: Links in Emacs

What it is

emacs-mailto makes macOS mailto: links open in Emacs, using whatever mail-user-agent you already have configured: mu4e, notmuch, Gnus, Rmail, or plain message-mode. Click a mailto: link in your browser, and Emacs comes to the front with a compose buffer already addressed.

The project lives at sr.ht/~bounga/emacs-mailto, where you’ll find the source and the ticket tracker.

The problem it solves

The GNU Emacs NS port (as shipped by emacs-plus, emacs-mac, or the official build) does not handle mailto: URLs.

When Emacs is already running, clicking a mailto: link activates Emacs but the URL never arrives: macOS brings the app to the front and drops the URL Apple-event before it ever reaches Emacs Lisp. So nothing happens: no compose buffer, and no way to fix it from your config, because the event never gets there in the first place.

That’s the whole frustration emacs-mailto exists to remove.

How it works

emacs-mailto sidesteps the dropped Apple-event with a tiny relay app registered as your default mail application. On a mailto: click, it brings Emacs to the front and hands the URL to emacsclient:

open -a Emacs
emacsclient -n -e "(run-at-time 0 nil (lambda () (browse-url-mail \"<url>\")))"

browse-url-mail parses the mailto: URL (recipient, subject, body) and opens a compose buffer through your mail-user-agent. The run-at-time wrapper lets emacsclient return immediately instead of blocking on an interactive prompt, such as a From-address picker.

A few details make it painless in practice:

  • emacsclient is located automatically at runtime (Homebrew on Apple Silicon or Intel, MacPorts, ~/.local/bin, or /usr/bin), so no path is hard-coded.
  • The app is built and ad-hoc signed locally by the installer, so there is no Gatekeeper quarantine and no pre-built binary to trust.

Requirements

  • macOS
  • Emacs running with a server (M-x server-start, or the Doom/Spacemacs defaults), so emacsclient can reach it
  • A configured mail-user-agent (this is what actually opens the compose buffer)

Installation

Clone the repository and run the installer:

git clone https://git.sr.ht/~bounga/emacs-mailto
cd emacs-mailto
./install.sh

This compiles Emacs Mailto.app into ~/Applications, registers it, and sets it as the default mailto: handler.

Test it:

open "mailto:test@example.com?subject=Hi"

Emacs should come to the front with a compose buffer addressed to test@example.com.

If your Emacs app has a different name, or you want a custom bundle identifier, override them with environment variables:

EMACS_APP="Emacs" BUNDLE_ID="org.nongnu.emacs-mailto" ./install.sh

A caveat about the default mail app

The relay runs as a background agent (LSUIElement), so it does not show up in Mail → Settings → General → “Default email application”. That menu may keep displaying another app; ignore it. To check the real handler, ask LaunchServices directly:

swift -e 'import AppKit; print(NSWorkspace.shared.urlForApplication(toOpen: URL(string:"mailto:x@y.z")!)!.path)'

It should print .../Emacs Mailto.app.

Uninstall

./uninstall.sh

This removes the app and resets the mailto: handler to Apple Mail.

Wrapping up

The whole thing is one small relay that removes a long-standing papercut of running Emacs as a mail client on macOS. It’s MIT licensed. If you hit a rough edge with your particular Emacs build or macOS version, the tracker is on the project page at sr.ht/~bounga/emacs-mailto.

-1:-- emacs-mailto: Open macOS mailto: Links in Emacs (Post Nicolas Cavigneaux)--L0--C0--2026-07-01T22:00:00.000Z

Nicolas Cavigneaux: shpaste: a paste.sr.ht Client for Emacs

What it is

shpaste is a small Emacs client for paste.sr.ht, the pastebin service from SourceHut. It lets me share a snippet without leaving the editor: select some text, run a command, and the paste URL is already in my kill-ring, ready to paste into a chat or an email.

It is built on the SourceHut GraphQL API and does just enough to be useful: create pastes, list them, open them, delete them. Nothing more.

The project lives at sr.ht/~bounga/shpaste, where you’ll find the source, the ticket tracker, and the mailing lists.

What it does

  • Create a paste from the region or from the whole buffer. The resulting URL is pushed to the kill-ring and shown in the echo area.
  • Browse your pastes in a dedicated list buffer, sorted and tabulated.
  • From that buffer: open a paste, copy its URL, delete it, or refresh the list.
  • Choose a visibility per paste: public, unlisted, or private.
  • Point it at a self-hosted SourceHut instance if you don’t use the public one.

The list buffer looks like this:

The shpaste-list buffer

Requirements

  • Emacs 29.1 or later
  • plz (GNU ELPA) and curl

Installation

shpaste is not on MELPA yet; the package is only a few weeks old. MELPA Stable support will come later, built from the version tags. In the meantime, install it straight from the repository.

On vanilla Emacs 29.1+, package-vc-install clones and builds it from source:

(package-vc-install "https://git.sr.ht/~bounga/shpaste")

On Emacs 30+, you can let use-package do the same with :vc:

(use-package shpaste
  :vc (:url "https://git.sr.ht/~bounga/shpaste" :rev :newest))

In Doom Emacs, declare the recipe in packages.el:

(package! shpaste
  :recipe (:type git
           :host nil
           :repo "https://git.sr.ht/~bounga/shpaste"
           :files ("*.el")))

Then configure it in config.el. This is the setup I use, with a paste leader menu and the list-buffer bindings:

(use-package! shpaste
  :commands (shpaste-list
             shpaste-create-from-region
             shpaste-create-from-buffer)
  :init
  (setq shpaste-default-visibility 'unlisted)
  (map! :leader
        (:prefix ("P" . "paste")
         :desc "List my pastes"      "l" #'shpaste-list
         :desc "Paste buffer"        "p" #'shpaste-create-from-buffer
         :desc "Paste region"        "r" #'shpaste-create-from-region))
  :config
  (map! :map shpaste-list-mode-map
        :n "RET" #'shpaste-list-open
        :n "w"   #'shpaste-list-copy-url
        :n "d"   #'shpaste-list-delete
        :n "gr"  #'shpaste-list-refresh))

Getting a token

shpaste authenticates with a SourceHut OAuth2 personal access token. Generate one at meta.sr.ht/oauth2 with the PASTES grant in read-write mode, then store it in auth-source with the host set to your instance — for example in ~/.authinfo.gpg:

machine paste.sr.ht password <YOUR_TOKEN>

There is no shpaste-token variable: the token is only ever read from auth-source. If you want the details of how that lookup works and why I chose it, I wrote a whole post about it: Using auth-source in a Real Emacs Package.

Usage

Command Action
shpaste-create-from-region Create a paste from the region; URL to kill-ring
shpaste-create-from-buffer Create a paste from the whole buffer
shpaste-list Browse your pastes in a list buffer

Inside the shpaste-list buffer:

Key Action
RET Open the paste
w Copy its URL
d Delete it
g Refresh the list

Configuration

Two options, both optional:

  • shpaste-instance (default "paste.sr.ht") — set it to a self-hosted host. It is used both to build the API endpoint and as the auth-source lookup key, so the machine field above must match it.
  • shpaste-default-visibility (default unlisted) — one of public, unlisted, or private.

Wrapping up

That’s the whole tool. shpaste is young (currently 0.1.0) and MIT licensed. If you live in Emacs and use SourceHut, give it a try — and if you hit a rough edge or have an idea, the project page is at sr.ht/~bounga/shpaste.

-1:-- shpaste: a paste.sr.ht Client for Emacs (Post Nicolas Cavigneaux)--L0--C0--2026-07-01T22:00:00.000Z

Nicolas Cavigneaux: Sign Always, Encrypt When Possible: Automatic GPG in mu4e

Introduction

GPG is only useful for the emails you actually remember to protect.

In mu4e, signing and encrypting a message is a manual gesture. Before sending, I hit C-c C-m s p to sign, or C-c C-m c p to sign and encrypt. It works, but it relies on me thinking about it every single time.

I don’t. Nobody does.

So in practice, the manual approach means most messages go out unsigned, and encryption only happens on the rare occasion I stop to remember it exists.

I wanted the opposite default. Something with no friction, that follows a clear policy:

  • Sign every message I send. Always.
  • Encrypt a message when, and only when, I have a public key for every recipient.

The second rule matters. Encryption is all-or-nothing: if a single recipient is missing from my keyring, encrypting would lock them out of their own email. So encryption has to be opportunistic — on when it can be, silently off when it can’t.

Here is how I wired that into mu4e.

The Building Blocks

Three pieces do the work.

MML secure tags. When you compose a message, Emacs doesn’t encrypt anything itself. It inserts an MML tag that tells the message layer what to do at send time. Two functions add those tags for me: mml-secure-message-sign and mml-secure-message-sign-encrypt.

message-send-hook. This hook runs right before a message is sent, when the headers are filled in and the recipients are known. That’s the perfect moment to decide whether to sign or to sign and encrypt.

epg. The Emacs interface to GnuPG. I use it to ask a simple question: do I have a public key for this address?

Two settings configure the signing context:

(setq mml-secure-openpgp-sign-with-sender t
      mml-secure-openpgp-encrypt-to-self t)

sign-with-sender tells the OpenPGP layer to pick the signing key based on the From address, which is exactly what I want when juggling several accounts.

encrypt-to-self is the one that saves you from a classic mistake. When you encrypt a message to someone else’s key, you can no longer read it — not even in your own Sent folder. Encrypting to yourself as well keeps your sent copies readable.

Collecting the Recipients

To decide whether encryption is possible, I first need the full list of people the message is going to.

(defun bounga/message-recipients ()
  "Return a list of all recipients in the message, looking at TO, CC and BCC.
Each recipient is in the format of `mail-extract-address-components'."
  (mapcan (lambda (header)
            (let ((header-value (message-fetch-field header)))
              (and
               header-value
               (mail-extract-address-components header-value t))))
          '("To" "Cc" "Bcc")))

The bounga/ prefix is my personal namespace. Emacs Lisp has a single global namespace, so prefixing your own functions is the convention that keeps them from colliding with built-ins or package code — never define a bare message-recipients that looks like it belongs to message.el. Pick whatever prefix you like; just be consistent.

I walk the To, Cc and Bcc headers, read each one with message-fetch-field, and parse it with mail-extract-address-components. The trailing t asks for all addresses in a header, not just the first, so a To line with five people returns five entries.

Bcc is in the list on purpose. A blind-carbon recipient still has to be able to read the message — if I can’t encrypt to them, I can’t encrypt at all.

mapcan concatenates the per-header results into a single flat list.

Do I Have Everyone’s Key?

Now the actual question: is there a public key in my keyring for every recipient?

(defun bounga/message-all-epg-keys-available-p ()
  "Return non-nil if the pgp keyring has a public key for each recipient."
  (require 'epa)
  (let ((context (epg-make-context epa-protocol)))
    (catch 'break
      (dolist (recipient (bounga/message-recipients))
        (let ((recipient-email (cadr recipient)))
          (when (and recipient-email (not (epg-list-keys context recipient-email)))
            (throw 'break nil))))
      t)))

I create an epg context, then loop over the recipients. For each one I ask epg-list-keys whether the keyring holds a key for that address. The email is the second element of each parsed recipient, hence cadr.

The logic is deliberately strict. The moment I find a single recipient with no key, I throw out of the loop and return nil: encryption is off the table. Only if the loop completes — every recipient covered — does the function return t.

The catch/throw pair is just an early exit. There is no point checking the remaining addresses once one is missing.

Tying It to Send

The decision function is tiny, because the two helpers did the hard part:

(defun bounga/message-sign-encrypt-if-all-keys-available ()
  "Add MML tag to encrypt message when there is a key for each recipient,
sign it otherwise.
Consider adding this function to `message-send-hook' to
systematically send signed / encrypted emails when possible."
  (if (bounga/message-all-epg-keys-available-p)
      (mml-secure-message-sign-encrypt)
    (mml-secure-message-sign)))

If I have everyone’s key, sign and encrypt. Otherwise, just sign. Either way, the message is signed — that part is never optional.

The last step is to run this before every send:

(add-hook 'message-send-hook 'bounga/message-sign-encrypt-if-all-keys-available)

message-send-hook fires once the message is complete and about to leave. By then the recipients are settled, so the keyring check reflects exactly who will receive the message.

The Result

With those few lines in place, I no longer have to remember GPG at all.

Every email I send is signed. Whenever I happen to have public keys for all the recipients (which, over time, covers more and more of my correspondence), the message is encrypted too, without a single keystroke on my part.

A couple of honest caveats.

Signing everything means every recipient can tell I use GPG, and gets a signature attachment whether they care about it or not. For me that’s a feature, not a cost.

And the policy is only as good as my keyring. Encryption depends on having imported the right public keys; nothing here fetches them for me. When a key is missing, the message silently falls back to signing only — which is the safe failure mode, but a silent one. If you’d rather be told, this is the spot to add a notice.

None of that changes the day-to-day feel, though: I write email, I send it, and the right cryptography happens on its own.

BTW, my public PGP key is available here: DB19B66E.

-1:-- Sign Always, Encrypt When Possible: Automatic GPG in mu4e (Post Nicolas Cavigneaux)--L0--C0--2026-07-01T22:00:00.000Z

Meta Redux: Projectile 3.0

Projectile 3.0 is finally out, and it’s a big one - easily the biggest release in years. There’s a nice reason for that: this year Projectile turns 15.1 It all started back in the summer of 2011, simply because I was frustrated that find-file-in-project didn’t work on Windows, and somehow that little anniversary made me want to shake things up and finally tackle a whole pile of changes I’d been putting off for ages. Some new features, some long-overdue spring cleaning, and a few things I’d wanted to remove for the better part of a decade.

What’s dead can’t die

There’s a running joke that Projectile is obsolete now that Emacs ships project.el out of the box. I’ve heard it many times, and I even wrote about it when Projectile turned 10 - it’s obviously hard to compete with a built-in package, as it has a home-field advantage you can never match.

But here’s the thing. If it’s really true that Projectile is dead and everyone has moved on to project.el, then that’s oddly liberating. What’s dead can’t die, right? If there are no users left to upset, I can finally go wild with my wildest ideas and stop worrying about breaking the workflows of Projectile’s (non-existent) users. And that’s more or less the spirit in which I approached 3.0 - I stopped being quite so precious about backwards compatibility and just did what I felt was right for the project.

(Turns out there might still be a user or two out there after all, so do skim the upgrade notes below. Sorry in advance!)

The highlights

There’s a lot in this release - well over a hundred entries in the changelog - but here are the highlights I’m most excited about:

  • projectile-dispatch (bound to s-p m) is a new transient menu that mirrors the command map, so you no longer have to remember every keybinding. It also exposes command modifiers as switches, so you can toggle a regexp search, start a fresh shell, invalidate the cache, or open a result in another window/frame right from the menu.
  • Projectile can now index a project asynchronously, in a background process, so a cold projectile-find-file no longer freezes Emacs while it walks a huge tree. The building blocks are public, so external tools can drive Projectile’s indexing asynchronously as well.
  • There’s a new (optional) projectile-consult integration built on those async data sources. Its finder streams candidates into the minibuffer as the project gets indexed, instead of blocking until indexing is done, while still honouring your VCS and indexing configuration.
  • The various search commands got folded into a single projectile-search, backed by a small, extensible backend registry. It ships grep, ripgrep and ag backends out of the box, and you can register your own (deadgrep, consult-ripgrep, whatever you fancy) in a few lines.
  • The shell, REPL and terminal commands got the same treatment - one projectile-run with shell, eshell, ielm, term, vterm, eat and ghostel backends, plus a seam to plug in your own.
  • The indexers got a thorough going-over - batched stat calls, single directory listings instead of dozens of file-exists-p probes, smarter caching. It all adds up, and it really shows over TRAMP, where every needless round-trip used to hurt. I think that 15 years later I’ve finally solved Projectile’s infamous performance issues over TRAMP!2
  • I finally removed a pile of features that had outlived their usefulness - the single-key Commander (superseded by the dispatch menu), the idle timer, projectile-browse-dirty-projects, and all of the built-in tags support (xref and LSP do this far better nowadays).
  • The minimum supported Emacs is now 28.1. That let me drop a heap of compatibility shims and finally make transient a proper dependency instead of an optional extra.
  • Projectile is a better project.el citizen now - it implements more of the protocol (project name, buffers, ignores), so tools built on top of project.el behave correctly in Projectile-managed projects.
  • I also dropped the legacy ido/ivy/helm completion systems - these days everything rides on the standard completing-read, so Vertico, Consult and friends just work. ido fans are the one group that has to lift a finger: install ido-completing-read+ (the package formerly known as ido-ubiquitous) and turn on ido-ubiquitous-mode, and ido will drive Projectile’s prompts just like before.
  • And there’s a pile of smaller quality-of-life additions - subproject compile/test for multi-module projects (c m c / c m t), a %p placeholder for the project name in command strings, projectile-add-and-switch-project, jumping back to the most recent project, and more.

Upgrading

3.0 is a major release for a reason - there are breaking changes. The minimum Emacs version is now 28.1, several long-deprecated commands and options are gone, and a couple of keybindings moved around. I’ve written a dedicated Upgrading to Projectile 3.0 guide that walks through everything and how to adapt your config. Most setups will keep working untouched, but please give it a read before you upgrade.

Fifteen years later

As I said back when Projectile turned 10 - it’s always hard to compete with built-in packages. Still, I’m genuinely proud that 15 years in, Projectile is still here and, hopefully, still relevant to at least some of you. It remains one of my favourite projects and the one I reach for every single day.

Huge thanks to everyone who has contributed code, reported bugs, written extensions, or simply used Projectile over the years - none of this would exist without you. I really hope 3.0 isn’t the end of the innovation for Projectile, and that it’ll keep surprising and delighting its users for years to come.

Keep hacking!

  1. Give or take - I started Emacs Prelude the same year, so it’s always been a little hard to say which of my open-source projects was truly first. In my mind it’s always been Projectile. 

  2. Famous last words… I know… 

-1:-- Projectile 3.0 (Post Meta Redux)--L0--C0--2026-07-01T17:21:00.000Z

Irreal: Deleting To Trash Redux

Over at Emacs Dwelling there’s a nice post on why you should set Emacs to delete files by moving them to trash. That’s easy to do by setting delete-by-moving-to-trash to t. The post has a story illustrating why you should do that: it’s very easy to accidentally delete a file with Dired and if you do it to a file with changes that haven’t been committed to version control you can lose a few hours work. If you don’t use version control, the story is even worse.

The post seemed to me to be a worthy cautionary tale and I thought it would make a good Irreal post but when I began the writing process by entering a file name for the post, delete-to-trash.org, I discovered that such a file already existed from 2022. Even worse, for my blogging plans, it said everything I was planning to say in this post. Nevertheless, that was 4 years ago and the lesson, it seems, is evergreen so it’s worth making the point again. It costs you nothing and in those rare occasions where errant muscle memory causes you to delete a file, it can save the day. So rather than repeat the story I’ll just suggest that you check out the Emacs Dwelling post or my previous effort.

-1:-- Deleting To Trash Redux (Post Irreal)--L0--C0--2026-07-01T15:13:19.000Z

Meta Redux: Sayid Redux

I have a soft spot for Sayid - it’s one of the most ingenious Clojure tools ever built, and also one of the most neglected. It’s an omniscient debugger: instead of stopping your program at a breakpoint, it quietly records every call to the functions you’ve traced and lets you rummage through the recording afterwards. It’s the kind of thing you demo to people and watch their jaw drop. And it had been sitting practically unmaintained for the better part of a decade.

Here’s the awkward part: that neglect is largely on me. Bill Piel, Sayid’s original author, handed me the keys ages ago, and I’ve been… let’s say a less than exemplary steward. I’d merge the occasional patch to keep the lights on, but until very recently I’d done precious little to actually move the project forward.

The best time to maintain your open-source project was six years ago. The second best time is now.

– Ancient proverb, lightly adapted

So what finally lit a fire under me? Blame CIDER 2.0. I’ve been reworking CIDER’s debugging and tracing story, and at some point I sat down to make the built-in tracer smarter. A few hours in it hit me: nothing I could realistically bolt onto the built-in tracing would come anywhere close to what Sayid already does. So instead of building a worse Sayid, I dusted off the real one, gave it a good scrub, and here we are - Sayid 0.4.

What is Sayid, anyway?

For the uninitiated: Sayid records the arguments, return value, timing and full call tree of the functions you trace, so you can go back and inspect exactly what happened - no breakpoints, no println, no re-running the thing five times.

While we’re on the subject of embarrassing confessions: I’ve been the maintainer of this thing for years and I still have no idea what “Sayid” actually means or what it’s a reference to. If you happen to know, please, put me out of my misery - I’d love to finally get the joke.

Trace a namespace, run your code, and pop open the workspace with C-c s w:

▾ (demo.coins/can-afford? [:quarter :dime :nickel :penny] 45) => true
    (demo.coins/total-cents [:quarter :dime :nickel :penny]) => 45

can-afford? says true, but four coins worth 41 cents shouldn’t cover a 45-cent tab. Something’s off, and the bug lives inside total-cents. This is where Sayid really shines - flip on an inner trace and it records every expression inside the function:

▾ (demo.coins/can-afford? [:quarter :dime :nickel :penny] 45) => true
  ▾ (demo.coins/total-cents [:quarter :dime :nickel :penny]) => 45
    ▾ (apply + (map coin-values coins)) => 45
        (map coin-values coins) => (25 10 5 5)

There it is, staring right at us: (25 10 5 5), when the last value should be 1. A penny is worth five cents in our coin map. We never wrote a single println, and we never had to guess where to put a breakpoint - we just looked at what actually happened.

That’s the pitch, and it’s a great one. So why was such a cool tool gathering dust?

Why an omniscient debugger, in 2026?

Honestly, part of it is that Clojure folks are spoiled. We have the REPL, so the reflex for most of us is to just re-run a form with some tap> or println sprinkled in and eyeball the output. That works right up until it doesn’t - until the bug is three layers deep in a map over a lazy seq, or only shows up on the 400th call, or lives in code you didn’t write and don’t feel like instrumenting by hand.

A traditional stepping debugger stops the world and makes you drive. Sayid does the opposite: it lets the program run to completion and then hands you the entire execution history as something you can navigate at your own pace. tools.trace is in the same spirit, but it dumps text to stdout - Sayid keeps structured data and gives you a query DSL to slice it. That’s a much better fit for how we actually work in Clojure: run it, capture everything, explore the data.

And here’s the thing that made me want to revive it rather than reinvent it - “capture everything as data and explore it later” is exactly the workflow that’s becoming more relevant, not less. Structured execution traces are gold, whether the thing doing the exploring is you, a data-inspection tool like Portal, or an AI assistant trying to understand why your code misbehaved.

Out with the old coordinates

First order of business was dragging the project into the present.

Sayid used to live under com.billpiel/sayid on Clojars, with namespaces like com.billpiel.sayid.core. The new home is clojure-emacs, so the artifact is now published as mx.cider/sayid:

{:user {:plugins [[mx.cider/sayid "0.4.0"]]}}

I also dropped the personal-domain prefix from every namespace - it’s plain sayid.core, sayid.trace, sayid.nrepl-middleware and so on now. The old com.billpiel/sayid coordinates still get the same releases for the time being, so nobody’s dependency breaks overnight, but the future is mx.cider.

Data first, strings later

Here’s the change I’m most excited about. Sayid’s nREPL middleware used to hand the Emacs client a pre-rendered blob of text plus a pile of text properties for colouring. In other words, the server did all the rendering and the client was a dumb terminal. That single decision is a big part of why there was exactly one Sayid client.

So the middleware now speaks data. There’s a family of new ops - sayid-get-workspace-data, sayid-query-data and friends - that return the recorded call tree as honest, navigable data instead of a wall of text:

{"id"       "4793"
 "name"     "demo.coins/can-afford?"
 "args"     ["[:quarter :dime :nickel :penny]" "45"]
 "return"   "true"
 "file"     "demo/coins.clj"
 "line"     12
 "children" [...]}

The structural bits (ids, names, timings, source location, the tree shape) come across as real nested maps and lists you can walk by key. The captured values are pr-str‘d, since an arbitrary Clojure value can’t always round-trip over the wire - but that’s the only place strings sneak in, and it’s exactly where you’d expect them.

The upshot: any editor or tool that speaks nREPL can now fetch a workspace and render it however it likes, and a REPL one-liner or a Portal tap gets you the same data with zero Sayid-specific machinery. The whole thing is written up in doc/nrepl-api.md, so you don’t have to reverse-engineer the wire format from the Emacs client the way you would have before.

A UI that doesn’t feel like 2015

With the data ops in place, I rebuilt the Emacs UI on top of them. The workspace and the “what’s traced” views are now proper foldable trees, built on CIDER’s new cider-tree-view. You get real folding (TAB), navigation (n/p), jump-to-source (RET), and - my favourite - c i hands the actual captured value straight to CIDER’s inspector, so you can drill into an argument or a return value as a live, navigable object rather than squinting at its printed form.

There’s also a query layer wired into the tree: f narrows to every recorded call of the function at point, i focuses a single call and its subtree. On a big trace that’s the difference between “wall of text” and “actually finding the thing”.

The best part is that the client is now smaller, not bigger - all the tree rendering, folding and value inspection are handled by mature components instead of bespoke code painting text properties by hand. That’s the payoff of moving rendering to the client: the server ships data, and the client is free to be as fancy or as plain as it wants.

Wait, you broke everything?

Backwards compatibility is a promise you make to the users you have. Sayid had exactly one, and reader, I am that user.

– Me, rationalising

Yeah, I did. I went pretty wild with the breaking changes this time around - new artifact coordinates, new namespaces, a reworked nREPL API, a bumped minimum CIDER version. Normally I’d bend over backwards to keep old clients working, but here I made a deliberate call: as far as I can tell, the bundled Emacs client was the only client Sayid ever had. Dancing around imaginary third parties to preserve compatibility nobody was relying on would have just made the project harder to adopt and harder to maintain.

So I opted for sweeping changes that leave Sayid in a much better place to build on, rather than a museum of backwards-compatible cruft. If it turns out I was wrong and you were quietly depending on the old coordinates - I’m sorry, and do let me know, because that’s genuinely useful information.

Take it for a spin

That’s the gist of it. Sayid is alive again, it’s leaner, it speaks data, and it has a UI I’m not embarrassed to demo. What I’d love now is for more people to actually use it and tell me whether the new direction resonates.

So please - [mx.cider/sayid "0.4.0"], trace something gnarly, and pop open the tree. Then head over to the issue tracker and tell me what you think: what feels great, what feels rough, what’s missing. I have ideas for where to take it next (bounding the recording so you can safely trace a whole namespace under a test suite is high on the list), but I’d rather steer by what people actually want out of it.

Big thanks to Bill Piel for building such a wonderful tool in the first place - I’m merely standing on the shoulders of a giant here. And thanks in advance to everyone willing to kick the tyres on the revival!

That’s all from me for now. Keep hacking!

P.S. One more thing…

Well, that didn’t take long. Remember that “high on the list” bit a few paragraphs up - bounding the recording so you can safely trace a whole namespace under a test suite? Turns out I couldn’t leave it alone. A short burst of small improvements after this post went up, and it’s done: Sayid 0.5.

That was the thing that made Sayid feel like a toy - point it at a real workload and it would cheerfully eat your whole heap and fall over. The recording now has a set of tunable bounds instead: a cap on how many top-level calls it keeps, a depth limit, 1-in-N sampling for hot paths, a per-function cap, and a keep-only-the-last-N mode for when what you care about is whatever happened right before things went sideways. Fat and infinite values no longer hang the data ops either, and the traced-functions view got its enable/disable/remove actions back.

The upshot for you: you can finally point Sayid at real code under real load without babysitting it. Same ask as before - give it a spin and tell me how it feels.

-1:-- Sayid Redux (Post Meta Redux)--L0--C0--2026-07-01T13:01:00.000Z

Chris Maiorana: God mode notes with Denote and Consult Notes

Consult notes is a package that hooks into your note-taking system. It works particularly well with Denote, picking up your notes directory variable automatically.

My interest in having a note system comes down to whether it helps my writing. A writer needs different kinds of notes: reading notes, notes on craft, notes on etymology, and random ideas. You’ll notice I’ve built that into the config below.

I’ve touched on the topic of note-taking in Emacs a few times, in posts on “making everything a note” and on the “undergrad” style of note-taking. I’ve often argued that a full dedicated notes package was unnecessary when you could simply create files and link them together.

But this “consult notes” package made me want to give Denote another try and see how it feels. So far, I like it. I’m going to test drive this config for a few weeks and report back on how it’s going.

Denote config

(use-package denote
  :ensure t
  :bind (("C-c n n" . denote)                ; new note
         ("C-c n f" . denote-open-or-create) ; find/create by title
         ("C-c n l" . denote-link)           ; insert a link to another note
         ("C-c n b" . denote-backlinks))     ; show what links here
  :custom
  ;; Names the destination for notes in the file system
  (denote-directory (expand-file-name "~/notes/"))
  ;; Default to Org type of files
  (denote-file-type 'org)
  ;; Keywords you reach for more often
  (denote-known-keywords '("craft" "etymology" "draft" "idea"))
  (denote-prompts '(title keywords))
  :config
  (denote-rename-buffer-mode 1))            ; show note title in the buffer name, nice

Consult

(use-package consult
  :ensure t
  :bind (("M-s l" . consult-line)            ; search within current draft
         ("M-s L" . consult-line-multi)      ; search across all open buffers
         ("M-s r" . consult-ripgrep)         ; search the whole writing directory
         ("M-s o" . consult-outline)         ; jump between org/markdown headings
         ("M-s b" . consult-buffer)
         ("M-s k" . consult-keep-lines)      ; isolate lines matching a pattern
         ("M-s f" . consult-flush-lines))    ; strip lines matching a pattern
  :custom
  ;; Small delay before previewing (keeps a big file from feeling jumpy)
  (consult-preview-key '(:debounce 0.2 any)))

Consult notes

(use-package consult-notes
  :ensure t
  :commands (consult-notes consult-notes-search-in-all-notes)
  :bind (("M-s n" . consult-notes)                       ; search notes by title
         ("M-s N" . consult-notes-search-in-all-notes))  ; full-text across notes
  :config
  ;; Reads denote-directory automatically (no second path to maintain)
  (consult-notes-denote-mode))

The post God mode notes with Denote and Consult Notes appeared first on Chris Maiorana.

-1:-- God mode notes with Denote and Consult Notes (Post Chris Maiorana)--L0--C0--2026-07-01T01:00:28.000Z

Raymond Zeitler: Create Date Books, Planners and Calendars with Emacs

Diary was my pick for this month's Emacs Carnival. But in fact it's part of a larger set of functions under the calendar group. Another set of functions allows you to create pocket date books, desk planners and wall calendars if you have access to a printer. The function names begin with cal-tex-cursor and cal-html-cursor, and they can be found in cal-tex.el and cal-html.el, respectively.

The "tex" and "html" in the function names describe the output formats, LaTeX and, well, HTML; "cursor" describes one way of defining their inputs (from the position of the cursor in the calendar buffer). They're further subdivided into layouts such as day, week, month and year. My favorite is cal-tex-cursor-week-iso.

Open the calendar (M-x calendar). Observe that the cursor is at today. Now invoke cal-html-cursor-month and accept the default at the prompt. The function will create a file named YYYY-MM.html. The variable cal-html-directory provides the cal-html functions with the path name. The HTML file is placed in a subdirectory named YYYY. In my case, cal-html-directory is set to "~/public_html." As I invoke it today, the subdirectory / filename is 2026/2026-06.html.

The calendar can pull information from your diary file(s), the holidays defined in holidays.el and in the holiday-other-holidays variable. If you have %%(diary-sunrise-sunset) and %%(diary-lunar-phases) you'll get the times of sunrise and sunset at your coordinates, and the dates and times of the four lunar phases, assuming that cal-tex-diary is non-nil. (I also have cal-tex-24 non-nil.)

I suggested cal-html-cursor-month because its output is easily viewed (rendered) with a web browser. But I prefer the weekly calendar produced by cal-tex-cursor-week-iso, which outputs in LaTeX.

I've invoked cal-tex-cursor-week-iso with the cursor on June 16. The output is shown in Figure 1 after rendering to PDF with pdflatex. (Note that the screenshot is closely cropped.) There's a lot included here.

The Rendered LaTeX for Week 25 of 2026

First, note that our Third Tuesday of the Month Club meeting (used as an example in a previous post) is shown. This comes from a diary entry; unfortunately org file events are ignored. (The agenda for this week is shown in Figure 2; note the event in red does not appear in the weekly calendar. This is another reason I favor recording appointments in the diary.)

Two other diary entries also are included: a record of when the kitchen cabinets were painted, plus a breakfast meeting with Howard.

Then you'll find two holidays, Islamic New Year and Father's Day. And because I have diary-lunar-phases in my diary, a moon phase also shows up. (I commented out the diary-sunrise-sunset statement because it adds the information to every day of the week, which makes the weekly planner look too cluttered.) Last but not least the Summer Solstice is included as a Sunday holiday along with the time it occurs.

The first line of each day block also shows (right justified) the day number (of the year) and the number of days remaining in the year. The Sunday block is quite crowded; the first line is wrapped, so the day number / days remaining field appears on the next line.

Sadly, any links in the diary that worked in Org Agenda do not work in the LaTeX output. You'll see instead the full syntax of the link, [URL][description] ].

I created an improved version of cal-tex-cursor-week-iso. Here are some of the changes:

  • inhibit sunrise/sunset data for all but the first day
  • switch to sans-serif font
  • color birthdays and anniversaries in blue
  • replace the non-ASCII characters (used for Bahai holidays)
  • offset left or right to allow for the binding of a year's worth of pages
  • more?

I uploaded it to Codeberg, but I need to clean it up a bit before I feel comfortable promoting it.

If you position the cursor on the last day of the year and invoke cal-tex-cursor-week-iso with a prefix argument of 53, you can create the pages for a personalized weekly planner for next year! Try it out!

-1:-- Create Date Books, Planners and Calendars with Emacs (Post Raymond Zeitler)--L0--C0--2026-06-30T21:05:58.956Z

James Dyer: That Moment Dired Eats Your File and why Trash Saves You

I was working on my diff-minimap package, deep in the weeds of fringe indicators and search highlights, and I did the thing. You know the thing. I was in Dired, accidentally, for some reason hit D to delete a filename (probably some malfunctioning muscle memory) and it was gone!

20260515110240-emacs--That-Moment-Dired-Eats-Your-File-and-why-trash-saves-you.jpg

Except it was my own source file. diff-minimap.el. The one I had been hacking on for the last hour. The one with all those uncommitted changes.

Now, in my head, I have a trash setup. I am sure I configured that somewhere, probably years ago, in some init file on some machine. But the machine I was sitting at? Nope. Emacs just did a straight delete, no safety net, no way back.

The file was tracked in git, so I could git checkout HEAD -- diff-minimap.el to get the committed version back. But the uncommitted work - the inline diff preview, the vc-diff positioning, the side-window hunk viewer - that was gone.

Emacs has a built-in variable, delete-by-moving-to-trash. When it is set to t, Dired (and other delete operations) move files to the system trash instead of permanently deleting them. On Linux that is ~/.local/share/Trash/files/, on macOS it is ~/.Trash/, on Windows it is the Recycle Bin.

It is one of those settings that seems obvious in hindsight but is easy to overlook because Emacs defaults it to nil. The reasoning is probably that Emacs is a serious tool for serious people who know what they are doing, so deleting means deleting?!

So I added it to my config:

(setq delete-by-moving-to-trash t)

That is it. One line. From now on, D in Dired moves to trash instead of deleting permanently and I will not have that sinking feeling again!

Anyway, back to diff-minimap.

-1:-- That Moment Dired Eats Your File and why Trash Saves You (Post James Dyer)--L0--C0--2026-06-30T09:17:00.000Z

Meta Redux: CIDER 2.0 is Brewing…

Normally I write these posts after a CIDER release is out, with the smug satisfaction of someone who has already tagged the version and updated the changelog. Today I’m flipping the script a little: the release isn’t out yet, but I’m too excited to keep quiet about it.

Here’s the thing. The next CIDER release was supposed to be 1.23. But as I kept piling change upon change, it slowly dawned on me that calling this “1.23” would be doing it a disservice. So I’m now strongly considering shipping it as CIDER 2.0 instead.

A different kind of release

The last few CIDER releases were on the conservative side - lots of important internal work, but relatively little that you’d actually notice day to day. This one is the exact opposite. It’s stuffed with user-visible changes, many of which have been sitting on the issue tracker and in the back of my mind for literally years.

Why now? Two reasons.

First, all that boring groundwork paid off. Some of you noticed I spent a lot of time recently backfilling tests and generally enriching the test suite. That wasn’t busywork - it was me building a safety net, so that finally making sweeping changes would be safe and pleasant instead of terrifying. Mission accomplished: I’ve been refactoring with abandon and sleeping just fine.

Second - and I know this is the controversial part - AI agents have been a genuinely great help. Not for writing CIDER for me (the design taste is still very much mine, thank you), but for quickly prototyping my half-formed ideas in different ways so I can see and feel them before committing to the approach I like best. Turns out “show me, don’t tell me” works wonders when the thing showing you can knock out a prototype in minutes. Boo me if you must, but when you’re the maintainer of so many projects and you have only so little time to spend on all of them that really makes a difference.

There’s a third ingredient worth mentioning: I’d been hammocking on most of this for months, quietly turning the designs over in the back of my mind while I was wrapping up CIDER 1.22. So while 1.22 itself took the better part of four months (and still shipped later than I’d have liked), the release right after it has been snapping together in a couple of weeks - the code was the easy part once the thinking was done. It helps that a lot of these changes close issues that had been gathering dust for years; the ClojureScript macroexpansion bug (#2099) was filed all the way back in 2017. Nothing beats hammock-driven development - even if it’s decidedly not the fastest way to ship anything.

OK, enough preamble. Let’s look at the goodies.

Keymaps you can actually discover

CIDER has a lot of commands, and historically the only way to find them was to memorize cryptic key chords or grep the manual. No more. Every command prefix now pops a transient menu (the same UI magit made famous), and there’s a top-level cider-menu that ties them all together:

Evaluate             Code              REPL & session           Diagnostics
 e Eval...            t Test...         j Insert into REPL...    r Trace...
 m Macroexpand...     n Namespace...    x Jack-in / connect...   p Profile...
 d Documentation...   w References...                            l Log...

Crucially, this isn’t a modal speed bump - your old muscle memory still fires instantly. C-c C-d C-d runs cider-doc as fast as ever; the menu only shows up if you pause after the prefix, wondering “wait, what else is in here?”

One tree view to rule them all

A bunch of CIDER’s browsers grew up independently and looked it. They now share a single, foldable tree-view widget - the namespace browser, the spec browser, and the new call-graph browsers all behave consistently (TAB to fold, n/p to move around). For example, exploring who calls a function:

who-calls clojure.core/reduce
  ▾ my.app.core/sum
      ▸ my.app.api/handler
  ▸ my.app.util/total

Expand a node and CIDER lazily fetches the next level. It’s a much nicer way to spelunk through a codebase than a flat list of strings.

Find usages, batteries included

Speaking of call graphs - CIDER now ships with genuinely capable cross-reference functionality out of the box. cider-who-calls/cider-who-is-called for the call graph, cider-who-implements for protocols and multimethods, plus xref-find-references (M-?) that searches your source and therefore finds usages even in code you haven’t loaded into the REPL yet.

The practical upshot: if you were reaching for clj-refactor.el or clojure-lsp mostly to find usages, you probably don’t need them anymore. (If you were using them for other things, carry on - we’re friends, not rivals.)

Much of the inspiration here came straight from swank-clojure and SLIME itself, which have offered this kind of cross-referencing for ages. Sometimes nothing beats revisiting the classics.

The debugging toolbox got a serious polish

This is the part I’m most happy about. Pretty much every “what is my code actually doing?” tool got some love.

The macro tooling is the headliner. The macroexpansion buffer finally grew a header line that shows the active expander and options, cycles namespace display and metadata in place, pulses the freshly-expanded form, and - at long last - says something useful when you point it at a special form or an unresolved symbol, instead of silently shrugging. Better still, there’s a brand new inline stepper (cider-macrostep, a Clojure spin on the venerable macrostep package) that expands macros right where they sit, one step at a time. Expandable sub-forms get underlined so you can hop between them with n/p, and every gensym is painted its own color, so you can finally follow where that g__1234 ends up. I did not expect macro debugging to be fun, and yet here we are.

Tracing got the same treatment. Traced calls no longer smear themselves all over your REPL output; they stream into a dedicated, foldable *cider-trace* buffer instead:

*cider-trace*
▾ (my.app/process {:id 7})
    ▸ (my.app/validate {:id 7})  => true
    ▸ (my.app/persist {:id 7})   => {:id 7, :saved? true}
  => {:id 7, :saved? true}

And because “wait, what did I even trace?” is a question I ask myself constantly, cider-list-traced and cider-untrace-all are now a keystroke away.

Enlighten (you remember it, right?) picked up some manners too. You can light up a single form with cider-enlighten-defun-at-point without flipping the global mode, and cider-enlighten-stop makes the whole thing vanish at once, instead of making you re-evaluate everything in penance. And for the tap> crowd, cider-tap opens a buffer that streams whatever you send to tap> and lets you crack any value open in the inspector with RET. It’s println debugging, minus the println guilt.

ClojureScript gets some love too

ClojureScript is always the trickier sibling, but it got meaningful improvements this round:

  • You can now run ClojureScript tests with the regular test commands (including async cljs.test tests) instead of CIDER refusing to play along.
  • Macroexpansion of user-defined cljs macros finally works (it used to silently echo the form back unexpanded - a bug that had been open since forever).
  • And when you invoke a Clojure-only command under a cljs REPL, CIDER now tells you so clearly, rather than failing in some confusing way.

This was another area where AI tooling was quite helpful to me, as I’ve rarely used ClojureScript in the past, but I still managed to figure out how to finally solve those problems that have been pain points for CIDER’s users for as long as we’ve had ClojureScript support.

On a related note, I’ve also been chasing down a few small bugs in Piggieback (the middleware that powers most of the cljs REPLs CIDER talks to) - have a look at the recent releases if you’re curious. And I’ve been idly pondering some form of “native” shadow-cljs support down the road. That last one is very much TBD, so don’t hold me to it - but the ClojureScript story keeps inching forward.

A pile of quality-of-life touches

It wouldn’t be a CIDER release without a long tail of small comforts. A few that I keep bumping into and grinning at:

  • Eldoc is asynchronous now, so dragging the cursor around no longer blocks Emacs on an nREPL round-trip. Buttery smooth.
  • A new cider-mode lighter (with an optional fringe marker) flags when the current buffer’s namespace isn’t loaded into the REPL, or has gone stale because you edited an already-evaluated form. That retires a whole genre of “why isn’t my change taking effect?” head-scratching.
  • cider-doc can pull ClojureDocs examples right into the doc buffer.
  • Sending a form into your namespace’s (comment ...) block is a one-keystroke affair now (cider-send-to-comment), and cider-jump-to-comment teleports you back there.
  • Stuck in .cljc land? You can pin where a buffer’s evaluations go - clj, cljs, or both - with cider-set-eval-destination and friends.
  • The REPL banner is slimmer and far less shouty; the getting-started spiel now lives in a summonable reference card (C-c C-h).
  • The cheatsheet finally learned about all the functions Clojure has grown since 1.11.

I’m probably still forgetting a dozen things - the changelog is genuinely enormous this time around.

Please go play with it!

Unfortunately no one can be told what CIDER 2.0 is – you have to experience it yourselves…

– Clorpheus

Here’s where you come in. All of this is already available in the snapshot release of CIDER on MELPA. I’d love for you to install it, kick the tires, and - especially - tell me how the various UX changes feel. Discoverable? Annoying? Joyful? I genuinely want to know before I carve any of it in stone.

If no serious problems surface, I plan to cut the real release fairly soon - think a week or two. So now’s the perfect time to influence it.

Epilogue

I keep hearing that the Clojure community isn’t innovating much these days. I hope CIDER 2.0 goes a small way towards convincing the doubters that we’re not quite done yet. The best is always yet to come - for both Clojure and Emacs.

Now let’s go forth and brew some (magic) CIDER together! Keep brewing!

-1:-- CIDER 2.0 is Brewing… (Post Meta Redux)--L0--C0--2026-06-30T09:00:00.000Z

tusharhero: Underappreciated builtin: Grand Unified Debugger

~1089 words. ~5 minutes.

Or as I would like to call it, GLORIOUS Unified Debugger.

This is my submission for June's Emacs Carnival, Underappreciated Emacs built-ins.

You can find detailed information in the excellent documentation at (info "(emacs) Debuggers").

Multi debugger support

I mostly use its GDB Graphical Interface. But it supports multiple debuggers such as,

  • lldb (LLVM debugger)
  • perldb (Perl debugger)
  • jdb (Java debugger)
  • pdb (Python debugger)
  • guiler (Guile!)
  • dbx (Debugger with support for C, C++, and so.)
  • xdb (Debugger for MS Windows (?))
  • sdb (System Debugger)

The GDB interface

The manual has a entire section of the special GDB GUI interface. Which I will illustrate now.

This is our demo source file: fibo.c,

 #include  

 int
 fibo ( int  n)
{
   if (n < 2)
     return 1;
   else
     return fibo (n - 1) + fibo (n - 2);
}

 int
 main ( void)
{
   for ( int  i = 0; i < 5; i++)
    printf ( "%d: %d\n", i, fibo(i));
}

Firstly, we compile via,

gcc -g fibo.c -o fibo

(The -g flag is necessary for debugging with GDB.)

Now that we have a compiled program, we can try executing it.

./fibo
0: 1
1: 1
2: 2
3: 3
4: 5

Now we can proceed with starting gdb gud in Emacs. To do so, we type M-x gdb RET fibo RET, and this creates a buffer called *gud-fibo*.

Current directory is /home/tusharhero/Documents/c-scratch/
GNU gdb (GDB) 17.2
Copyright (C) 2025 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
    .

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from fibo...
 (gdb) 

Though we won't be directly using this CLI interface, you can use it if you want to.

Let's set some breakpoints.

GDB breakpoints by clicking on the fringe.

Yup, you can just click on the fringe (the place left to the text), and automatically set up the breakpoints, which show up like so in *gud-fibo*.

Breakpoint 1 at 0x401138: file fibo.c, line 7.
Breakpoint 2 at 0x40113f: file fibo.c, line 9.
Breakpoint 3 at 0x401174: file fibo.c, line 16.
 (gdb)  

Now, we will start debugging by running the program. tool-bar-run-button.jpg

These buttons: Run, Next Line, Step Line, Up Stack, and Down Stack are part of the tool bar.

And immediately it stops at the first breakpoint it encounters, (breakpoint 3). The white arrow represents the current line that is being executed.

fringe-current-line-arrow.jpg

Now we can step into fibo function by using Step Line.

tool-bar-step-line.jpg

Notice that we also have a new button for Continue now, which continues until the next break point is hit. So, let us turn off the breakpoint in the for loop. For this we will use the dedicated breakpoints management window.

Now let's just spam the continue button a bunch of times.

And since we are calling printf in the for loop, there is a pop out dedicated window for I/O. (Notice that it is not just for output).

Well what if I want to see what the value of n is currently? There are actually a bunch of ways to look at that.

Here you just kinda put your cursor over the thing you want to get the value of … and you just get it? :)

The other way to use to the locals window,

But then who wants to keep track of this manually? Let's just watch the variable, so that we get update whenever it changes.

Closing

I would like to cover many other features, but I unfortunately don't have the time to get through all of them, maybe I will write a sort-of sequel covering more features and other debuggers supported by GUD.

-1:-- Underappreciated builtin: Grand Unified Debugger (Post tusharhero)--L0--C0--2026-06-29T18:30:00.000Z

Sacha Chua: 2026-06-29 Emacs news

This week, lots of people were talking about FSF's policy of not accepting LLM contributions to Emacs core (see the last two items in the AI category). Comments seem generally supportive of FSF's caution.

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!

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- 2026-06-29 Emacs news (Post Sacha Chua)--L0--C0--2026-06-29T17:04:18.000Z

Raymond Zeitler: Emacs disabled Commands

It's possible to configure Emacs to prompt the user before calling a function, thereby "disabling" a function. The function isn't totally inaccessible -- Emacs will let you run it if you want it to. I chose to do this with scroll-left, scroll-right, scroll-up-command and scroll-down-command as explained in the previous post.

Most keypresses call self-insert-command, which is itself a function. So what would happen if self-insert-command were disabled?

It turns out to be not as debilitating as you'd think -- only the alpha-numeric and punctuation keys are affected. When a modifier is used with a key (M-x, for example), there is no interruption. Likewise, the TAB key is allowed to pass, which makes completion easier.

When Emacs inhibits self-insert-command, it prevents the keypress from having its intended effect. It will say that the command is disabled because many users find it confusing. Then you'll have the options of:

  • (n) Not running the command
  • (y) Run the command and don't ask again
  • SPC Run the command just one time to try it
  • (!) Enable the command and all other disabled commands

The n, y, SPC and ! keys are not inhibited at this point, so the user doesn't get trapped in a set of recursive prompts.

So to invoke org-agenda, I'd press M-x o. Then I'd press SPC to enable the self-insert-command for "o." Then I'd press r followed by SPC, and so on. My keypresses are M-x o r g - a g TAB ENTER.

Thus, adding (put 'self-insert-command 'disabled t) to a cube-mate's init file is a harmless prank, something to try on April Fool's Day.

-1:-- Emacs disabled Commands (Post Raymond Zeitler)--L0--C0--2026-06-29T02:40:43.489Z

Protesilaos: Emacs: fontaine version 3.1.0

Fontaine allows the user to define detailed font configurations and set them on demand. For example, one can have a regular-editing preset and another for presentation-mode (these are arbitrary, user-defined symbols): the former uses small fonts which are optimised for writing, while the latter applies typefaces that are pleasant to read at comfortable point sizes.

Below are the release notes.


Version 3.1.0 on 2026-06-29

This is a small release that includes internal refinements as well as two user-facing changes:

  1. The user option fontaine-presets accepts an optional :line-spacing entry, which corresponds to the line-spacing variable. As of Emacs version 31, line-spacing can be bound to a cons cell to set the space above and below. Fontaine now handles this as intended.

  2. By default, changing the font size has the effect of resizing the frame. This is because of the original value of the variable frame-inhibit-implied-resize. Fontaine is now designed to always inhibit frame resizing, regardless of frame-inhibit-implied-resize.

-1:-- Emacs: fontaine version 3.1.0 (Post Protesilaos)--L0--C0--2026-06-29T00:00:00.000Z

Monadic Sheep: Canvas patch: we need testers!

by Tushar
2026 June 29

The Canvas patch is almost done. Except that we need more testing, specifically for its MS Windows port. (Though testers on other operating systems like GNU/Linux and MacOS are also welcome.)

Since we don't have any computers running MS Windows, we are not sure if the code is actually correct.

Please follow the following instructions for testing it on MS Windows. You can discuss the results or ask us questions at MonadicSheep Emacs Fork's Issue tracker, #phi-mu-lambda on Libera IRC (open webchat in browser), or on the fediverse (tag the post with #emacs).

1. Building GNU Emacs on MS Windows

More detailed information is available here.

  1. Install MSYS2 by following the official instructions mentioned there.
  2. Open a MSYS2 UCRT64 session terminal.
  3. In the bash prompt, run the following commands to get the canvas patch source code.

    pacman -Sy git
    git clone https://codeberg.org/MonadicSheep/emacs
    
  4. Now, change directory to emacs source checkout, by running the following command.

    cd emacs/
    
  5. Install dependencies,

    pacman -Sy --needed base-devel autoconf \
    mingw-w64-ucrt-x86_64-toolchain \
    mingw-w64-ucrt-x86_64-xpm-nox \
    mingw-w64-ucrt-x86_64-gmp \
    mingw-w64-ucrt-x86_64-gnutls \
    mingw-w64-ucrt-x86_64-libtiff \
    mingw-w64-ucrt-x86_64-giflib \
    mingw-w64-ucrt-x86_64-libpng \
    mingw-w64-ucrt-x86_64-libjpeg-turbo \
    mingw-w64-ucrt-x86_64-librsvg \
    mingw-w64-ucrt-x86_64-libwebp \
    mingw-w64-ucrt-x86_64-lcms2 \
    mingw-w64-ucrt-x86_64-libxml2 \
    mingw-w64-ucrt-x86_64-zlib \
    mingw-w64-ucrt-x86_64-harfbuzz \
    mingw-w64-ucrt-x86_64-libgccjit \
    mingw-w64-ucrt-x86_64-sqlite3 \
    mingw-w64-ucrt-x86_64-libtree-sitter
    
  6. Now, run the following command to build Emacs.

    make
    

    This may take a while depending on your hardware.

  7. Once that is done, you should be able to start emacs with the following command.

    ./src/emacs
    

2. Testing

Evaluate the following in *scratch* (Using C-c C-c keybinding).

(defun make-rect (width height pixel)
  (make-vector (* width height) pixel))

(setq rect-canvas-vec (make-rect 250 250 #xFFFF0000))
(setq rect-canvas `(image :type canvas
                          :data-width 250
                          :data-height 250
                          :data ,rect-canvas-vec))
(insert (propertize "#" 'display rect-canvas))

(defvar rect-canvas-timer nil)
(let ((i 0))
  (setq rect-canvas-timer
        (run-with-timer
         0 0.016
         (lambda ()
           (if (< i (* 20 250))
               (progn
                 (aset rect-canvas-vec (+ (* 115 250) i) #xFF0000FF)
                 (canvas-refresh rect-canvas t)
                 (setq i (1+ i)))
             (cancel-timer rect-canvas-timer))))))

You should be able to see something like this.

-1:-- Canvas patch: we need testers! (Post Monadic Sheep)--L0--C0--2026-06-29T00:00:00.000Z

Irreal: Linking A Diary Event To An Org Heading

Ray Zeitler has a useful post that describes how to solve a niche problem involving Emacs diary and Org mode. The problem is you have an event scheduled in your diary and you want access to an Org heading for clocking and note taking.

It turns out that it’s pretty easy to arrange that. Take a look at Zeitler’s post to see the exact recipe.

-1:-- Linking A Diary Event To An Org Heading (Post Irreal)--L0--C0--2026-06-28T14:33:03.000Z

Protesilaos: Emacs: new ‘doric-tiger’ and ‘doric-lion’ for the ‘doric-themes’

I just added two new themes to my minimalist doric-themes package. doric-tiger is a light theme, while doric-lion is dark. Both use orange and yellow colours. Below are sample screenshots.

doric-tiger

doric-tiger theme sample

doric-tiger theme sample

doric-tiger theme sample

doric-tiger theme sample

doric-lion

doric-lion theme sample

doric-lion theme sample

doric-lion theme sample

doric-lion theme sample

Part of development

The new themes will be available in version 1.2.0. I think they already are well defined and may not need furter changes. Though I will continue to test—and possibly tweak—them over the course of the next two weeks or so.

Sources

The Doric themes use few colours and will appear monochromatic in many contexts. They are my most minimalist themes. Styles involve the careful use of typographic features and subtleties in colour gradients to establish a consistent rhythm.

If you want maximalist themes in terms of colour, check my ef-themes package. For something in-between, which I would consider the best “default theme” for a text editor, opt for my modus-themes.

-1:-- Emacs: new ‘doric-tiger’ and ‘doric-lion’ for the ‘doric-themes’ (Post Protesilaos)--L0--C0--2026-06-28T00:00:00.000Z

Jakub Nowak: New Package: Gomuks.el

For the past few months I've been on and off working on a new package for Emacs, which is my first attempt at a proper major mode "application".

It is a wrapper/frontend for Gomuks, which is a Matrix client written in Go. Typically, Gomuks runs a web app client for Matrix, which connects to the Gomuks backend via a websocket. My wrapper, gomuks.el, connects to the websocket and acts as a full (well, rudimentary) Matrix client inside Emacs.

Why make this?

Matrix/Element is the single chat platform I use, and not having it be in Emacs has been a pain for ages, especially because the official Element client keeps getting worse (subjectively).

I would rather use a client from the terminal, and previously Gomuks was a solely TUI app that did that for me. However, they've moved away from the old TUI to a new (and better architecture), and now the TUI breaks on home-servers for me. So it was essentially the straw the broke the camels back in terms of forcing me to fix something myself.

If you're across the wider Emacs package world, you may have heard of ement.el, and are probably wondering why I can't use it. The simple answer is that ement.el doesn't handle encryption natively, instead requiring pantalaimon. Now, I've already had mixed success with pantalaimon in the past (which is why I was using the Gomuks TUI), but the repo for it has been archived as of earlier this year so I don't think it's wise to rely on it. As a side note, I'm not actually sure why it was archived; I assume the matrix org just didn't want to maintain it anymore, which is fair enough, but I would have liked some explanation on the readme at least. Anyway, Gomuks already handles encryption, and so gomuks.el does as well.

What state is it in?

This client is VERY basic. I would say it's usable, in the sense that I will use it and add features to it as I need them, but it is far from the quality of ement.el or the myriad of IRC clients in Emacs.

Basically all I have at the moment is live state synchronisation (without handling edit/delete events) and sending messages (only textual). The GUI is horrible, because I don't know how to use EWOC so I'm not, and it smells of programmer design. If you intend to use this, expect lots of unexpected jank.

That being said, I think the code is fairly high quality and well documented, so it should be fairly easy to modify and understand.

Contributing

I'm self-hosting this on my own git server, so there's no easy fork->PR workflow, and I'm not opening public registration. Sorry.

I'm happy to accept contributions though, either in the form of git patches or snippets sent to my email at birdt_@cyan.sh.

-1:-- New Package: Gomuks.el (Post Jakub Nowak)--L0--C0--2026-06-28T00:00:00.000Z

Raymond Zeitler: Preventing Errant Navigation

I often press C-<end> or C-<next> accidentally. It's annoying and disorientating because I lose my place in the document as well as my train of thought. I think of them (a bit dramatically) as "Deadly Navigation Keys." But I discovered two valuable Emacs features to make it easy to recover from them (or prevent them) as a result.

The first valuable feature is that C-<end> (which is bound to end-of-buffer) sets the mark at the cursor location prior to setting point to the end of the buffer. (I only just realized this after seeing the message "Mark set" in the minibuffer about fifty times.)

There's an almost-applicable saying, "What goes up, must come down," except in this case I'd say, "What gets pushed, may be popped." A simple invocation of C-h a pop.+mark revealed the pop-to-mark-command. And then, happily, M-x pop-to-mark-command returned me to exactly where I left off. It was the undo feature that Emacs had been missing -- a sort of undo-cursor-motion.

Recovering from beginning-of-buffer is just as easy. It, too, pushes mark.

There are, however, other Deadly Navigation Keys that do not push mark. In fact, they don't even change the position of the cursor. Instead, they scroll the buffer. I'm referring to scroll-left and scroll-right . These are disabled by default. However, I figured that only babies needed to shielded from such commands, so I enabled them.

Holy cow was that a mistake! On my laptop keyboard, the page down and right arrow keys are crammed very close together. I often press C-<right> to advance one word, but that's only 1 cm away from C-<next>, which is bound to scroll-left. And scroll-left alters the view of the buffer such that all the content I been work on shifts off screen. The first time it happened, I freaked, of course, and then I checked the status bar to see if the buffer had been modified. (It hadn't.) So I closed Emacs entirely and restarted it. Then I re-disabled scroll-left and scroll-right.

But then it occurred to me to disable end-of-buffer and beginning-of-buffer, too, because I rarely use those functions, and if I really need to call them, all I'd have to do is press the spacebar after each C-<end>. Disabling a command is a convenient way of seeking confirmation without making the user type "yes" (or "y").

What commands do you have disabled? And why?

-1:-- Preventing Errant Navigation (Post Raymond Zeitler)--L0--C0--2026-06-27T22:17:17.917Z

Irreal: Toggling Ghostel

Randy Ridenour is living proof that you don’t have to be a young nerd to master Emacs and Elisp. Ridenour, a professor of Philosophy, has shown repeatedly that it’s possible for a nontechnical person to become sufficiently proficient with Emacs and Elisp to bend the editor to his will and make it work as he needs it to.

Irreal has recounted some examples of Ridenour’s past use of Elisp to customize his work environment and now there’s a new one. Lately he decided to try ghostel for his terminal emulator. He was already using ghostty as his macOS terminal emulator so the change made sense.

The change worked out well and Ridenour was pleased with the result except for one thing. With his previous Emacs terminal emulator, eat, he was able to toggle the emulator on and off easily but it didn’t work with ghostel. Rather than look for another package to solve this, he decided to fix it himself.

The desired functionality was

  1. If no instance of ghostel is running, start one in the current window.
  2. If there is already a ghostel instance running in another window, switch to it.
  3. If there is a ghostel instance running in the current window, bury it.

Ridenour implemented that functionality with two functions. The first gets the active buffer of any running instance of ghostel or returns nil if there is none. The second function implements the three steps above in a straightforward way. The code is in his post and easy to understand so take a look if you have a similar need.

-1:-- Toggling Ghostel (Post Irreal)--L0--C0--2026-06-27T14:25:33.000Z

Emacs APAC: Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, June 27, 2026

This month’s Emacs Asia-Pacific (APAC) virtual meetup is scheduled for Saturday, June 27, 2026 with BigBlueButton and #emacs on Libera Chat IRC. The timing will be 1400 to 1500 IST.

The meetup might get extended by 30 minutes if there is any talk, this page will be updated accordingly.

If you would like to give a demo or talk (maximum 20 minutes) on GNU Emacs or any variant, please contact bhavin192 on Libera Chat with your talk details:

-1:-- Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, June 27, 2026 (Post Emacs APAC)--L0--C0--2026-06-26T17:54:46.000Z

Meta Redux: copilot.el 0.7

Good news, everyone – copilot.el 0.7 is out! And it’s a big one.

Ever since I took over the maintenance of copilot.el in the spring of 2025, I’ve had one north star: close the gap between Emacs and the editors that enjoy first-party Copilot support. VS Code and (Neo)vim got the rich, official experience – chat, agents, the whole shebang – while we Emacs folks were stuck with humble ghost-text completion. That never sat right with me. Emacs deserves better.

For a bit of perspective on the pace: in its first three years the project never shipped a single tagged release. 0.7 is the seventh I’ve cut since taking over. We’ve been busy.

For a long time that gap felt unbridgeable. We were essentially reverse-engineering an undocumented protocol and replicating it in Elisp, always one step behind. But then Microsoft open-sourced @github/copilot-language-server, and everything changed. Suddenly we had a documented, first-party LSP server to talk to – the very same backend that powers Copilot in VS Code. The migration to it in 0.4 was the turning point. From that moment on, catching up to the other editors stopped being a fantasy and became a (long) TODO list.

We’ve been chipping away at that list ever since. A few months ago, in 0.5, we added basic support for Copilot Chat – the first time the package did anything beyond completion. 0.6 brought Next Edit Suggestions (NES) and the experimental beginnings of agent mode. And now 0.7 fills in pretty much all of the remaining blanks.

So what’s actually in it? Quite a lot. Let me walk you through the highlights.

Agent Mode, For Real

Chat is nice, but talking to an assistant that can’t do anything gets old fast. Enter agent mode:

(setopt copilot-chat-use-agent-mode t)

With this on, Copilot can run tools to actually get work done – run shell commands, create and edit files, read your diagnostics, fetch web pages. Ask it to “add a test for foo and run it” and it’ll write the test, invoke your test runner, read the output, and iterate, all without you leaving the chat buffer.

Of course, letting an AI run commands and rewrite your files unsupervised is a recipe for excitement of the wrong kind, so every tool call asks for confirmation first. File edits show you a diff preview before you approve them, and if you trust a particular tool you can pick always to stop being asked for the rest of the conversation. Long-running terminal commands now run asynchronously too, so Emacs stays responsive while that test suite chugs along – and C-g aborts a command that’s overstaying its welcome.

Workspace Awareness

An agent that can only see the current buffer isn’t much of an agent. So 0.7 teaches copilot.el to answer the server’s workspace requests: Copilot can now search your project by file glob or by text/regex (via ripgrep, so your .gitignore is respected), read files, and list directories across the whole tree. That’s what makes “where is X handled in this codebase?” actually work.

For the truly ambitious there’s also opt-in semantic search:

(setopt copilot-chat-enable-semantic-search t)

Flip that on and the server builds an embeddings index of your workspace, so you can ask whole-codebase questions instead of pointing at specific files. It’s off by default, since indexing isn’t free, but it’s there when you want it.

MCP Support

Model Context Protocol is how you give an assistant extra superpowers, and copilot.el now speaks it. Point copilot-mcp-servers at the servers you want and their tools show up in chat, right alongside the built-in ones:

(setopt copilot-mcp-servers
        '(:fetch (:command "uvx" :args ["mcp-server-fetch"])
          :memory (:command "npx"
                   :args ["-y" "@modelcontextprotocol/server-memory"])))

M-x copilot-chat-list-mcp-tools shows you which servers connected and what they offer (and complains loudly when one fails to start, which beats the old silent treatment).

A Chat You Can Actually Use

The chat buffer grew up too. A few of the quality-of-life additions:

  • Slash commands (C-c /) – /explain, /fix, /tests, /doc, and friends, fetched live from the server.
  • Acting on code blocksC-c C-i inserts the code block at point into your source buffer, C-c M-w copies it to the kill ring. No more copy-paste gymnastics out of a read-only buffer.
  • Attaching contextcopilot-chat-add-file-reference (C-c C-f) and copilot-chat-add-region-reference let you hand Copilot specific files or selections along with your question.

Odds and Ends

A grab bag of smaller, but handy, additions:

  • M-x copilot-quota shows how much of your chat, completion, and premium-request allowance is left – useful now that the fancy models eat into a quota.
  • Copilot will tell you when a suggestion closely matches public code and collect those matches, with their licenses and reference URLs, in a buffer you can open via M-x copilot-list-code-citations.
  • A pile of bug fixes, including agent-mode tool confirmations that were flat-out broken against newer servers, and chat finally picking a sensible default model on its own.

As always, see the full changelog for the complete rundown.

Epilogue

Agent mode, MCP, and semantic search are all opt-in, so nothing here changes your setup unless you ask for it. But I’d love for you to ask for it! Turn on agent mode, point it at a real task, and tell me where it shines and where it falls on its face. This kind of feature only gets good with people kicking the tires and filing detailed reports, so please do open issues and let me know how it goes. Your feedback is what shapes the next release.

I’d be remiss not to acknowledge it: there’s a decent chance that by now half of you have wandered off to Claude Code or some other agentic CLI, and that pouring this much time and energy into Copilot support for Emacs is, objectively, a questionable use of a human life. Fair enough.

But here’s the thing – Emacs has always excelled at providing amazing support for niche technologies long after the rest of the world has moved on. We’re the people who’ll lovingly maintain a mode for a language nobody has written any code in since 2003. Keeping Copilot first-class in Emacs is squarely in that grand tradition, and I, for one, intend to see it through. :-)

That’s all I have for you today. Keep hacking!

-1:-- copilot.el 0.7 (Post Meta Redux)--L0--C0--2026-06-26T09:00:00.000Z

Nicolas Cavigneaux: Not Quite Vim: Switching to Evil in Doom Emacs

Introduction

I’ve used Emacs for many years.

My fingers knew the native bindings by heart. C-a, C-e, C-k, M-f, the whole choreography of chords. I never really questioned it.

Then I moved to Doom Emacs.

Doom is built around Evil, the Vim emulation layer. Vim keybindings are the default, on purpose. And the first thing I did was turn them off.

It was pure habit. I had years of native muscle memory and no intention of throwing it away to relearn how to move a cursor.

So for a while I ran Doom the “wrong” way: a Vim-first distribution with Vim disabled.

Later, I changed my mind.

Giving Evil a Real Chance

At some point it felt silly to run Doom against its own grain.

The whole project is designed around Evil. Most of its polish, its defaults, its bindings assume you’re editing the Vim way. By keeping native bindings I was fighting the tool instead of using it.

So I flipped the switch and committed to it.

The beginning was rough.

There’s a whole grammar to learn. Modes. Operators. Motions. Text objects. A language where pressing keys in the wrong order does nothing useful, and where the “obvious” Emacs reflex is suddenly wrong.

For a few days I was slower at everything.

Then, slowly, it started to make sense. And once it did, I kept it. This is still how I edit today.

One thing matters before the bindings, though.

What I switched to isn’t really Evil. It’s Doom in Evil mode, which is a very different, much friendlier thing.

It’s Doom, Not Raw Vim

This distinction took me a while to appreciate.

Evil on its own is the engine: it teaches Emacs to speak Vim. But raw Evil is spartan. Doom wraps it in a coherent set of defaults, a leader key, discoverable menus, and dozens of small integrations that make the modal model pleasant instead of merely functional.

which-key is the clearest example. Start a key sequence, pause, and Doom shows you every possible continuation in a popup. You’re never stranded wondering what comes next.

So when I say I switched to Evil, I really mean I switched to Doom’s curated take on it. I don’t think I would have stuck with bare Evil. The polish is what kept me.

The Mental Model That Made It Click

The breakthrough wasn’t memorising bindings.

It was understanding that Vim editing is a small language: a verb, then something to act on.

You combine an operator (the verb) with a motion or a text object (what to act on):

  • d — delete
  • c — change (delete, then drop into insert mode)
  • y — yank (copy)

And the things to act on:

  • w — to the next word
  • iw / aw — “inner word” / “a word” (with surrounding space)
  • i) / a) — inside / around parentheses
  • ip / ap — inside / around a paragraph

Once that clicks, you stop memorising commands and start composing them. dw, ci), yap — none of these needed to be learned individually. They fall out of the grammar.

The other revelation was the dot command.

Pressing . repeats your last change. Edit one word, jump to the next occurrence, press ., and you’ve made the same edit again. It sounds trivial. It’s one of the things I’d miss the most.

The Editing Bindings I Reach For Daily

These are the ones that earned a permanent place in my hands.

Acting on the word under the cursor:

  • daw — delete a word (and its surrounding space)
  • caw — change a word (delete it and land in insert mode)
  • diw — delete the inner word, leaving the spaces
  • ciw — change the whole inner word
  • cw — change from the cursor to the end of the word

Working with indentation, which is wonderful in indentation-heavy languages:

  • dii — delete everything at the current indentation level
  • cii — change that whole block

Doom ships the indentation text object (ii / ai), so these compose just like the word objects.

Commenting:

  • gcc — toggle the comment on the current line
  • gc + a motion — comment a region, e.g. gcap to comment a paragraph

And for shuffling things around, there’s a surprising amount you can do with operators and the p (paste) register. The Vim wiki has a good reference on swapping characters, words and lines that I still go back to.

Getting Around

Motion is where the modal model really pays off, because once you’re in normal mode the whole keyboard is navigation.

Jumping to a line is just the line number followed by G:

30G

That puts the cursor on line 30. (:30 followed by Enter does the same.)

Within a line, f and t are indispensable:

  • f( — jump forward to the next (
  • t( — jump just before it
  • ; and , — repeat that jump forward and backward

But the binding that genuinely changed how I move is Doom’s avy integration, hiding under the gs prefix:

  • gss — type a couple of characters and jump to anything visible on screen
  • gsj / gsk — jump visually to a line below / above
  • gs SPC — jump to any character, anywhere in the buffer

After a while, reaching for the mouse or hammering arrow keys feels slow.

For code, Doom’s LSP layer adds the usual suspects:

  • gd — go to definition
  • gr — find references
  • K — show documentation for the symbol under the cursor

Doom’s Polish: The Leader Key

This is the part that has very little to do with Vim, and everything to do with why Doom is comfortable.

Almost every non-editing action lives under the SPC leader key:

  • SPC f f — find a file
  • SPC b b — switch buffer
  • SPC p p — switch project
  • SPC w — window management
  • SPC s — search (in buffer, project, etc.)
  • SPC g g — open Magit
  • SPC : — run a command (the old M-x)

Because of which-key, I never had to memorise these up front. I’d press SPC, read the menu, and drill down. The bindings stuck on their own through repetition.

And when I want to know what a key does in the current mode, the bindings help is right there under SPC h b — a searchable list of everything currently bound. It’s how I discovered half of what I now use.

What I Didn’t Fully Convert

I’m not a purist about any of this.

A few Emacs reflexes survived the switch, simply because my fingers refused to let them go.

The big one is C-g. It’s my universal “get me out of here”, and it works everywhere in Doom, so I never replaced it. Leaving insert mode is the clearest case: I still do it three different ways depending on where my hands are — Esc, jk, or C-g. I never forced myself to settle on one.

In insert mode I also kept C-a and C-e to jump to the start and end of the line. That’s an Emacs-ism rather than a Vim one — Doom keeps those motions alive in insert state — and it’s so wired into my hands that switching to a normal-mode motion just to reach the end of a line never felt worth it.

And every so often I still reach for M-x instead of SPC : to run a command. Old habits.

On top of that I keep a handful of small custom bindings, and they fall into two telling camps.

Some of them duplicate what Evil already does. I bound my own commands to duplicate a line and to change the case of a word, even though Evil has perfectly good answers — yyp to duplicate a line, gUiw / guiw to upper- or lower-case a word. I kept the Emacs versions purely out of reflex.

Others have no Vim equivalent at all, and those I would never give up. Narrowing is the clearest example: focusing the buffer on a single region or function is a pure Emacs power, so I gave it a proper home under the leader.

(map! :leader
      :desc "Narrow to region" "n r" #'narrow-to-region
      :desc "Narrow to defun"  "n f" #'narrow-to-defun
      :desc "Widen"            "n w" #'widen)

That’s the honest part of the story. I didn’t replace one orthodoxy with another. I took Doom’s Evil as a strong default and bent the last few percent to fit my hands.

If anything, that’s the lesson: the goal isn’t to edit like Vim, or like Emacs. It’s to edit like you, on top of sane defaults.

Resources

A few things that helped me far more than any single cheat sheet:

Was It Worth It?

Yes — with an asterisk.

It was worth it because, after the rough first days, modal editing genuinely made me faster: the verb-plus-object grammar turns most common edits into a couple of deliberate keystrokes.

The asterisk is that “switching to Evil” undersells what actually happened. I didn’t move to Vim. I moved to Doom, resisted its way of working for a while, then finally let it do what it was built to do.

The grammar fought me. Then it made sense. And it stuck.

-1:-- Not Quite Vim: Switching to Evil in Doom Emacs (Post Nicolas Cavigneaux)--L0--C0--2026-06-25T22:00:00.000Z

Irreal: Simulating The Unix at With Emacs

Charles Choi has a nice post on simulating the Unix at command with Emacs. Back in the old days, Choi used to use the Unix at command to notify himself of various events. In macOS, the at command is disabled by default because its use can deplete the battery. Old habits die hard though, and Choi wanted a way to get the same behavior.

He started with the Emacs run-at-time function that does roughly the same thing as the at command except it runs an Emacs function instead of a command line function. Choi filled that gap with a macOS shortcut that he calls nota. The idea is that a small Emacs function schedules nota with the desired message at whatever time you want.

If you’d like to use his workflow, his post has a link to his shortcut that you can download. Of course, the shortcut bit works only with macOS but I doubt it would be hard to adapt to any Unix-like system. As far as I can see, the shortcut mechanism is merely a convenience and could probably be replaced with something like the Unix message command.

Regardless, if you’re looking for a handy way to serve a notification at a certain time and, like Choi and me, you have Emacs running all the time, take a look at his post.

-1:-- Simulating The Unix at With Emacs (Post Irreal)--L0--C0--2026-06-25T14:41:30.000Z

Jakub Nowak: org-roam Orphans

I use org-roam for taking notes, but I also like to have indexes (just org roam nodes that link their contents) that link everything together. This means that if I want nodes properly linked when I make them, I first have to find the index they belong to insert them into, which really ruins the flow. I would rather go through the graph regularly and clean things up later, but aside from squinting at org-roam-ui finding those orphan nodes is actually quite annoying from within Emacs.

Thankfully I stumbled upon this note by Justin Abrahms, which gives a nice SQL query for finding orphans, and I've written a bit of Elisp around it to prompt for the resulting nodes with completing read. Hopefully someone else finds this useful!

(defun org-roam-find-orphan ()
  "Find all orphaned nodes and then provide their paths to open with completing-read."
  (interactive)
  (let* ((org-roam--db (sqlite-open org-roam-db-location))
       (org-roam-orphans
        (sqlite-select
         org-roam--db
         "select * from nodes n
where not exists ( select null from links l where n.id = l.source )
and not exists ( select null from links l where n.id = l.dest );")))
    (sqlite-close org-roam--db)
    (find-file
     (completing-read
      "Orphan node: "
      (mapcar
       (lambda (rec)
       (string-replace "\"" "" (cadr rec)))
       org-roam-orphans)))))
-1:-- org-roam Orphans (Post Jakub Nowak)--L0--C0--2026-06-25T00:00:00.000Z

Charles Choi: Scheduling Future Tasks in Emacs

Back when I had to run a lot of long-running simulations on Unix systems, I became an ardent user of the at command, which lets one schedule a program to run at a future date. I also used at to schedule different notifications, like sending a message to a pager (yer, how quaint).

Moving to macOS (nee OS X) changed that, as it disabled by default the at command due to battery life management. As I was unwilling to give up battery life for at, I learned to live without it. That was, until I learned that Emacs had run-at-time.

The run-at-time command does as it says on the tin: it will run a function at some future specification of time.

1
(run-at-time TIME REPEAT FUNCTION &rest ARGS)

That specification of time is “natural language-like” in that multiple string representations of time can be accepted, both absolute and relative.

The function can be anything Elisp offers, but running shell commands is what makes run-at-time a substitute for at, with some caveats.

Jobs that are submitted by run-at-time can be listed out via the list-timers command. You can cancel a job (actually a timer object) using the c key, bound to timer-list-cancel. Note that Emacs disables list-timers by default as it exposes timers that you should likely not mess with. Be forewarned.

Another caveat is that using run-at-time presumes that your Emacs session is still running at that future time. If you have Emacs running all the time (particularly as a server) then this should work without complication.

Example: Scheduling a macOS Notification

A common use I have for run-at-time is to send myself a notification at a future time. The way I prefer to be notified is using the macOS Notification Center. Here’s an example of what a notification looks like:

The above notification was sent using a macOS Shortcut I’ve defined named nota. Shown below is how nota is defined in the Shortcuts app.

A macOS shortcut can be invoked from the command line. We can wrap this capability in the Elisp function cc/run-nota.

1
2
3
4
5
6
7
8
(defun cc/run-nota (msg)
  "Run nota shortcut with MSG."
  (process-lines
   "shortcuts"
   "run"
   "nota"
   "-i"
   msg))

Finally we can schedule cc/run-nota with run-at-time through the wrapper command cc/notify-at.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(defun cc/notify-at (&optional start-time msg)
  "Send a nota notification with MSG at START-TIME.

This command invokes `cc/run-nota' with MSG at START-TIME passed into
`run-at-time'."
  (interactive)
  (let* ((start-time (if start-time
                         start-time
                       (read-string "Time: ")))
         (msg (if msg
                  msg
                (read-string "Message: "))))
    (run-at-time start-time
                 nil
                 #'cc/run-nota
                 msg)

    (message "Show notification “%s” in/at %s" msg start-time)))

Users wishing to install the nota macOS shortcut can download it via the following iCloud link:

nota shortcut (via iCloud)

Closing Thoughts

run-at-time is an underappreciated built-in which has given me much of what I used to use at for. If you use Emacs for implementing any kind of automation, you should include run-at-time as part of your toolbox for it.

-1:-- Scheduling Future Tasks in Emacs (Post Charles Choi)--L0--C0--2026-06-24T19:25:00.000Z

Charlie Holland: svg-margin: Better Gutters for Emacs

1. TLDR

Emacs can draw line-level indicators in the built-in fringe and margin, the thin gutters beside the buffer text. The fringe gives you a single monochrome mark per side, and the margin can technically hold more, but getting several independent sources to share one line cleanly is surprisingly hard to pull off in the plain text rendering that the margin uses out-of-the-box.

svg-margin (code on GitHub) fixes this by rendering indicators as SVG in the window margins, so any number of independent "providers" present text, icon glyphs, and clickable markers side by side on one line. Like my svg-line package, it overlays SVGs onto built-in UI components (the margin in this case) by leveraging Emacs's built-in SVG support. My personal config channels many sources to it, including VC, flycheck, evil marks, Org elements, whitespace indicators, and live symbol occurrences.

svg-margin-annotated.png

Figure 1: See svg-margin in action (to the left of the grey fringe). Note the lines where there are multiple indicators. On line 38, we have an evil mark (m), a symbol-overlay indicator ({}), and git-gutter. I'm also using the right margin to display a white-space indicator. The margin grows and shrinks to accommodate an arbitrary number of indicators.

2. About

I lean on the fringe and margin heavily because I'm a HUD maximalist, and scanning a buffer's gutter at a glance is almost always faster than parsing the same data out of the buffer's text.

To satisfy my greed for HUD, I yearned for a flexible gutter where any number of independent (that's a key word, remember it) indicator sources can coexist without competition on a given line, and without me having to allocate them to predetermined slots. I want an API that's like "here's a gutter, any package can push pretty indicators to it".

Emacs's fringe can't give me that because it's one monochrome bitmap per side. It's charming in its ugliness, and very useful (arguably necessary) for things like line wrap indicators. It's the Quasimodo of Emacs's UI components: rough around the edges, naive, and loyal.

Emacs's margin is the Esmeralda to the fringe's Quasimodo, and provides a much better stage for the decoupled-indicator-provider vibe I'm going for, mainly because it supports multiple indicators on a given line. But it falls ever so short of my requirements because it lacks a way to compose multiple indicators together.

The main contribution of svg-margin is exactly that missing compositor. It lets you push however many indicators you want into a given margin line, with graceful resizing (over the built-in truncation), and with position priority (for example, so that git-gutter information always renders closest to the buffer's text).

To address an anticipated question: Why use SVG when you can use a compositor like the one defined in svg-margin with the built-in margin? Basically, it works, but it's very difficult to get the built-in margin's text rendering to play nice with pretty glyphs in the multiple-indicator-per-line scenario. As optional reading, you can read more about why in the Why an image, not text section below.

3. svg-margin's Features

  • Multi-column packing: several indicators stack side by side on one line, in both margins; the margin grows and shrinks to fit, with no upper bound on the number of indicators per side.
  • Decoupled providers: a provider of indicators is a simple function of the buffer returning a list of indicators, so independent packages coexist without conflict.
  • Rich indicator style: built-in shapes (dot, ring, bar, box, triangle), centred text, Nerd Font icon glyphs, or a custom :draw function can all be used to render indicators in the margin.
  • Clickable anything: any indicator can carry a left-click action, a right-click menu, and hover help with a highlight.
  • No jitter on buffer switch, text-scale aware: margin widths are buffer-local and indicators resize with text-scale. This avoids jitter when switching buffers and ugly or truncated repaints, respectively.

4. Configuration

A provider is a function of the buffer that returns a list of indicator plists. As shown below, you simply define the provider, register it with svg-margin-register-provider and then enable svg-margin-mode (or global-svg-margin-mode).

4.1. A simple provider

This example puts a red dot beside every line containing the string TODO:

(svg-margin-register-provider 'todo
  (lambda (_buffer)
    (let (out) ;; out is shorthand for 'output'
      (save-excursion
        (goto-char (point-min))
        (while (re-search-forward "\\_<TODO\\_>" nil t)
          (push (list :line (line-number-at-pos) :shape 'dot :color "#cc3333")
                out)))
      out)))

(svg-margin-mode 1)            ; or (global-svg-margin-mode 1)

The provider (that lambda) returns a list of indicator plists, each with a position (:line or :pos), a :shape, and a :color. svg-margin-mode enables the gutter in this buffer.

4.2. Decoupled providers

The real point of providers in svg-margin is that they are decoupled (or independent). In this example, three separate ones (a VC bar, a lint icon, and an evil-style mark) are registered. Two of them happen to target the same line, and svg-margin gracefully handles packing them into adjacent columns within the margin:

;; A VC provider: a coloured bar.  Highest priority, so it sits nearest the text.
(svg-margin-register-provider 'vc
  (lambda (_buffer)
    (list (list :line 10 :shape 'bar :color "#8fb39a" :help "added")))
  :side 'right :priority 9)

;; An independent lint provider.  It knows nothing about 'vc, but because it
;; emits an indicator for the same line, svg-margin packs it into the next
;; column over.  This one is a clickable icon with an action and a menu.
(svg-margin-register-provider 'lint
  (lambda (_buffer)
    (list (list :line 10
                :text (nerd-icons-codicon "nf-cod-bug") :font "Symbols Nerd Font Mono"
                :color "#cf9999"
                :help "syntax error"
                :action #'flycheck-list-errors :action-help "list errors"
                :menu '(("Next error" . flycheck-next-error)
                        ("Explain"    . flycheck-explain-error-at-point)))))
  :side 'right :priority 5)

;; A third provider, on the LEFT margin: a letter mark coloured by a face.
(svg-margin-register-provider 'marks
  (lambda (_buffer)
    (list (list :line 25 :text "a" :face 'warning)))
  :side 'left)

(global-svg-margin-mode 1)
  • The key take-away here is that three independent providers (vc, lint, and marks) never reference each other. vc and lint both emit for line 10, so svg-margin packs them into adjacent columns of one image, ordered by :priority (vc with priority 9 is rendered nearest the text, and lint with priority 5 is rendered next outward).
  • :side / :priority passed to register-provider are defaults: they add these keys for any indicator that doesn't set them.
  • :text + :font: a string drawn centred. Note that when you use a Nerd Font, the text can be a glyph. :face uses a face's foreground instead of a literal :color.
  • The :action (left-click action) / :action-help (hover-over) / :menu (right-click context menu) keys turn an indicator into a button.

4.3. Reclaiming the fringe

Because a provider just reads data, you can move what a package draws in the fringe into the margin (with one practical exception). For example, here are evil's marks, which display in the fringe by default, mirrored into the left margin:

(setq svg-margin-disable-fringe 'left)   ; reclaim the left fringe

(svg-margin-register-provider 'evil-marks
  (lambda (buffer)
    (with-current-buffer buffer
      (cl-loop for (ch . m) in (bound-and-true-p evil-markers-alist)
               when (markerp m)
               collect (list :pos (marker-position m)
                             :text (char-to-string ch)
                             :face 'font-lock-keyword-face))))
  :side 'left)

The provider reads evil-markers-alist and renders each mark's letter at its position.

svg-margin-disable-fringe tells svg-margin to collapse the named fringe (left, right, or both) to zero on each render, so the margin effectively reclaims the evil marks from the fringe's space. This is also how my config moves git-gutter, flycheck, and evil marks off the fringe into the better gutter provided by svg-margin.

5. Why an image, not text

This is optional reading for those curious as to why SVG is justified in this case.

Packing several independent indicators into one built-in margin line has two challenges, one compositional (already solved by the compositor), and the other graphical.

With the built-in margin displaying multiple indicators per line, I found that pixel-exact layout in text is a losing game. In the built-in margin, indicators are laid out as characters. The margin is reserved in whole character columns, but text glyph advances (e.g. for Nerd Font icons) don't divide evenly into them, and I found that this became worse at fractional text-scale. The challenges that arose for me from this char advance inconsistency:

  • Text clipping: the rendered content runs slightly past the reserved width and the outermost marker is cut off at the margin's edge.
  • Horizontal jitter: glyph advances vary, so when one indicator appears or disappears, its neighbours shift by a sub-pixel.
  • Line-height growth: Nerd-Font icons often raster taller than the text line and stretch it, and trying to trim their height to fit shrinks their character advance below a character, which reintroduces the jitter issue.

I'm sure it was possible to get around this, but I had to add enough code to account for these issues that the simple compositor-only implementation quickly outgrew the compositor-plus-SVG implementation.

The reason SVGs are easier to work with is that an image has an exact, author-controlled pixel width. I found there were no issues handling character advances, no clipping, no jitter, no line-height problems, and that the SVG-based approach worked at any text-scale.

6. Caveat: line-wrap indicators stay in the fringe

One thing you can't pull into the margin is the line-wrap (continuation) and visual-line arrows. I tried this for a while, and to my surprise, I couldn't get it to work.

The reason is that those arrows are drawn by Emacs's redisplay engine per screen row as it lays the buffer out. When a long line spans several screen rows, each wrapped row gets one line-wrap indicator. Margin content, by contrast, is anchored to a buffer position, and renders on the single screen row that contains that position.

The points where a line wraps are not buffer positions, and I found that they're actually decided at display time from the window's width and font. So with the margin, there is nothing to anchor a marker to on the second visual row, short of recomputing the line-breaking yourself (reimplementing part of redisplay), which I don't think is worth the squeeze. After all, the fringe already does this well.

7. Note on Inspiration

svg-margin is the gutter sibling of svg-line. Both grew out of the same realization that Emacs's native SVG support, which Nicolas Rougier's explorations showed could stand in for so much of the display engine, applies just as well to the margins as to the status bars. Where svg-line rebuilt the *-lines, svg-margin rebuilds the gutter.

-1:-- svg-margin: Better Gutters for Emacs (Post Charlie Holland)--L0--C0--2026-06-24T11:22:06.000Z

Bozhidar Batsov: Neocaml 0.9: A Better REPL, Dune/Opam Completion, and More Robustness

It’s been a couple of months since the last neocaml release, and the reason is simple — for a while there I was genuinely out of ideas. Back when I shipped 0.6 I declared (again!) that I was done with new features, and this time I almost meant it. But ideas have a way of creeping back in, and 0.9 turned out to be a meaty release. Here are the highlights.

A much nicer REPL experience

The biggest chunk of work went into the REPL (toplevel) integration. I’m well aware that the OCaml toplevel isn’t terribly popular with seasoned OCaml developers — most of them reach for a proper build and a debugger instead. But I think newcomers get a lot of mileage out of a REPL, and (no surprise to anyone who’s followed my work) I’m a Lisper at heart with a real soft spot for interactive development. Clojure and Emacs Lisp spoiled me, and I want OCaml beginners to taste a bit of that too.

So, what’s new:

  • A dedicated REPL per project. The REPL buffer is now named after its project (e.g. *OCaml: myproject*), and the send commands route to the current buffer’s project REPL. You can have several projects running side by side without them stepping on each other.
  • Choose your toplevel. The new neocaml-repl-flavor lets you pick between ocaml, utop, and dune-utop. Set it globally, or per project via .dir-locals.el:

    1
    
    ((neocaml-mode . ((neocaml-repl-flavor . dune-utop))))
    

    The active flavor shows up in the REPL’s mode line, so you always know what you’re talking to.

  • Send a phrase and step. C-c C-n (neocaml-repl-send-phrase-and-step) sends the phrase at point to the REPL and moves on to the next one — perfect for walking through a file top to bottom while you experiment.
  • #require from Emacs. neocaml-repl-require loads a findlib package into the running toplevel without you having to type the directive by hand.
  • Restart on demand. neocaml-repl-restart kills and restarts the toplevel when things get into a weird state.

Completion for Opam and Dune, no LSP required

This one I’m particularly happy with. .ml/.mli files have ocaml-lsp-server to lean on for completion, but the auxiliary file formats have no language server at all. That’s exactly the kind of gap neocaml is meant to fill, so both neocaml-dune-mode and neocaml-opam-mode now ship a completion-at-point backend.

In a dune file you get completion for stanza names, the field names valid for the enclosing stanza, and library names inside libraries/pps fields:

(library
 (name my_lib)
 (libraries str re|))   ; <- completes both your own libraries and installed findlib ones

The library candidates combine your project’s own libraries with whatever’s installed in the active Opam switch. And it’s switch-aware — if there’s a project-local switch (an _opam/ directory), neocaml detects it and queries it via opam exec --, without any configuration on your part. The results are cached per project, so it stays snappy.

In opam files you get completion for field and section names, and package names inside depends/depopts/conflicts (sourced from opam list):

depends: [
  "dune" {>= "3.0"}
  "cmd|"   ; <- completes to cmdliner and friends
]

Both backends can be toggled off via neocaml-dune-complete-libraries and neocaml-opam-complete-packages if you’d rather not have them.

Robustness improvements

A good chunk of this release is the unglamorous but important work of making things just behave correctly:

  • Character literals and quoted strings at the syntactic layer. Tree-sitter fontifies '"', '(', and {|raw "strings"|} correctly, but the syntax table underneath was getting confused — which broke forward-sexp, delete-pair, and electric-pair-mode around those constructs. The fix was a good old syntax-propertize-function. I wrote up the whole story over on Emacs Redux in Tree-sitter Modes Still Need a Syntax Table, if you’re into mode-writing internals.
  • project.el integration. A directory with a dune-project file is now recognized as a project root (even without version control), _build/ and _opam/ are ignored, and compile-command defaults to dune build in dune projects.

There’s more in there too — ocamlformat integration (C-c C-f), clickable URLs and bug references in comments, a font-lock level selector, and richer menus across the modes.

OCaml 5.5 support, and the ABI 14 balancing act

OCaml 5.5 was released on June 19th, so this felt like a good moment to ship 5.5 support in neocaml. The ocaml and ocaml-interface grammars now track tree-sitter-ocaml v0.25.0, which brings the 5.5 grammar along with it.

There’s a wrinkle here that’s worth explaining, because it’s shaped the last few releases. A tree-sitter grammar gets compiled into a parser that speaks a particular ABI version, and Emacs can only load parsers up to the latest ABI supported by the libtree-sitter it was built against — it’s not the Emacs version itself that sets the ceiling. In practice a lot of Emacs 30 builds out there (notably Homebrew’s on macOS) are linked against tree-sitter 0.24, which tops out at ABI 14; you need an Emacs built against tree-sitter 0.25+ to load ABI 15 grammars. The trouble is that the tree-sitter 0.25 CLI now generates ABI 15 parsers by default, so any grammar regenerated with current tooling produces something those builds simply can’t load — you install it and it just errors out. Emacs 31 will ship with newer tree-sitter and make ABI 15 the common case, but it’s not out yet. (This isn’t a neocaml problem as such; it’s been biting tree-sitter modes across the ecosystem.)

After a few users ran into exactly this, I’ve made a deliberate decision: stick to ABI 14 grammars until Emacs 31 is widely available. That effort started a couple of releases back — in 0.8.1 I lowered the ABI requirement from 15 to 14 across the opam, dune, and ocamllex modes, switched the menhir recipe to tmcgilchrist/tree-sitter-menhir, and pinned ocamllex back to v0.24.0, all of which target ABI 14 (#42). 0.9 extends that policy to the core OCaml grammars.

The catch with v0.25.0 is precisely that it generates an ABI 15 parser. Happily, the 5.5 grammar didn’t actually need any ABI 15 features — the bump rode along with the CLI upgrade — so an ABI 14 regeneration of the very same grammar is a drop-in. Big thanks to 314eter, the tree-sitter-ocaml maintainer, for cutting a v0.25.0-abi14 tag for exactly this purpose (#141). The one snag was that tagging normally triggers releases to NPM, crates.io, and PyPI, so I sent a small PR to skip publishing for ABI-suffixed tags (#142), and the tag followed. neocaml now pins both grammars to it.

Roadmap and docs

If you’re curious where neocaml is headed, I’ve started keeping a ROADMAP.md with ideas and guiding principles (short version: tree-sitter first, lean on the LSP stack for .ml/.mli, and own the auxiliary modes that have no language server). The project also has a proper documentation site now at neocaml.org, so there’s a real home for the details beyond the README.

Give it a Try

As always — update from MELPA, play with it, and let me know how it goes. The full list of changes is in the 0.9.0 release notes. Bug reports, feature requests, and pull requests are all welcome on GitHub.

That’s all from me, folks! Keep hacking!

-1:-- Neocaml 0.9: A Better REPL, Dune/Opam Completion, and More Robustness (Post Bozhidar Batsov)--L0--C0--2026-06-24T07:00:00.000Z

Raymond Zeitler: Link diary Event to Org Heading

Suppose you want to schedule a recurring meeting in the diary, yet you want to access an org heading for note-taking and clocking time. You can add a link to that org heading in the description of the diary event.

Here's how I'd do it:

  • Place point on the desired Org heading
  • Invoke org-store-link, which might be bound to C-c l
  • In the diary file...
    • Add the date expression for the corresponding event. In this example, we'll schedule an event that occurs from 7pm to 8pm on the third Tuesday of every month.
      • %%(diary-float t 2 3) 19:00-20:00
    • Add a space and then the link as the event description with M-x org-insert-link
      • Note that C-c C-l might not bound to org-insert-link in diary-mode
  • That's it!
-1:-- Link diary Event to Org Heading (Post Raymond Zeitler)--L0--C0--2026-06-23T19:38:40.369Z

Randy Ridenour: Toggle Ghostel

I have been using Eat and toggle-term.el for terminal emulation in Emacs. After seeing some recommendations for Ghostel, I decided to give it a try, especially since I use Ghostty as my terminal emulator on the Mac anyway. All the reports were correct, Ghostel works like a charm, but I didn’t have as smooth a toggle experience as I had with Eat. Before switching back, I wondered if I really needed to install a package just to toggle the Ghostel buffer. It turns out that writing a simple toggle function was fairly easy.

The first function returns the name of the ghostel buffer if there is one. The second function starts ghostel if there is no existing ghostel buffer, switches to the buffer if there is one, and buries the ghostel buffer if it is currently the active buffer. I had to use bind-key* to override the ghostel key-map.

(defun rlr/ghostel-buffer ()
  "Return the active ghostel buffer, or nil if none exists."
  (seq-find (lambda (buf)
              (string-match-p "\\*ghostel:" (buffer-name buf)))
            (buffer-list)))

(defun rlr/ghostel-toggle ()
  (interactive)
  (let ((buf (rlr/ghostel-buffer)))
    (cond
     ((not buf)
      (ghostel))
     ((eq (current-buffer) buf)
      (bury-buffer))
     (t
      (switch-to-buffer buf)))))

(bind-key* "<f2>" 'rlr/ghostel-toggle)

Tagged: Emacs

-1:-- Toggle Ghostel (Post Randy Ridenour)--L0--C0--2026-06-23T15:36:00.000Z

Irreal: YouTube Radio Player

As you all know by now, I’m not a Google fan and generally avoid using their products. The one exception is YouTube for videos. Apparently, YouTube also has a radio channel, which is a sort of music streaming service. I listen to my music offline so I’m not interested in steaming services and even if I were, I’d probably use Apple’s or maybe the Amazon service that I get for free as an Amazon Prime member.

Still, lots of people don’t share my antipathy to Google and at least occasionally like to stream music. One such person is Álvaro Ramírez who sometimes has the urge to listen to YouTube radio channels and, being Ramírez, built an Emacs player, ytr, to do that. As usual, he built ytr to scratch and itch and is using it to experiment with its UI with the long term goal of possibly integrating it into his music player, ready-player.

Ytr is still at an early stage of development and will probably change as Ramírez gets more experience with it. It’s not yet in MELPA, of course, but you can get a copy from its GitHub repository where you’ll find installation instructions as well as a command summary.

If you have any interest in playing YouTube radio from within Emacs, take a look at Ramírez’s post and the ytr GitHub repository.

Update [2026-06-24 Wed 10:08]: Álvaro Ramírez reached out to me offline with a clarification. What ytr actually does is download the audio track from any YouTube video. As Ramírez says, often the video portion is secondary to the audio. I’ve noticed that that’s especially true for older recordings where the video portion may consist simply of a picture of the album cover or the artist.

-1:-- YouTube Radio Player (Post Irreal)--L0--C0--2026-06-23T15:09:57.000Z

Andros Fenollosa: How I built a GPU backend for Emacs

A few months ago I became obsessed with a silly question: why does my Emacs, on a laptop with a perfectly capable GPU, draw all of its text using the CPU? And that led to others: why can't I play a video inside a buffer? Why can't I have animated cursor effects? Why can't I cross-fade between buffers? I needed to satisfy my curiosity, so I started digging.

I started reading the code, with an AI as my companion. I discovered that every glyph, every underline, every scroll is recomputed and repainted by the processor. Emacs's redisplay engine (xdisp.c) was born in an era when there was no other option, and it is tuned to the millimeter for exactly that. And nobody had managed to slip a GPU underneath without rewriting half of Emacs... until recently.

So I decided to try. What began as a weekend experiment ended up being a complete display backend for macOS with Metal, a second backend for GNU/Linux with OpenGL, a video player inside the buffer, shader-based cursor effects, and a debate of more than a hundred messages on the Emacs developers' mailing list that ranged from cairo's performance to software freedom and the ethics of artificial intelligence.

This article exists because I feel like telling the story, and it might be useful for future implementations. At the end I leave the lessons I take away and a conclusion that is not the one I expected when I started.

A note of honesty up front: I built this project with the help of an LLM as a copilot, from start to finish. I say it here just as I said it in public when I was asked. I will come back to it, because it turned out to be the most important plot twist of the whole journey.

Phase 1: the architecture decision

Anyone's first instinct would be to open the macOS code, the Cocoa backend (nsterm.m), and start replacing CoreGraphics calls with Metal calls. It is the most direct path. And it is exactly what I decided not to do.

The problem with that approach is that it ties you to one platform. If I write "Emacs with Metal", I have an Emacs for Mac and nothing else. I needed to write a display-backend abstraction that would let me have one driver per platform. So I sketched a three-layer architecture on a Post-it:

flowchart TD
    X["Redisplay engine
(xdisp.c, untouched)"]:::core --> P["src/gfxterm.c
Neutral drawing policy (plain C)"]:::policy P --> D["src/gfxdrv.h
Driver interface (~25 operations)"]:::iface D --> M["src/mtlterm.m (macOS)
Metal driver"]:::mtl D --> G["src/glterm.c (GNU/Linux, X11)
OpenGL ES / EGL driver"]:::gl classDef core fill:#37474F,stroke:#263238,stroke-width:2px,color:#fff classDef policy fill:#00897B,stroke:#00695C,stroke-width:2px,color:#fff classDef iface fill:#7CB342,stroke:#558B2F,stroke-width:2px,color:#fff classDef mtl fill:#8E24AA,stroke:#6A1B9A,stroke-width:2px,color:#fff classDef gl fill:#D32F2F,stroke:#B71C1C,stroke-width:2px,color:#fff

The idea is that all the drawing logic (how a glyph string is composed, where the wavy underline goes, how an image is clipped to the window, how scrolling works) lives in a plain-C file, without a single platform-specific line. And that each platform only has to implement a small contract: about 25 primitive operations of the kind "upload this texture", "draw this quad", "present the frame". That contract is gfxdrv.h. The first driver would be Metal, in mtlterm.m.

The golden rule, the one I imposed on myself and never broke once: xdisp.c is not touched. The redisplay engine computes the glyph matrices exactly as always; I only hook into the drawing interface that already exists. If the experiment went wrong, Emacs was still Emacs.

In hindsight, this was the best decision of the whole project!

Phase 2: the Metal backend and the tyranny of the pixel

With the architecture clear, I dove into Metal. The technical plan was that of any modern text renderer:

  1. Rasterize each glyph just once, via CoreText, into a grayscale texture (a glyph atlas in R8 format).
  2. Draw the text as textured quads that sample that atlas.
  3. Upload images (PNG, JPEG, SVG, GIF) as textures.
  4. Composite the whole frame on the GPU, in a persistent texture, and present it.

On paper, two afternoons. In practice, weeks. The reason has a name: pixel parity.

My success criterion was not "it looks good". It was that the result be identical, pixel for pixel, to the original Cocoa backend. Same binary, with the GPU on and off, and the diff between the two captures had to be practically zero. I built a harness that launched the same Emacs twice, loaded an identical scenario, captured the screen on both and compared them with Python and PIL. The bar landed around 0.055% of differing pixels in the baseline, and anything that strayed from there was a bug to hunt down.

That harness was relentless, and it surfaced a collection of details I had to look at under a magnifying glass:

  • The ink weight. CoreText and my shader applied antialiasing differently.
  • The relief colors (the 3D borders of buttons and the mode-line) were not coming out right.
  • There was an off-by-one in the vertical position of the glyphs.

We should not overlook that the way of drawing is completely different, both in approach and in architecture. That makes the bugs subtle and hard to detect.

Phase 3: the cursor that froze

Of all the bugs, the one that taught me the most was the cursor one.

I wanted animated cursor effects: a ring that expands when it jumps, a comet-like trail, that kind of visual candy the GPU does almost for free. I implemented them as a compositor layer on top of the frame, without touching the buffer content underneath. They worked perfectly... while I was typing. The moment I stopped touching the keyboard, the animation froze halfway. The culprit was Apple's synchronization mechanism, CADisplayLink: it dies at rest, Emacs's event loop does not feed it when there is no user input. While I typed, the keyboard events pumped the run loop and everything ran fine; the moment I stopped, there was no one to move the clock.

The solution was to stop depending on the system and move everything continuous to a Lisp timer. Cursor, buffer cross-fade and video, everything advances from a single "pump" in Emacs Lisp that ticks periodically and tells the driver "advance everything you have and present at most once". Later I unified the three timers into one with auto-pacing (60 Hz when there is a fade, 30 Hz otherwise, and it shuts itself off when there is nothing to animate).

Once this was solved, macOS was complete. Text, decorations, images, animated GIF, line numbers, fringes with custom bitmaps, mode-line, header-line, tab-bar, Retina/HiDPI at 2x, the four cursor types, splits, dynamic text-scale. Everything pixel-perfect against Cocoa.

Now it was time to add the things only the GPU can do:

  • Video inside the buffer
  • Shader-based cursor effects
  • Cross-fade when switching buffers.

As an experiment I even put together a small YouTube frontend inside Emacs: it searched for the video and played it directly in a buffer, with the GPU compositing the frames over the text. A fun little silliness that is only possible when the frame is painted by the graphics card.

And the cross-fade when switching buffers, a smooth fade that on the GPU is just one more shader pass:

It was relatively simple, since the redisplay engine neither knows nor cares what I do on top of it; they are just compositing operations on the GPU.

Phase 4: packaging is half the work

Having the binary working on my machine and having something another person can install are two different planets. This phase has no glamour but it ate whole days.

Apple's signing and notarization were a labyrinth of their own. And when I added native-comp (AOT native compilation), about 1564 .eln files appeared that are also Mach-O code and also have to be signed one by one, with a secure timestamp, for notarization to accept them.

I published the first signed and notarized release, a Homebrew cask, and started using it daily along with a colleague. It worked. I was happy. I thought the hard part was behind me.

Then I decided to show it to the Emacs mailing list.

Phase 5: emacs-devel, or how to learn humility in an email thread

On June 8, 2026 I sent an [RFC PATCH] to emacs-devel with the subject "GPU display backend with a neutral driver layer (Metal on macOS)". I framed it carefully: I was not selling "Emacs with Metal", I was selling the abstraction. A neutral drawing layer plus a thin per-platform driver behind a small vtable, with Metal as the first driver, xdisp.c untouched, parity verified with an automatic harness, and the FSF copyright assignment already on file.

My first mistake was sending the complete patch, rather than an RFC with the idea, the design and a minimal demo. The response came fast:

Sean Whitton wrote:

"People don't normally post such large patches at once without first discussing the design issues with people on the list. Given this, I just have to ask, this isn't LLM-generated, is it?"

I answered honestly:

"100% created with LLM.

I understand that this is a rather large addition, and if it's rejected, I won't be offended. My intention was to share it because it's fully developed [...]; I'm using it daily without any problems (along with other colleagues)."

Whitton's reply was courteous and final:

"I'm afraid there is a policy conflict. The GNU project does not accept any LLM-generated contributions at present. Thank you for your interest in Emacs, anyway."

And there, in terms of "does this get merged?", the project died in less than a day. The GNU project does not, as of today, accept LLM-generated contributions. Period. No technical debate matters when it collides with a hard policy.

What I did not expect is that, far from closing the thread, it opened in three directions at once.

The turn toward "study subject"

Dmitry Gutov set the tone for what was coming:

"We cannot accept this as code contribution, but if you are already using it locally, it might be useful as a study subject. It might be more useful to test with a Linux port, though."

In other words: as code it does not get in, but as a reference or an experiment it might be worth something. And there, too, was the seed of what I would do next.

The freedom debate

And then Richard Stallman stepped in, forking the thread's subject to "GPU-specific code with no GPU-specific features?" and raising it to a moral question:

"In general the GPU is a disaster for software freedom: it turns your computer into a prison."

Later, insisting:

"They don't put physical chains on the user, but they do put digital chains on the user's computing. GPUs are a substantial part of what we are fighting to free people from."

Not everyone bought it. Arsen Arsenović answered with the sharpest technical objection in the thread:

"This is a bizarre comparison based on frivolous word association. GPU programming APIs such as Vulkan or OpenGL [...] can be implemented fully in software, and indeed are implemented using fully free software in Mesa, so there's no downside to using them from this perspective."

And Madhu brought up the uncomfortable fact that dismantles half the discussion:

"If you are using X11 on a modern (say post 2021) intel machine on linux, all your 2d graphics probably goes through the GPU backend, X11 windows are just textures."

They were right about something I could demonstrate: my OpenGL driver also runs on Mesa's software rasterizer (llvmpipe). In fact, the parity suite runs headless on it. In other words, the code does not require non-free GPU firmware to be exercised. I said so in the thread, although by then the debate had a life of its own.

The underlying technical doubt

The most substantive criticism, and the one that made me think the most, was neither the political nor the ideological one. It came from Eli Zaretskii, one of the historical maintainers:

"I'm not really surprised that using a GPU in the display backend yields performance gains that are not really spectacular with reasonable sizes of the frame: the design of the current display engine is optimized towards CPU-driven redisplay, so taking a better advantage of GPUs will probably need a more thorough redesign, not just a separate backend."

And Gerd Möllmann, the redisplay maintainer, finished it off with elegant indifference:

"It looks to me as if this adds GPU support without adding GPU-only features or changing the architecture of redisplay [...]. Can have performance benefits, maybe, don't know, but it's outside of my field of interest."

They were partly right. The engine is built to repaint small dirty rectangles on the CPU, and cairo does that extraordinarily well. Slipping a GPU underneath without redesigning the engine has a ceiling. But they were also partly making a point that can only be seen with a second backend and with numbers. And I had neither one yet.

The community had, without knowing it, written my roadmap for the following weeks: build a second backend (to validate the abstraction) and bring honest numbers (to settle Eli's doubt).

Phase 6: the OpenGL driver, or how to cash in the architecture bet

If my design's big promise was "the neutral layer is reused as-is, you only change the driver", the only way to prove it was to write a second driver from scratch and see how much shared code survived untouched.

I chose GNU/Linux over OpenGL ES 3 with EGL, on X11. The cross-platform counterpart of the Metal driver: I rasterize the glyphs with FreeType into a GPU atlas, render to an FBO and present by blitting to the window surface with eglSwapBuffers. The drawing policy, gfxterm.c, I reused entirely. And it worked: the second backend came out pixel-perfect against the stock GTK/cairo Emacs, on the same exhaustive test battery as macOS, running both on a real X server with a GPU and headless under Xvfb for the harness.

That was the moment the architecture stopped being a promise and became a fact. Writing the whole driver, with all its EGL and FreeType quirks, took me far less than the first one, because all the hard logic was already written and tested in the neutral layer.

But Linux brought its own hell, with the hardest bugs of the entire project. The worst one: when switching buffers, during a single flicker (one vblank), the half-painted startup dashboard leaked through, ghost images of other content. It took me days to discover that the root cause was not my GPU code, but the back buffer of X11's double-buffer extension (XDBE), which Emacs painted at startup and my backend never touched again.

Still, after a while of work and debugging, the OpenGL driver became stable and functional. Not perfect, but good enough to run the performance harness and compare it against cairo.

Phase 7: optimizing and bringing honest numbers

The results, on a laptop with an integrated AMD Radeon (Renoir) GPU, a 1616x912 frame, an 8000-line font-locked buffer:

Workload Stock (X/cairo) GPU (OpenGL) Ratio
Line scroll 530 fps 487 fps 0.92x
Page scroll 297 fps 296 fps 1.00x
Full-frame redraw 247 fps 294 fps 1.19x
Typing 1857 fps 1311 fps 0.71x
Image scroll 1359 fps 1239 fps 0.91x

On a laptop-sized frame, typing and line scroll are still slower than cairo, which is excellent at clipping small rectangles. My floor is one EGL buffer swap per redisplay; cairo's is a tiny damage rectangle with no swapchain. In absolute terms, everything is far above what is perceptible (the worst case, typing, is ~0.8 ms per keystroke), so it is a matter of throughput, not of responsiveness.

In short: a GPU backend does not beat a mature CPU rasterizer at static text.

But the same workloads at 4K (3760x2210) flip the result:

Workload cairo (CPU) GPU Speedup
Line scroll 117 fps 240 fps 2.05x
Page scroll 102 fps 124 fps 1.22x
Full-frame redraw 66 fps 121 fps 1.84x
Typing 238 fps 1766 fps 7.4x
Image scroll 115 fps 1328 fps 11.5x

cairo's cost grows linearly with the pixel count; the GPU's barely moves. Image scroll is the extreme case: cairo re-blits the image from CPU memory every frame, the GPU re-composites an already-cached texture. There, plus the features that only exist on the GPU (video, cross-fades, cursor effects), is where the real value lies. Text throughput on a small screen is not.

Conclusion

Let's be honest, for everyday text on a normal screen there is no reason to switch; the CPU backend is just as fast or faster. The GPU earns its place in motion, effects and video at high pixel counts, things the CPU does more expensively or cannot do at all.

The backend will never be merged into Emacs, and that is fine. It collides with the policy of not accepting LLM-generated contributions, and that is a legitimate decision by the project that I respect. I keep it as a personal fork, behind --with-gpu, opt-in and disablable with an environment variable, and I use it daily.

Was it worth it, then? For me, without a doubt. I came out with a complete display backend on two platforms, with video inside the buffer and effects that stock Emacs cannot draw, with an architecture that proved it could hold up across two implementations, and with a pile of technical scars worth their weight in gold. But above all I came out with something I was not looking for: a public, hard and honest conversation, with people who have maintained this editor for decades, about performance, about software freedom and about the role of AI in the code we write. That conversation, quoted above in their exact words, is worth more than any merge.

Right now I am focusing my efforts on the OpenGL backend for GNU/Linux, where there is still much to polish and optimize. If you are interested in trying it, in the repository you will find a .deb with the test binaries.

My final advice, if you are considering something similar: chase the experiment that obsesses you even if you know it might not end where you imagined. I started out wanting my GPU to draw text and ended up learning about architecture, about rasterization, about packaging, about the culture of a 40-year-old project and about myself. The destination turned out to be irrelevant. The journey did not.

The code is at github.com/tanrax/emacs-gpu, and the full emacs-devel thread can be read in the June 2026 archive.

-1:-- How I built a GPU backend for Emacs (Post Andros Fenollosa)--L0--C0--2026-06-23T09:33:09.000Z

Sacha Chua: 2026-06-22 Emacs news

There was lots of discussion around Rahul's post on Emacs 31. It's the first link in the list below, so I won't repeat the links here. Also, I like visualizations, so I thought these force-directed graphs (Reddit) and text-based mindmaps (Reddit, lobste.rs) were pretty cool. 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!

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- 2026-06-22 Emacs news (Post Sacha Chua)--L0--C0--2026-06-23T02:30:26.000Z

Alvaro Ramirez: ytr: YouTube radio for Emacs

I've been a happy ready-player user for some time now. I consider the Emacs package fairly feature-complete, for my needs anyway. Well almost.

While I've successfully migrated most of my music-listening to offline playback, there are the odd times when I enjoy streaming YouTube audio. I've pondered extending ready-player for this use case, but its current approach is fairly file-driven. For starters, it uses dired as a core abstraction.

Before venturing on a major refactoring, without even knowing if an Emacs streaming flow would stick, I decided to build a new package. Coincidentally, this enables me to experiment with the package UX without being restricted by ready-player's needs. And so that's what I did in my new YouTube radio package ytr.

ytr really is fairly experimental. I'm currently driving its development purely on current needs. Let's see where it goes. It borrows lots from ready-player, but its UX presents itself more as a widget. I'm kinda liking the experience. There isn't much to it: you add a channel URL, and its content metadata is automatically pulled and presented as a child frame. I've also sprinkled in some eye candy (animations), reminiscing about the Winamp days. Beware, these sweets require running on Emacs GUI.

ytr is powered by mpv and yt-dlp, the real streaming workhorses doing the heavy lifting.

ytr is available on GitHub if you're keen to check it out. Keep in mind this is a brand new package and a first iteration, so it may need some improvements. If you give it a try, I'd love to hear how you got on. I've only tested on macOS so far.

Make it sustainable, sponsor the effort

Liking ytr? Would like to see it evolve? Consider sponsoring the effort.

-1:-- ytr: YouTube radio for Emacs (Post Alvaro Ramirez)--L0--C0--2026-06-23T00:00:00.000Z

Raymond Zeitler: Emacs Carnival: diary, Part 2

As I wrote earlier, I consider diary to be an underappreciated Emacs built-in.

I got interested in it again when I wanted to schedule a recurring meeting in Org for the third Tuesday of every month. I saw on Reddit that some folks use diary-float in place of the usual active timestamp to achieve this. So I added this after the meeting header to make it work:

  * Third Tuesday of the Month Club
  SCHEDULED: <(%%diary-float t 2 3) 19:00-20:00>

I have five meetings that use this method of automatic scheduling. I like this approach because of its simplicity and elegance. Unfortunately, it doesn't behave the same way as a recurring task.

When I mark a recurring task as complete, Org reschedules it for the next date on which it should occur. And it changes DONE to TODO to ensure the event shows up on the agenda for the next occurrence.

This doesn't happen for a task that's scheduled with diary-float. When marked complete, the headline remains at DONE; the next event won't show up on the agenda.

And so Fengyuan Chen wrote next-day-spec1 to solve this issue. Unfortunately it doesn't work with the latest versions of Emacs.

One of the many recognized cognitive biases is called the Sunk Cost Fallacy2, which impels an individual to favor an inferior (or incorrect) solution because a significant amount time, money, and/or effort has been invested in the solution. Well, I've invested time and effort in both diary-float and next-day-spec, so I've continued to endorse it as a solution.

But even if rescheduling did work, I think it's better to have each meeting scheduled as a subheading of the overall meeting topic, like this:

  * Third Tuesday of the Month Club
  ** DONE May 2026: <2026-05-19 Tue>
    :LOGBOOK:
    CLOCK: [2026-05-19 Tue 18:54]--[2026-05-19 Tue 20:12] =>  1:18
    :END:
    - Note taken on [2026-05-19 Tue 21:20] \\
    We ate pizza.  Again.
    ** TODO June 2026: <2026-06-23 Tue>
      ** TODO July 2026: <2026-07-21 Tue>

The advantage to this is that clocked time and notes are organized neatly within each individual subtopic, not aggregated into one logbook and a series of notes. If the aggregated time is desired, one could insert the log report. I'll review this in another post.

But it's fun to schedule events that occur on irregular dates, such as National Engineers Week (US), which is the week in which George Washington's birth anniversary occurs3. Here's how you can add it to an Org file:

  * TODO Celebrate Engineers Week!
  SCHEDULED: <%%(equal (calendar-gregorian-from-absolute (calendar-dayname-on-or-before 0 (calendar-absolute-from-gregorian (list 2 22 (calendar-extract-year date))))) date)>

Do you have a favorite use for dairy?

1 https://github.com/chenfengyuan/elisp/blob/master/next-spec-day.el
2 Sunk cost - Wikipedia
3 https://www.holidayscalendar.com/event/national-engineers-week/
-1:-- Emacs Carnival: diary, Part 2 (Post Raymond Zeitler)--L0--C0--2026-06-22T21:31:52.553Z

Marcin Borkowski: Disabling minor modes with local variables

I’ve known about file local variables for a long time. I also knew about the eval keyword, which can be used as a file variable, but instead of binding the given value to the (non-existent) variable eval, Emacs evaluates it. This is often useful, but the usefulness is diminished by the fact that Emacs nags the user whether it is safe to evaluate a form provided that way. (Of course, it is reasonable security-wise, and that’s why I didn’t set enable-local-variables to :all – I still prefer the nagging to the risk of executing untrusted code!) There is, however, one case where the eval form seems safe and useful enough to grant an exception: enabling or disabling minor modes.
-1:-- Disabling minor modes with local variables (Post Marcin Borkowski)--L0--C0--2026-06-22T17:19:29.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!