Marcin Borkowski: Lispy and Iedit
-1:-- Lispy and Iedit (Post Marcin Borkowski)--L0--C0--2026-03-02T16:39:08.000Z
-1:-- Lispy and Iedit (Post Marcin Borkowski)--L0--C0--2026-03-02T16:39:08.000Z
When it rains, it pours. Sometimes I find it hard to find an interesting topic to write about. Other times, like today, four or five topics pop up. The problem is that today is Sunday and tomorrow Sacha will be publishing her weekly Emacs News. I generally try not to write about things that she’s already covered but I may have to break that rule for some of these interesting topics.
For me, the most exciting thing I’ve found today is Bozhidar Batsov’s post on Preview Regex Replacements as Diffs. It addresses a problem we’ve all had. You want to do a query-replace-regexp on a large file—or even multiple files—but you’re a bit nervous that maybe your regex isn’t quite right and the command might make a change you don’t want. So you step through each change, which is time consuming and a pain.
As Batsov explains, that got a lot easier in Emacs 30. There’s a new command, replace-regexp-as-diff that runs the regexp replace process but instead of actually making the changes, it produces a diff file in a separate buffer. That way you can see all the changes that would be made. If you’re happy with them, you can simply apply the diff buffer as a patch with diff-ediff-patch to apply them. If you’re not happy, you can simply delete the diff buffer.
There are two related commands: multi-file-replace-regexp-as-diff and dired-do-replace-regexp-as-diff for handling multiple files. The Dired variety is probably the easiest to use because you can simply mark the files you want to change in a Dired buffer and call dired-do-replace-regexp-as-diff to generate a diff for them all.
Batsov speculates that in the age of AI, people won’t be as interested in this type of command. I disagree strongly. It’s useful not only for coding but for writing pose or any other text-based file that you might want to edit.
If you’re an Emacs user, I urge you to take a look at Batsov’s post. It’s about a really useful new(ish) feature of Emacs that you can probably make good use of.
-1:-- A Diff Preview Of A Regex Replace (Post Irreal)--L0--C0--2026-03-02T15:47:35.000Z
Hello folks! Last month's Emacs Carnival about completion had 17 posts (nice!), and Philip Kaludercic is hosting this month's Emacs Carnival: Mistakes and Misconceptions. Looking forward to reading your thoughts!
Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!
You can comment on Mastodon or e-mail me at sacha@sachachua.com.
-1:-- 2026-03-02 Emacs news (Post Sacha Chua)--L0--C0--2026-03-02T15:07:38.000Z
A new Emacs annoyance: org-capture: Capture abort: Invalid function: org-element-with-disabled-cache when I try to use org-capture. Fails the first time, works the second. Where did it come from and how do I get rid of it…?
-1:-- (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-03-02T13:00:20.000Z
-1:-- [RFC] Pros and cons of using LLM for patches beyond simple copyright (was: [PATCH] ob: typed custom vars for org-babel-default-header-args (2 patches attached)) (Post Org Mode requests)--L0--C0--2026-03-01T19:56:58.000Z
There’s the app for health-related records Irreal mentioned the other day, Harp. It’s an org-mode-centered app for Android (soon to be iOS though), which looks pretty basic at this point. You can create several profiles (for different people), and each one has a medical journal and documentation attached, along with some graphs as you accumulate data.
It’s a good idea to have an org-mode-based health app, with all the information you need available to you quickly, protected behind encryption. The issue specific to me is that even though I have a personal Android phone, it’s my iPhone that has my medical apps (part of the Epic suite), as this is the phone I usually carry around with me. These apps already have all my health records, doctors, appointment etc.
I’ve been playing with it a bit, and I think it’s mostly the idea of having my health records saved in org mode that makes sense, especially with the denote file convention system. My Android is also where I have signal, which I can use to share medical records with people close to me, so there’s that. It’s not ideal to carry around two phones, but I think I want to experiment for a bit.
-1:-- Harp is org-mode medical app for Android (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-03-01T18:02:26.000Z
I think about note-taking more than I probably should. I’m thinking about it right now, actually. 😅
It started as a way to understand myself better — to track how my mind worked, what I cared about, where I was going. Somewhere along the way, it became a time machine. I can look back six months, a year, and see myself thinking. Sometimes I cringe at what I wrote. Sometimes I surprise myself with wisdom I’ve since forgotten.
I didn’t expect any of this. I didn’t sit down and design a workflow. It just… happened.
In January, I bought a pack of blank little square papers. Non-sticky, white, nothing fancy. I thought I’d use them for doodling. Like during meetings while half-listening 😁 .
But then a grocery list ended up on one. And some todo items on another. And before long, I was sketching diagrams, mapping out project architectures, jotting down random thoughts across these small white pages.
They kind of took over.
Now I keep three or four squares spread across my desk, each holding a different topic. When something comes to mind, I jump to the right one, write it down, move on. One square for project ideas. Another for a blog draft. A third for whatever’s rattling around.
The square format forces me to be concise — there’s only so much space. And having multiple squares open means I can switch contexts without losing the thread.
It’s simple. It shouldn’t work this well. But it does.
At the end of each day, I go through my squares one by one. It feels like viewing old photographs — each one a small window into what I was thinking earlier.
I regroup similar topics. If something needs more, I flip the square over and keep writing on the back. Tasks get marked done. And when a square is fully processed — completed, digitized, no longer needed — I scrunch it up and drop it in a transparent jar.
The jar idea came from Laurie Herault’s article about sticky notes and feedback loops. There’s something satisfying about watching thoughts become artifacts. The jar fills up. I see how much I’ve processed. It’s a small thing, but it matters.
Google Gemini digitizes my handwritten notes. It’s free, it’s capable, and the process is simple:
Photo. Send. Get text.
Analog becomes digital. Messy becomes searchable. Most of the time, that digitized text ends up in Logseq — where it connects to everything else.
I’m also testing other models on OpenRouter — experimenting for something I’m building. But Gemini handles the job for now.
Here’s what I’ve learned: handwriting is thinking. The pen slows me down, and in that slowness, understanding emerges. AI just ensures I don’t lose what I write. The notes live twice — once on paper where hand meets thought, once digital where search and connection happen.
My current stack is nothing special:
Square papers — for raw capture. One topic per square, spread across my desk.
Logseq — for networked thinking, daily journaling, tracking people. My old banner plugin resurfaces quotes randomly, which keeps old insights alive.
Emacs (Org Mode) — just org-clock-in and org-clock-out. My pomodoro timer, time-blocker, work tracker.
Google Gemini — for digitizing handwriting.
So naturally, I’m building something to bring it all together.
Yes, I know.
Yet another PKM app.
The squares work. The ritual gives closure. The tools hold what matters. But I can’t help wanting something that brings it all together — notes, schedule, time. A tool that resurfaces not just old notes but connections between them.
Proactive insights. Notes that talk back. Patterns I’m too close to see.
The technology exists. I know how to build it.
I just need the time.
If this sounds interesting, stick around. I’ll be writing about the journey.
-1:-- My 2026 Note-Taking Workflow (Post Donovan R.)--L0--C0--2026-03-01T16:31:26.000Z
Over at the Emacs subreddit, alraban tells a nice story about game development with Emacs. The TL;DR is that it’s amazingly good. Alraban isn’t a professional developer but has been a hobbyist since the 80s. He has, several times, tried to write a game but never made anything he felt was performant enough to ship.
Recently he decided to try again. He used the Godot engine because he wanted to work with FOSS tools but he didn’t like the builtin editor or GUI so as a long time Emacs user, he thought he’d give Emacs a try.
Airaban was amazed at how good the experience was. The amazement wasn’t that you could develop games in Emacs—of course you can; people are doing it everyday—but at how good the tooling was and at how smooth and delightful the process was. Even as a long term user who “pretty much live[d] in Emacs” he was surprised at how much tooling there was available and how good it was. As he puts it,
It was like starting a home improvement project I’d never done before, and discovering that I already had all the tools I needed in the basement.
Most of us are pretty familiar with the Emacs tooling associated with our normal tasks. The takeaway from airaban’s post is that you’re apt to be surprised at what’s available when you move to a new task.
This is a short post and only takes a couple of minutes to read. It’s well worth your time.
-1:-- Emacs For Game Development (Post Irreal)--L0--C0--2026-03-01T15:44:18.000Z
Check out all the wonderful entries people sent in for the Emacs Carnival Feb 2026 theme of Completion:
Also, this one about completing the loop:
Sometimes I miss things, so if you wrote something and you don't see it here, please let me know! Please e-mail me at sacha@sachachua.com or DM me via Mastodon with a link to your post(s). If you like the idea but didn't get something together in time for February, it's never too late. Even if you come across this years later, feel free to write about the topic if it inspires you. I'd love to include a link to your notes in Emacs News.
I added a ton of links from the Emacs News archives to the Resources and Ideas section, so check those out too.
I had a lot of fun learning together with everyone. I already have a couple of ideas for March's Emacs Carnival theme of Mistakes and Misconceptions (thanks to Philip Kaludercic for hosting!), and I can't wait to see what people will come up with next!
You can e-mail me at sacha@sachachua.com.
-1:-- Emacs Carnival Feb 2026 wrap-up: Completion (Post Sacha Chua)--L0--C0--2026-03-01T12:37:57.000Z
Emacs has always offered two camps when it comes to long lines: hard wrapping
(inserting actual newlines at fill-column with M-q or auto-fill-mode) and
soft wrapping (displaying long lines across multiple visual lines with
visual-line-mode).1
Hard wrapping modifies the buffer text, which isn’t always desirable. Soft wrapping preserves the text but has always had one glaring problem: continuation lines start at column 0, completely ignoring the indentation context. This makes wrapped code and structured text look terrible.
Emacs 30 finally solves this with visual-wrap-prefix-mode.
When enabled alongside visual-line-mode, visual-wrap-prefix-mode
automatically computes a wrap-prefix for each line based on its surrounding
context. Continuation lines are displayed with proper indentation — as if the
text had been filled with M-q — but without modifying the buffer at all.
The effect is purely visual. Your file stays untouched.
As usual, you can enable the mode for a single buffer:
(visual-wrap-prefix-mode 1)
Or globally:
(global-visual-wrap-prefix-mode 1)
You’ll likely want to pair it with visual-line-mode:
(global-visual-line-mode 1)
(global-visual-wrap-prefix-mode 1)
Note that with visual-line-mode soft wrapping happens at the window edge. If
you’d like to control the extra indentation applied to continuation lines, you
can tweak visual-wrap-extra-indent (default 0):
;; Add 2 extra spaces of indentation to wrapped lines
(setq visual-wrap-extra-indent 2)
Without visual-wrap-prefix-mode (standard visual-line-mode):
Some deeply indented text that is quite long and
wraps to the next line without any indentation, which
looks terrible and breaks the visual structure.
With visual-wrap-prefix-mode:
Some deeply indented text that is quite long and
wraps to the next line with proper indentation,
preserving the visual structure nicely.
If this sounds familiar, that’s because it’s essentially the adaptive-wrap
package from ELPA — renamed and integrated into core Emacs. If you’ve been using
adaptive-wrap-prefix-mode, you can now switch to the built-in version and drop
the external dependency.
As mentioned earlier, I’m not into soft wrapping myself - I hate long
lines and I prefer code to look exactly the same in every editor.
Still, sometimes you’ll be dealing with some code you can’t change,
and I guess many people don’t feel as strongly about cross-editor
consistency as me. In those cases visual-wrap-prefix-mode will
be quite handy!
I have to admit I had forgotten about auto-fill-mode before doing the
research for this article - now I’m wondering why I’m not using it,
as pressing M-q all the time can get annoying.
That’s all I have for you today. Keep hacking!
I’ve always been in the M-q camp. ↩
-1:-- Soft Wrapping Done Right with visual-wrap-prefix-mode (Post Emacs Redux)--L0--C0--2026-03-01T05:39:00.000Z
One of the first use-cases I found for LLMs back when ChatGPT first
released was automating the creation of citations, or rather the
transformations of citations structured in one way into .bib-files
that can be used to create a wide variety of uniform citations in
\(\LaTeX\) documents. LLMs are fantastic for this sort of work, where
some sort of messily structured data needs to be transformed into some
other form that is then useful. As LLMs become cheaper and cheaper it
becomes easier and easier to make data become useful. The benefits of
this is obvious to the point of it being the main strength of what is
perhaps the world’s oldest continuously developed software project;
GNU Emacs.
Much of software engineering is piping that transforms data from one form to another, where we can then process it, ingest it, or present it in some interesting and beneficial way. This is true regardless of the underlying nature of that data. In Emacs there are primarily two forms of this data: lisp code and text. Ardent lisp wizards will object on the basis that one of the core strengths of lisp is that it does not discriminate between lisp as a program qua list of instructions and lisp as a data structure due to its simple syntax.1 The classic UNIX environment also made use of this compatibility made possible by a common language of text.
Emacs is a lisp interpreter that comes with a text editor and tools to evaluate elisp code written in said editor. This simple basis allows Emacs to very quickly and easily be extended. While other programs (and even text editors specifically) may offer theoretically similar capabilities2 through scripting languages and APIs they do not offer the truly free experience that only a few Emacs-like programs build their experience upon. Most code written in Emacs is not packaged or distributed anywhere, but is made up of small and opinionated changes and functions that are likely not maintained in any way. This means that each Emacs user’s computing experience is personally tailored to his or hers own preferences.
The second of the four freedoms (freedom #1) of free software is the “the freedom to study how the program works, and change it to make it do what you wish”. In practice this only means access to the source code under a free-software license. But Emacs takes this much further; instead of a merely “negative” freedom (freedom from proprietary restrictions) it adopts a positive approach, where the user is directly given the tools and documentation3 to change each and every part of the Emacs source code.
Emacs, UNIX, and modern LLMs all make use of the unique strengths of text. Emacs however goes much further in this regard than the standard UNIX system, and in many regards can be seen as an extension and intensification of it. Tietze pointed out recently how the textual representations of almost all data in Emacs is “completes computing” through the universality of text and primarily the text buffer.
If the costs of creating software goes to zero due to continuing advancements in LLMs it would bring this quality that Emacs has to all software. The restrictions of proprietary software has always been an invention by monopolistic software companies wishing to add a shackle on what is really just a bunch of abstract logical statements. That this has been maintained is impressive, but it can not do so under the onslaught of code produced by LLMs.
Trivially creating quick and simple programs that serve the user is Emacs’ greatest strength, and it is something that will be accessible to everyone, no matter their experience in software creation. One will be able to make small little applications that serve yourself, and because of its low cost will naturally freely share them with friends, colleagues, and family members. It is, to repeat a often-used sentiment of mine, a revolution in the field of software development — a dramatic return to the older state of affairs, albeit now aided by the lessons of the time in between. In this case it is a return to the times before “free software”, when specifying that a given software was free to use, share, and modify was not necessary but expected and normal.
To reiterate, vibe coding and LLMs have two great strengths:
The consequence of these to strengths is a renaissance of free software development where the user become free to construct their computing environment however they see fit. “Emacs is a great operating system, if only it came with a decent text editor” goes the famous quip; Emacs is of course not a operating system in the strict sense,4 but it does allow for the almost complete reshaping of one’s interactions with a computer — being able to replace most other use-facing applications. LLMs extend this freedom to outside the frame of Emacs and into almost every part of the software stack. ❦
Simple in the sense of this quote by Leonardo da Vinci:
A poet knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.
This simplicity does not mean that it is impossible to construct elaborate or complex programs in lisp — in fact it is one of the most expressive programming languages. Rather it refers to the basic axioms inferred from the nature of prefix notation and the structure of the abstract syntax tree itself.
What matters in theory is in fact not very interesting. Many languages and processes are Turing-complete, and could thus be used to create any other program, but what actually matters is the ease and manner of creating such a program.
Emacs has extensive manuals, but most of all it is its nature as the “self-documenting editor” that gives it this quality.
In that it does not facilitate the interaction between software and hardware.
-1:-- Vibe-coding Brings the Power of Emacs to Everything (Post Joar von Arndt)--L0--C0--2026-02-28T23:00:00.000Z
If you’ve ever hesitated before running query-replace-regexp across a large
file (or worse, across many files), you’re not alone. Even experienced Emacs
users get a bit nervous about large-scale regex replacements. What if the regex
matches something unexpected? What if the replacement is subtly wrong?
Emacs 30 has a brilliant answer to this anxiety: replace-regexp-as-diff.
Run M-x replace-regexp-as-diff, enter your search regexp and replacement
string, and instead of immediately applying changes, Emacs shows you a diff
buffer with all proposed replacements. You can review every single change in
familiar unified diff format before committing to anything.
If you’re happy with the changes, you can apply them as a patch. If something looks off, just close the diff buffer and nothing has changed.
It gets even better. There are two companion commands for working across files:
multi-file-replace-regexp-as-diff — prompts you for a list of files and
shows all replacements across them as a single diff.dired-do-replace-regexp-as-diff — works on marked files in Dired. Mark the
files you want to transform, run the command, and review the combined diff.The Dired integration is particularly nice — mark files with m, run the command
from the Dired buffer, and you get a comprehensive preview of all changes.
Note to self - explore how to hook this into Projectile.
Say you want to rename a function across your project. In Dired:
m (or % m to mark by regexp)dired-do-replace-regexp-as-diff\bold_function_name\bnew_function_nameNo more sweaty palms during large refactorings.1
I have a feeling that in the age of LLMs probably few people will get excited about doing changes via patches, but it’s a pretty cool workflow overall. I love reviewing changes as diffs and I’ll try to incorporate some of the commands mentioned in this article in my Emacs workflow.
That’s all I have for you today. Keep hacking!
Assuming you’re still doing any large-scale refactorings “old-school”, that is. And that you actually read the diffs carefully! ↩
-1:-- Preview Regex Replacements as Diffs (Post Emacs Redux)--L0--C0--2026-02-28T20:51:00.000Z
I really, really don’t like how I get ebooks onto, and notes off my Supernote e-ink tablet.
I’ve had it for a year now. Great device. But the stuff I create just … is there.
I don’t want to connect to the web UI to push an Upload button to select a file to upload (drag and drop doesn’t work!) just because I’d like to read a new book; and I don’t want to sift through filenames, hastily cobbled together to create a notebook, to get to my sketches and meeting notes. That chore doesn’t sit well with me.
Meanwhile, my Boox devices sync in a weird way, but they sync via WebDAV to my Nextcloud which does the job of giving me access to notes from my Mac. And Calibre Sync works well to download books; I can’t get that app to work on the Supernote though (yet).
Like any sensible person, a year later, I reach for Emacs.
Emacs can SSH into servers and display directory listings in a way that hides from you, the user, the fact that these listings are not from your computer, so you can transparently move files, edit stuff, whatever. It’s a great experience. And I know that there’s ways to make this facility, called TRAMP, speak other protocols than ssh:.
So I ended up with mtp.el to expose mtp: and let me browse files and copy them over, including live previews of images or reading ebooks from inside Emacs via USB on the Supernote. (Not useful, but cool.)
It wasn’t too bad to interface with libmtp or the mtp CLI, so I figured I might want to have a Swift library wrapper instead to help me write more comfortable sync and transfer tools. (And maybe a Mac app while I’m at it that helps with file sync. Not there yet.)
Since that topic is likely to stay with me to transfer files to/from Android, here’s the overview page:
https://christiantietze.de/mtp/
Hire me for freelance macOS/iOS work and consulting.
Buy my apps.
Receive new posts via email.
-1:-- Media Transfer Protocol Tools (Post Christian Tietze)--L0--C0--2026-02-28T20:43:44.000Z
Abhinav Tushar likes to curate what he calls macro health data. That means things like ailments, aches and pains, and other symptoms one might want to mention to the doctor during an appointment. After researching the currently available apps, he realized there was nothing that met his needs so he decided to roll his own. The result is Harp, an Android app that should soon be available on the Play Store. It’s also available for free on Fdroid. You can also checkout the source at Sourcehut. Finally, you can find out more about Harp here.
Like my favorite app Journelly, Tushar decided to keep his data in Org Mode. That, of course, brings the immediate benefit of making the data viewable and editable in Emacs or any other editor for that matter. It’s one of the reasons I’m so fond of Journelly. A couple more apps like these and we could see Org markup evolve into a sort of universal app data storage language.
Right now, only an Android version of Harp exists but his near term plans include an iOS version. That good news for those of us in the Apple camp. The main difficulty appears to be navigating the Apple App Store submission maze, which is well known for its opaque rules.
Take a look at Tushar’s post for some more of his short term goals. It looks like a handy app—and, of course, one that keeps its data in Org mode—so it’s definitely worth trying out. I’ll probably give it a try when the iOS version appears and will let you know what I think of it then.
-1:-- Harp: A Private Health Records App (Post Irreal)--L0--C0--2026-02-28T16:07:44.000Z
This month’s Emacs Asia-Pacific (APAC)
virtual meetup is scheduled for Saturday, February
28, 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, February 28, 2026 (Post Emacs APAC)--L0--C0--2026-02-28T05:14:03.000Z
I’m so happy to be joining this month’s emacs carnival! I love the idea behind these carnivals and I think it’s such a good way of building community virtually.
This month’s topic is Emacs completions. I’m going to share a sort of “hack”… a way that I’ve been able to achieve completions within core org mode.
Org Mode has long supported a template expansion mechanism, some times called “easy templates” or “structured templates”.
The default behavior changed dramatically in version 9.2, and is now built on top of the Emacs builtin tempo.el.
Essentially, you start a new line with <X (where X is a pre-defined key), then hit TAB to have the template expanded.
For example, starting a new line with:
<sand hitting TAB will expand to:
#+begin_src
#+end_srcSuper handy and very easy to remember.
The default are not entirely documented, though most are listed on the Structured Templates manual page.
Listed again here for convenience:
| Key | Expansion |
|---|---|
| <a | #+BEGIN_EXPORT ascii … #+END_EXPORT |
| <c | #+BEGIN_CENTER … #+END_CENTER |
| <C | #+BEGIN_COMMENT … #+END_COMMENT |
| <e | #+BEGIN_EXAMPLE … #+END_EXAMPLE |
| <E | #+BEGIN_EXPORT … #+END_EXPORT |
| <h | #+BEGIN_EXPORT html … #+END_EXPORT |
| <l | #+BEGIN_EXPORT latex … #+END_EXPORT |
| <q | #+BEGIN_QUOTE … #+END_QUOTE |
| <s | #+BEGIN_SRC … #+END_SRC |
| <v | #+BEGIN_VERSE … #+END_VERSE |
In addition to those blocks, there are also some quick tags:
| Key | Expansion |
|---|---|
| <L | #+latex: |
| <H | #+html: |
| <A | #+ascii: |
| <i | #+index: |
| <I | #+include: (will interactively find file to include) |
You can see that there is sort of a convention, uppercase letters usually insert a tag, whereas lowercase letters are mainly for blocks (though it’s definitely not perfect).
Some additional templates can be defined by packages. For example, the org-re-reveal package adds:
| Key | Expansion |
|---|---|
| <n | #+begin_notes ... #+end_notes |
You can add your own tags and blocks. In fact, there is actually no need for the “keys” to single characters.
Adding another tag is very easy, seen here:
(add-to-list 'org-tempo-keywords-alist '("N" . "name"))Which results in this completion:
| Key | Expansion |
|---|---|
| <N | #+name: |
I don’t like the <E hotkey for export block, instead I would like that to be <x.
That is easily added with:
(add-to-list 'org-tempo-tags '("<x" . tempo-template-org-export))You can define your own completion with the tempo-define-template function (see the doc string for full details).
It is very flexible!
You can specify where the cursor (or “prompt”) ends up after the completion, or you can interactively prompt (via the minibuffer) for additional details.
There are more advance features, including auto indentation and dealing with regions.
There is no requirement that these templates be simply blocks or tags. I’ve implemented about 5 custom templates, but here are a few that I think would be most useful for others.
Org headings can have properties, specified by the properties drawer:
:PROPERTIES:
:END:I add this as <p via this implementation code:
(tempo-define-template "org-properties-block"
'(":PROPERTIES:" n
(p) n
":END:" n %))
(add-to-list 'org-tempo-tags '("<p" . tempo-template-org-properties-block))This is one that I use most frequently.
It’s a title block that I start all my org documents with.
It also executes a function call to format-time-string to get today’s date in my preferred format.
By having this completion, all of my org documents get a title with a date and I always know when I started working on a document!
My desired result:
#+title:
#+author: Elsa Gonsiorowski
#+date: February 28, 2026The implementation code:
(tempo-define-template "org-title-block"
'("#+title: " (p) n
"#+author: Elsa Gonsiorowski" n
(concat "#+date: " (format-time-string "%B %e, %Y")) n %))
(add-to-list 'org-tempo-tags '("<t" . tempo-template-org-title-block))I also implement another completion that is a slightly different title block which I use for starting all my blog posts. It includes the all the options that I want by default.
In writing this article I stumbled across orgmode documentation page for Completions.
I had no idea these M-tab completions existed!
(Clearly, since I implemented my own completion for the properties drawer).
I’ll definitely be trying these out.
1 The :properties: keyword can also be added with the M-tab completion on :
-1:-- Emacs Carnival: Org Mode Completions (Post Elsa Gonsiorowski)--L0--C0--2026-02-28T00:00:00.000Z
When I'm writing a journal entry in French, I sometimes want to translate a phrase that I can't look up word by word using a dictionary. Instead of switching to a browser, I can use an Emacs function to prompt me for text and either insert or display the translation. The plz library makes HTTP requests slightly neater.
(defun my-french-en-to-fr (text &optional display-only)
(interactive (list (read-string "Text: ") current-prefix-arg))
(let* ((url "https://translation.googleapis.com/language/translate/v2")
(params `(("key" . ,(getenv "GOOGLE_API_KEY"))
("q" . ,text)
("source" . "en")
("target" . "fr")
("format" . "text")))
(query-string (mapconcat
(lambda (pair)
(format "%s=%s"
(url-hexify-string (car pair))
(url-hexify-string (cdr pair))))
params
"&"))
(full-url (concat url "?" query-string)))
(let* ((response (plz 'get full-url :as #'json-read))
(data (alist-get 'data response))
(translations (alist-get 'translations data))
(first-translation (car translations))
(translated-text (alist-get 'translatedText first-translation)))
(when (called-interactively-p 'any)
(if display-only
(message "%s" translated-text)
(insert translated-text)))
translated-text)))
I think it would be even nicer if I could use speech synthesis, so I can keep it a little more separate from my typing thoughts. I want to be able to say "Okay, translate …" or "Okay, … in French" to get a translation. I've been using my fork of natrys/whisper.el for speech recognition in English, and I like it a lot. By adding a function to whisper-after-transcription-hook, I can modify the intermediate results before they're inserted into the buffer.
(defun my-whisper-translate ()
(goto-char (point-min))
(let ((case-fold-search t))
(when (re-search-forward "okay[,\\.]? translate[,\\.]? \\(.+\\)\\|okay[,\\.]? \\(.+?\\) in French" nil t)
(let* ((s (or (match-string 1) (match-string 2)))
(translation (save-match-data (my-french-en-to-fr s))))
(replace-match
(propertize translation
'type-hint translation
'help-echo s))))))
(with-eval-after-load 'whisper
(add-hook 'whisper-after-transcription-hook 'my-whisper-translate 70))
But that's too easy. I want to actually type things myself so that I get more practice. Something like an autocomplete suggestion would be handy as a way of showing me a hint at the cursor. The usual completion-at-point functions are too eager to insert things if there's only one candidate, so we'll just fake it with an overlay. This code works only with my whisper.el fork because it supports using a list of functions for whisper-insert-text-at-point.
(defun my-whisper-maybe-type-with-hints (text)
"Add this function to `whisper-insert-text-at-point'."
(let ((hint (and text (org-find-text-property-in-string 'type-hint text))))
(if hint
(progn
(my-type-with-hint hint)
nil)
text)))
(defvar-local my-practice-overlay nil)
(defvar-local my-practice-target nil)
(defvar-local my-practice-start nil)
(defun my-practice-cleanup ()
"Remove the overlay and stop monitoring."
(when (overlayp my-practice-overlay)
(delete-overlay my-practice-overlay))
(setq my-practice-overlay nil
my-practice-target nil
my-practice-start nil)
(remove-hook 'post-command-hook #'my-practice-monitor t))
(defun my-practice-monitor ()
"Updates hint or cancels."
(let* ((pos (point))
(input (buffer-substring-no-properties my-practice-start pos))
(input-len (length input))
(target-len (length my-practice-target)))
(cond
((or (< pos my-practice-start)
(> pos (+ my-practice-start target-len))
(string-match "[\n\t]" input)
(string= input my-practice-target))
(my-practice-cleanup))
((string-prefix-p (downcase input) (downcase my-practice-target))
(let ((remaining (substring my-practice-target input-len)))
(move-overlay my-practice-overlay pos pos)
(overlay-put my-practice-overlay 'after-string
(propertize remaining 'face 'shadow))))
(t ; typo
(move-overlay my-practice-overlay pos pos)
(overlay-put my-practice-overlay 'after-string
(propertize (substring my-practice-target input-len) 'face 'error))))))
(defun my-type-with-hint (string)
"Show hints for STRING."
(interactive "sString to practice: ")
(my-practice-cleanup)
(setq-local my-practice-target string)
(setq-local my-practice-start (point))
(setq-local my-practice-overlay (make-overlay (point) (point) nil t t))
(overlay-put my-practice-overlay 'after-string (propertize string 'face 'shadow))
(add-hook 'post-command-hook #'my-practice-monitor nil t))
Here's a demonstration of me saying "Okay, this is a test, in French.":
Since we're faking in-buffer completion here, maybe we can still get away with considering this as an entry for Emacs Carnival February 2026: Completion ? =)
You can e-mail me at sacha@sachachua.com.
-1:-- Using speech recognition for on-the-fly translations in Emacs and faking in-buffer completion for the results (Post Sacha Chua)--L0--C0--2026-02-27T20:11:58.000Z
Yi-Ping Pan has an interesting post that recapitulates one of my favorite hobby horses: Emacs is actually a modern day Lisp Machine that happens to ship with an embedded editor. Pan kept trying other editors but always returned to Emacs. Finally, he stopped treating it as merely a tool and started reading the C source code. What he discovered is what I’ve been preaching for years: Emacs is actually a C-based Lisp interpreter with an embedded text editor.
Pan’s post—the first in a series about Emacs internals—recounts how Emacs grew from a set of TECO macros to a stand alone application built on its own Lisp interpreter. Other editors have tried to recreate this magic but they have all failed because of Greenspun’s Tenth Rule:
Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
Emacs, on the other hand, started with an actual Lisp interpreter and layered a text editor on top of that interpreter. That enables the magic. It’s possible to modify any particular editor function simply by rewriting it in Elisp and adding it to your configuration. Similarly, you can write your own editing—or even general purpose—functions and add them to the Emacs runtime simply by adding them to your configuration.
Pan announced his post over at the Emacs subreddit and, as usual, the comments are instructive. To me, the most interesting comments lamented that Lisp was never able “to fix” the parenthesis problem. I have to admit that it makes me grumpy every time I see someone complaining about parentheses in Lisp. To me, it’s one of Lisp’s successes, not one of its failures. That’s why the planned m-expressions using a more conventional syntax never caught on. Lispers like and prefer s-expressions.
In any event, Pan’s post is worth a couple of minutes of your time. Head over and take a look.
-1:-- Emacs Internals Part 1 (Post Irreal)--L0--C0--2026-02-27T15:20:24.000Z
Over the past year I’ve been spending a lot of time building TreeSitter-powered major modes for Emacs – clojure-ts-mode (as co-maintainer), neocaml (from scratch), and asciidoc-mode (also from scratch). Between the three projects I’ve accumulated enough battle scars to write about the experience. This post distills the key lessons for anyone thinking about writing a TreeSitter-based major mode, or curious about what it’s actually like.
Before TreeSitter, Emacs font-locking was done with regular expressions and indentation was handled by ad-hoc engines (SMIE, custom indent functions, or pure regex heuristics). This works, but it has well-known problems:
Regex-based font-locking is fragile. Regexes can’t parse nested structures, so they either under-match (missing valid code) or over-match (highlighting inside strings and comments). Every edge case is another regex, and the patterns become increasingly unreadable over time.
Indentation engines are complex. SMIE (the generic indentation engine for non-TreeSitter modes) requires defining operator precedence grammars for the language, which is hard to get right. Custom indentation functions tend to grow into large, brittle state machines. Tuareg’s indentation code, for example, is thousands of lines long.
TreeSitter changes the game because you get a full, incremental, error-tolerant syntax tree for free. Font-locking becomes “match this AST pattern, apply this face”:
1
2
3
;; Highlight let-bound functions: match a let_binding with parameters
(let_binding pattern: (value_name) @font-lock-function-name-face
(parameter)+)
And indentation becomes “if the parent node is X, indent by Y”:
1
2
;; Children of a let_binding are indented by neocaml-indent-offset
((parent-is "let_binding") parent-bol neocaml-indent-offset)
The rules are declarative, composable, and much easier to reason about than regex chains.
In practice, neocaml’s entire font-lock and indentation logic fits in about 350
lines of Elisp. The equivalent in tuareg is spread across thousands of lines.
That’s the real selling point: simpler, more maintainable code that handles more
edge cases correctly.
That said, TreeSitter in Emacs is not a silver bullet. Here’s what I ran into.
TreeSitter grammars are written by different authors with different philosophies.
The tree-sitter-ocaml
grammar provides a rich, detailed AST with named fields. The
tree-sitter-clojure grammar,
by contrast, deliberately keeps things minimal – it only models syntax, not
semantics, because Clojure’s macro system makes static semantic analysis
unreliable.1 This means font-locking def forms in Clojure requires
predicate matching on symbol text, while in OCaml you can directly match
let_binding nodes with named fields.
To illustrate: here’s how you’d fontify a function definition in OCaml, where the grammar gives you rich named fields:
1
2
3
;; OCaml: grammar provides named fields -- direct structural match
(let_binding pattern: (value_name) @font-lock-function-name-face
(parameter)+)
And here’s the equivalent in Clojure, where the grammar only gives you lists of symbols and you need predicate matching:
1
2
3
4
;; Clojure: grammar is syntax-only -- match by symbol text
((list_lit :anchor (sym_lit !namespace
name: (sym_name) @font-lock-keyword-face))
(:match ,clojure-ts--definition-keyword-regexp @font-lock-keyword-face))
You can’t learn “how to write TreeSitter queries” generically – you need to
learn each grammar individually. The best tool for this is treesit-explore-mode
(to visualize the full parse tree) and treesit-inspect-mode (to see the node
at point). Use them constantly.
You’re dependent on someone else providing the grammar, and quality is all over the map. The OCaml grammar is mature and well-maintained – it’s hosted under the official tree-sitter GitHub org. The Clojure grammar is small and stable by design. But not every language is so lucky.
asciidoc-mode uses a
third-party AsciiDoc grammar
that employs a dual-parser architecture – one parser for block-level structure
(headings, lists, code blocks) and another for inline formatting (bold, italic,
links). This is the same approach used by Emacs’s built-in markdown-ts-mode,
and it makes sense for markup languages where block and inline syntax are largely
independent.
The problem is that the two parsers run independently on the same text, and they
can disagree. The inline parser misinterprets * and ** list markers as
emphasis delimiters, creating spurious bold spans that swallow subsequent inline
content. The workaround is to use :override t on all block-level font-lock
rules so they win over the incorrect inline faces:
1
2
3
4
5
6
7
8
;; Block-level rules use :override t so block-level faces win over
;; spurious inline emphasis nodes (the inline parser misreads `*'
;; list markers as emphasis delimiters).
:language 'asciidoc
:override t
:feature 'list
'((ordered_list_marker) @font-lock-constant-face
(unordered_list_marker) @font-lock-constant-face)
This doesn’t fix inline elements consumed by the spurious emphasis – that requires an upstream grammar fix. When you hit grammar-level issues like this, you either fix them yourself (which means diving into the grammar’s JavaScript source and C toolchain) or you live with workarounds. Either way, it’s a reminder that your mode is only as good as the grammar underneath it.
Getting the font-locking right in asciidoc-mode was probably the most
challenging part of all three projects, precisely because of these grammar
quirks. I also ran into a subtle treesit behavior: the default font-lock mode
(:override nil) skips an entire captured range if any position within it
already has a face. So if you capture a parent node like (inline_macro) and a
child was already fontified, the whole thing gets skipped silently. The fix is
to capture specific child nodes instead:
1
2
3
4
5
6
;; BAD: entire node gets skipped if any child is already fontified
;; (inline_macro) @font-lock-function-call-face
;; GOOD: capture specific children
(inline_macro (macro_name) @font-lock-function-call-face)
(inline_macro (target) @font-lock-string-face)
These issues took a lot of trial and error to diagnose. The lesson: budget extra time for font-locking when working with less mature grammars.
Grammars evolve, and breaking changes happen. clojure-ts-mode switched from
the stable grammar to the experimental
branch
because the stable version had metadata nodes as children of other nodes, which
caused forward-sexp and kill-sexp to behave incorrectly. The experimental
grammar makes metadata standalone nodes, fixing the navigation issues but
requiring all queries to be updated.
neocaml pins to
v0.24.0 of the
OCaml grammar. If you don’t pin versions, a grammar update can silently break
your font-locking or indentation.
The takeaway: always pin your grammar version, and include a mechanism to
detect outdated grammars. clojure-ts-mode tests a query that changed between
versions to detect incompatible grammars at startup.
Users shouldn’t have to manually clone repos and compile C code to use your
mode. Both neocaml and clojure-ts-mode include grammar recipes:
1
2
3
4
5
6
7
(defconst neocaml-grammar-recipes
'((ocaml "https://github.com/tree-sitter/tree-sitter-ocaml"
"v0.24.0"
"grammars/ocaml/src")
(ocaml-interface "https://github.com/tree-sitter/tree-sitter-ocaml"
"v0.24.0"
"grammars/interface/src")))
On first use, the mode checks treesit-language-available-p and offers to install
missing grammars via treesit-install-language-grammar. This works, but requires
a C compiler and Git on the user’s machine, which is not ideal.2
The TreeSitter support in Emacs has been improving steadily, but each version has its quirks:
Emacs 29 introduced TreeSitter support but lacked several APIs. For instance,
treesit-thing-settings (used for structured navigation) doesn’t exist – you
need a fallback:
1
2
3
;; Fallback for Emacs 29 (no treesit-thing-settings)
(unless (boundp 'treesit-thing-settings)
(setq-local forward-sexp-function #'neocaml-forward-sexp))
Emacs 30 added treesit-thing-settings, sentence navigation, and better
indentation support. But it also had a bug in treesit-range-settings offsets
(#77848) that broke
embedded parsers, and another in treesit-transpose-sexps that required
clojure-ts-mode to disable its TreeSitter-aware version.
Emacs 31 has a bug in treesit-forward-comment where an off-by-one error
causes uncomment-region to leave ` *)` behind on multi-line OCaml comments. I
had to skip the affected test with a version check:
1
2
3
(when (>= emacs-major-version 31)
(signal 'buttercup-pending
"Emacs 31 treesit-forward-comment bug (off-by-one)"))
The lesson: test your mode against multiple Emacs versions, and be prepared to write version-specific workarounds. CI that runs against Emacs 29, 30, and snapshot is essential.
Most TreeSitter grammars ship with .scm query files for syntax highlighting
(highlights.scm) and indentation (indents.scm). Editors like Neovim and
Helix use these directly. Emacs doesn’t – you have to manually translate the
.scm patterns into treesit-font-lock-rules and treesit-simple-indent-rules
calls in Elisp.
This is tedious and error-prone. For example, here’s a rule from the OCaml
grammar’s highlights.scm:
1
2
;; upstream .scm (used by Neovim, Helix, etc.)
(constructor_name) @type
And here’s the Elisp equivalent you’d write for Emacs:
1
2
3
4
;; Emacs equivalent -- wrapped in treesit-font-lock-rules
:language 'ocaml
:feature 'type
'((constructor_name) @font-lock-type-face)
The query syntax is nearly identical, but you have to wrap everything in
treesit-font-lock-rules calls, map upstream capture names (@type) to Emacs
face names (@font-lock-type-face), assign features, and manage :override
behavior. You end up maintaining a parallel set of queries that can drift from
upstream. Emacs 31 will introduce
define-treesit-generic-mode
which will make it possible to use .scm files for font-locking, which should
help significantly. But for now, you’re hand-coding everything.
When a face isn’t being applied where you expect:
treesit-inspect-mode to verify the node type at point matches your
query.treesit--font-lock-verbose to t to see which rules are firing.treesit-font-lock-feature-list.:override, an earlier rule that
already fontified a region will prevent later rules from applying. This can be
intentional (e.g. builtin types at level 3 take precedence over generic types)
or a source of bugs.TreeSitter modes define four levels of font-locking via
treesit-font-lock-feature-list, and the default level in Emacs is 3. It’s
tempting to pile everything into levels 1–3 so users see maximum highlighting
out of the box, but resist the urge. When every token on the screen has a
different color, code starts looking like a Christmas tree and the important
things – keywords, definitions, types – stop standing out.
Less is more here. Here’s how neocaml distributes features across levels:
1
2
3
4
5
(setq-local treesit-font-lock-feature-list
'((comment definition)
(keyword string number)
(attribute builtin constant type)
(operator bracket delimiter variable function)))
And clojure-ts-mode follows the same philosophy:
1
2
3
4
5
(setq-local treesit-font-lock-feature-list
'((comment definition)
(keyword string char symbol builtin type)
(constant number quote metadata doc regex)
(bracket deref function tagged-literals)))
The pattern is the same: essentials first, progressively more detail at higher
levels. This way the default experience (level 3) is clean and readable, and
users who want the full rainbow can bump treesit-font-lock-level to 4. Better
yet, they can use treesit-font-lock-recompute-features to cherry-pick
individual features regardless of level:
1
2
3
4
5
;; Enable 'function' (level 4) without enabling all of level 4
(treesit-font-lock-recompute-features '(function) nil)
;; Disable 'bracket' even if the user's level would include it
(treesit-font-lock-recompute-features nil '(bracket))
This gives users fine-grained control without requiring mode authors to anticipate every preference.
Indentation issues are harder to diagnose because they depend on tree structure, rule ordering, and anchor resolution:
treesit--indent-verbose to t – this logs which rule matched for each
line, what anchor was computed, and the final column.treesit-explore-mode to understand the parent chain. The key question
is always: “what is the parent node, and which rule matches it?”Remember that rule order matters for indentation too – the first matching rule wins. A typical set of rules reads top to bottom from most specific to most general:
1
2
3
4
5
6
7
8
9
10
11
;; Closing delimiters align with the opening construct
((node-is ")") parent-bol 0)
((node-is "end") parent-bol 0)
;; then/else clauses align with their enclosing if
((node-is "then_clause") parent-bol 0)
((node-is "else_clause") parent-bol 0)
;; Bodies inside then/else are indented
((parent-is "then_clause") parent-bol neocaml-indent-offset)
((parent-is "else_clause") parent-bol neocaml-indent-offset)
Watch out for the empty-line problem: when the cursor is on a blank line,
TreeSitter has no node at point. The indentation engine falls back to the root
compilation_unit node as the parent, which typically matches the top-level
rule and gives column 0. In neocaml I solved this with a no-node rule that
looks at the previous line’s last token to decide indentation:
1
(no-node prev-line neocaml--empty-line-offset)
This is the single most important piece of advice. Font-lock and indentation are easy to break accidentally, and manual testing doesn’t scale. Both projects use Buttercup (a BDD testing framework for Emacs) with custom test macros.
Font-lock tests insert code into a buffer, run font-lock-ensure, and assert
that specific character ranges have the expected face:
1
2
3
(when-fontifying-it "fontifies let-bound functions"
("let greet name = ..."
(5 9 font-lock-function-name-face)))
Indentation tests insert code, run indent-region, and assert the result
matches the expected indentation:
1
2
3
4
(when-indenting-it "indents a match expression"
"match x with"
"| 0 -> \"zero\""
"| n -> string_of_int n")
Integration tests load real source files and verify that both font-locking
and indentation survive indent-region on the full file. This catches
interactions between rules that unit tests miss.
neocaml has 200+ automated tests and clojure-ts-mode has even more.
Investing in test infrastructure early pays off enormously – I can refactor
indentation rules with confidence because the suite catches regressions
immediately.
When I became the maintainer of clojure-mode many years ago, I really struggled with making changes. There were no font-lock or indentation tests, so every change was a leap of faith – you’d fix one thing and break three others without knowing until someone filed a bug report. I spent years working on a testing approach I was happy with, alongside many great contributors, and the return on investment was massive.
The same approach – almost the same test macros – carried over directly to
clojure-ts-mode when we built the TreeSitter version. And later I reused the
pattern again in neocaml and asciidoc-mode. One investment in testing
infrastructure, four projects benefiting from it.
I know that automated tests, for whatever reason, never gained much traction in the Emacs community. Many popular packages have no tests at all. I hope stories like this convince you that investing in tests is really important and pays off – not just for the project where you write them, but for every project you build after.
This one is specific to clojure-ts-mode but applies broadly: compiling
TreeSitter queries at runtime is expensive. If you’re building queries
dynamically (e.g. with treesit-font-lock-rules called at mode init time),
consider pre-compiling them as defconst values. This made a noticeable
difference in clojure-ts-mode’s startup time.
The Emacs community has settled on a -ts-mode suffix convention for
TreeSitter-based modes: python-ts-mode, c-ts-mode, ruby-ts-mode, and so
on. This makes sense when both a legacy mode and a TreeSitter mode coexist in
Emacs core – users need to choose between them. But I think the convention is
being applied too broadly, and I’m afraid the resulting name fragmentation will
haunt the community for years.
For new packages that don’t have a legacy counterpart, the -ts-mode suffix is
unnecessary. I named my packages neocaml (not ocaml-ts-mode) and
asciidoc-mode (not adoc-ts-mode) because there was no prior neocaml-mode
or asciidoc-mode to disambiguate from. The -ts- infix is an implementation
detail that shouldn’t leak into the user-facing name. Will we rename everything
again when TreeSitter becomes the default and the non-TS variants are removed?
Be bolder with naming. If you’re building something new, give it a name that makes sense on its own merits, not one that encodes the parsing technology in the package name.
I think the full transition to TreeSitter in the Emacs community will take 3–5 years, optimistically. There are hundreds of major modes out there, many maintained by a single person in their spare time. Converting a mode from regex to TreeSitter isn’t just a mechanical translation – you need to understand the grammar, rewrite font-lock and indentation rules, handle version compatibility, and build a new test suite. That’s a lot of work.
Interestingly, this might be one area where agentic coding tools can genuinely
help. The structure of TreeSitter-based major modes is fairly uniform: grammar
recipes, font-lock rules, indentation rules, navigation settings, imenu. If you
give an AI agent a grammar and a reference to a high-quality mode like
clojure-ts-mode, it could probably scaffold a reasonable new mode fairly
quickly. The hard parts – debugging grammar quirks, handling edge cases, getting
indentation just right – would still need human attention, but the boilerplate
could be automated.
Still, knowing the Emacs community, I wouldn’t be surprised if a full migration never actually completes. Many old-school modes work perfectly fine, their maintainers have no interest in TreeSitter, and “if it ain’t broke, don’t fix it” is a powerful force. And that’s okay – diversity of approaches is part of what makes Emacs Emacs.
TreeSitter is genuinely great for building Emacs major modes. The code is simpler, the results are more accurate, and incremental parsing means everything stays fast even on large files. I wouldn’t go back to regex-based font-locking willingly.
But it’s not magical. Grammars are inconsistent across languages, the Emacs APIs
are still maturing, you can’t reuse .scm files (yet), and you’ll hit
version-specific bugs that require tedious workarounds. The testing story is
better than with regex modes – tree structures are more predictable than regex
matches – but you still need a solid test suite to avoid regressions.
If you’re thinking about writing a TreeSitter-based major mode, do it. The ecosystem needs more of them, and the experience of working with syntax trees instead of regexes is genuinely enjoyable. Just go in with realistic expectations, pin your grammar versions, test against multiple Emacs releases, and build your test suite early.
Anyways, I wish there was an article like this one when I was starting out
with clojure-ts-mode and neocaml, so there you have it. I hope that
the lessons I’ve learned along the way will help build better modes
with TreeSitter down the road.
That’s all I have for you today. Keep hacking!
See the excellent scope discussion in the tree-sitter-clojure repo for the rationale. ↩︎
There’s ongoing discussion in the Emacs community about distributing pre-compiled grammar binaries, but nothing concrete yet. ↩︎
-1:-- Building Emacs Major Modes with TreeSitter: Lessons Learned (Post Bozhidar Batsov)--L0--C0--2026-02-27T08:00:00.000Z
Completion within Emacs is not just about “intellisense” auto-completion as you type, or tab-completion in command prompts. When I think of “completion” within Emacs, I think about all operations within Emacs being (closed over) textual representations.
Text buffers form a complete representation of process interaction in Emacs.
Maybe it’s even accurate to say: Emacs is complete over textual processes. I’m not sure, it sounds almost correct, but I may be missing important nuances. (You tell me in the comments or via email!)
If it’s text, it’s Emacs-able, that’s what I’m getting at. And oh boy can you emacs a lot of textual things in weird ways.
In this post, I’ll share a particular insight that you can not only extend how many processes you can move into Emacs, but also use the textual representation itself again as input to then inform processes to change Emacs further in a feedback loop of textuality.
This is my February Emacs Carnival entry, hosted by Sacha.
At a recent Software Craftsmanship Open Space, I’ve squeezed in as much Emacs weirdness into the description of one workflow as possible. The day was full of excitement about large language models for coding in various flavors, sadly and oddly enough (there’s not much Craftsmanship in that!). Being a good sport, I wanted to show the extent of how Emacs is, in a way, a general purpose text-based abstraction over anything that you pump through its Lispy veins, and how well this composes, and then feeds into anybody’s favorite coding agent of choice if needed.
The example is this:
dired as a wrapper around a mere directory listing with ls on the command line to get both the directory listing and interactivity for each of the file entries (open, preview, mark, copy, …)/ssh:user@example.com/, but otherwise using the normal file opening facilities.Veteran Emacs users will nod along and now see anything particularly exciting. That’s just normal, everyday Emacs.
This particular integration of remote/local file management and connecting to machines at all got me so excited that I became sad that I cannot use this to sync files with my Supernote e-ink tablet, convert the notes to PDF, and sync projects.
So I did the sensible thing: set out and generated mtp.el to make Emacs TRAMP connect to the Supernote via /mtp:...:/... for me. Aafter a couple of hours of research and planning using the mtp CLI behind the scenes (eventually interfacing with libmtp for potentially better performance):
https://codeberg.org/ctietze/mtp.el
In this post, my point is not that this remote connection to a Supernote tablet is possible in this awkwardly niche way by using Emacs directory listings – my point is how I got there.
Everything is in text buffers. That’s the primary abstraction that Emacs offers.
Run emacs -nw and you get a text-only terminal application, a TUI if you will, that presents in a text-only terminal emulator text-only representations of much more complex, much more interactive things. Like files you can ‘click’ on to open them from a directory listing. (In TUI mode, you can’t click and press Return instead, but the idea is the same.) The terminal emulator only sees text. The interpretation, the charade, is all in Emacs.
While the terminal emulator only sees text, well, it sees text at least and not just an image of colorful pixels that require OCR to read and other algorithms I have no clue about to interpret.
Consequently, the directory listing within Emacs showing contents of the Supernote tabled has predicable textual output that you can save to a file for later – as we have established in the beginning.
Hey, did you know that tmux can be remote-controlled with commands to move the cursor to positions and to create snapshots?
I didn’t, but I knew asciinema.org exists and people record their terminal contents to produce replays as ‘videos’ there, so the problem of screen-shotting terminal windows was solved already. (asciinema has a custom frame-by-frame JSON format, it turns out, but still a great start for the research.)
With tmux, you can remote control Emacs in TUI mode and capture the whole ‘screen’ as a text file to then check whether Emacs output is what it is supposed to be from a user’s perspective. (You could also save the buffer contents directly, but that won’t capture minibuffer messages and modeline.)
More detail is in mtp.el’s doc on TUI snapshot testing; the gist for end-to-end testing Emacs via tmux sessions (this one called “test”) is the following:
# Create 'test' tmux session 80x24, launch emacs TUI
tmux new-session -d -s test -x 80 -y 24 "emacs -nw --init-directory=dev ..."
# Send shortcuts and type to execute a command:
tmux send-keys -t test M-x
tmux send-keys -t test 'mtp-list-devices' Enter
sleep 3
# Capture a snapshot as plain text
tmux capture-pane -t test -p
# Cleanup
tmux kill-session -t test
With this, you can do snapshot-based UI tests – as long as the UI you create is in Emacs, and it fits into the text-only TUI mode. Or you just grep as a way to assert that the output contains something specific.
Interfacing with any device or hardware is a fiddly procedure I don’t enjoy much. That’s just not the kind of computing I particularly like.
I do not mind it as much when I have a suite of regression tests that make manual QA testing easier the more edge cases I discover and save as test scenarios to automate-away.
So by closing the feedback loop from tmux to launch emacs -nw to invoking work-in-progress implementations for interactive Supernote directory and file listings o saving snapshots of the results to test automation with “golden files” that in turn can be opened and viewed in Emacs again as text, I found that the true power of Emacs lies in being complete over any process that can be represented in text. Any and all. There can be no gaps, and there are none, and thus the world is in order.
Emacs does not just “complete me” in a cheesy Valentines Day way, it “completes computing” for me by creating a textual (mostly) bottleneck to do tasks, inspect them, copy/store/restore/automate away. Now I also found that Emacs is “complete computing”, can be used for input and output, throughput, make the computer inspect the computer (by virtue of tmux snapshots for example).
Emacs complete.
Hire me for freelance macOS/iOS work and consulting.
Buy my apps.
Receive new posts via email.
-1:-- Emacs Complete: Feedback Loops in Emacs, Feedback Loops in Computing (Post Christian Tietze)--L0--C0--2026-02-27T06:55:07.000Z
I like using the orderless completion package for Emacs because it allows me to specify different parts of a completion candidate than any order I want. Because I'm learning French, I want commands like consult-line (which uses minibuffer completion) and completion-at-point (which uses in-buffer completion) to also match candidates where the words might have accented characters. For example, instead of having to type "utilisé" with the accented é, I want to type "utilise" and have it match both "utilise" and "utilisé".
(defvar my-orderless-accent-replacements
'(("a" . "[aàáâãäå]")
("e" . "[eèéêë]")
("i" . "[iìíîï]")
("o" . "[oòóôõöœ]")
("u" . "[uùúûü]")
("c" . "[cç]")
("n" . "[nñ]"))) ; in case anyone needs ñ for Spanish
(defun my-orderless-accent-dispatch (pattern &rest _)
(seq-reduce
(lambda (prev val)
(replace-regexp-in-string (car val) (cdr val) prev))
my-orderless-accent-replacements
pattern))
(use-package orderless
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles basic partial-completion))))
(orderless-style-dispatchers '(my-orderless-accent-dispatch orderless-affix-dispatch)))
This is an entry for Emacs Carnival February 2026: Completion.
You can comment on Mastodon or e-mail me at sacha@sachachua.com.
-1:-- Emacs completion and handling accented characters with orderless (Post Sacha Chua)--L0--C0--2026-02-26T20:10:16.000Z
-1:-- Emacs Carnival: Completion (Post Eric MacAdie)--L0--C0--2026-02-26T19:18:01.000Z
: Made the code even neater with :key, included the old code as well
At this week's Emacs Berlin meetup, someone wanted to know how to change the order of completion candidates. Specifically, they wanted to list the top level Org Mode headings before the second level headings and so on. They were using org-ql to navigate Org headings, but since org-ql sorts its candidates by the number of matches according to the code in the org-ql-completing-read function, I wasn't quite sure how to get it to do what they wanted. (And I realized my org-ql setup was broken, so I couldn't fiddle with it live. Edit: Turns out I needed to update the peg package) Instead, I showed folks consult-org-heading which is part of the Consult package, which I like to use to jump around the headings in a single Org file. It's a short function that's easy to use as a starting point for something custom.
Here's some code that allows you to use consult-org-heading to jump to an Org heading in the current file with completions sorted by level.
(with-eval-after-load 'consult-org
(advice-add
#'consult-org--headings
:filter-return
(lambda (candidates)
(sort candidates
:key (lambda (o) (car (get-text-property 0 'consult-org--heading o)))))))
My previous approach defined a different function based on consult-org-heading, but using the advice feels a little cleaner because it will also make it work for any other function that uses consult-org--headings. I've included the old code in case you're curious. Here, we don't modify the function's behaviour using advice, we just make a new function (my-consult-org-heading) that calls another function that processes the results a little (my-consult-org--headings).
(defun my-consult-org--headings (prefix match scope &rest skip)
(let ((candidates (consult-org--headings prefix match scope)))
(sort candidates
:lessp
(lambda (a b)
(let ((level-a (car (get-text-property 0 'consult-org--heading a)))
(level-b (car (get-text-property 0 'consult-org--heading b))))
(cond
((< level-a level-b) t)
((< level-b level-a) nil)
((string< a b) t)
((string< b a) nil)))))))
(defun my-consult-org-heading (&optional match scope)
"Jump to an Org heading.
MATCH and SCOPE are as in `org-map-entries' and determine which
entries are offered. By default, all entries of the current
buffer are offered."
(interactive (unless (derived-mode-p #'org-mode)
(user-error "Must be called from an Org buffer")))
(let ((prefix (not (memq scope '(nil tree region region-start-level file)))))
(consult--read
(consult--slow-operation "Collecting headings..."
(or (my-consult-org--headings prefix match scope)
(user-error "No headings")))
:prompt "Go to heading: "
:category 'org-heading
:sort nil
:require-match t
:history '(:input consult-org--history)
:narrow (consult-org--narrow)
:state (consult--jump-state)
:annotate #'consult-org--annotate
:group (and prefix #'consult-org--group)
:lookup (apply-partially #'consult--lookup-prop 'org-marker))))
I also wanted to get this to work for C-u org-refile, which uses org-refile-get-location.
This is a little trickier because the table of completion candidates is a list of cons cells that don't store the level, and it doesn't pass the metadata to completing-read to tell it not to re-sort the results. We'll just fake it by counting the number of "/", which is the path separator used if org-outline-path-complete-in-steps is set to nil.
(with-eval-after-load 'org
(advice-add
'org-refile-get-location
:around
(lambda (fn &rest args)
(let ((completion-extra-properties
'(:display-sort-function
(lambda (candidates)
(sort candidates
:key (lambda (s) (length (split-string s "/"))))))))
(apply fn args)))))
In general, if you would like completion candidates to be in a certain order, you can specify display-sort-function either by calling completing-read with a collection that's a lambda function instead of a table of completion candidates, or by overriding it with completion-category-overrides if there's a category you can use or completion-extra-properties if not.
Here's a short example of passing a lambda to a completion function (thanks to Manuel Uberti):
(defun mu-date-at-point (date)
"Insert current DATE at point via `completing-read'."
(interactive
(let* ((formats '("%Y%m%d" "%F" "%Y%m%d%H%M" "%Y-%m-%dT%T"))
(vals (mapcar #'format-time-string formats))
(opts
(lambda (string pred action)
(if (eq action 'metadata)
'(metadata (display-sort-function . identity))
(complete-with-action action vals string pred)))))
(list (completing-read "Insert date: " opts nil t))))
(insert date))
If you use consult--read from the Consult completion framework, there is a :sort property that you can set to either nil or your own function.
This entry is part of the Emacs Carnival for Feb 2026: Completion.
You can comment on Mastodon or e-mail me at sacha@sachachua.com.
-1:-- Sorting completion candidates, such as sorting Org headings by level (Post Sacha Chua)--L0--C0--2026-02-26T19:08:59.000Z
Although many of us won’t admit it, we all enjoy a bit of nostalgia. Here’s some from 20 to 25 years ago. Kids today
, they don’t know how good they have it. There’s a tendency to romance the “powerful” workstations from the turn of the century. I do the same thing with the Lisp Machines but I was there for those turn-of-the-century workstations and I can tell you from first hand experience that someone sitting in front of a run-of-the-mill laptop running Linux, FreeBSD , or even macOS is having a much better Unix/Emacs experience than the folks sitting in front of one of those old workstations had.
Nevertheless, nostalgia is strong and many would like to experience what it was like. Victor Larios is offering a trip on the tech time machine to experience what it was like back then. The “time machine” consists is a browser based recreation of the Common Desktop Environment (CDE) complete with the then popular XEmacs.
XEmacs was popular then mainly because Gnu Emacs was dragging its feet on adapting to the X Window system and it offered a better experience. I was a Vim user in those days so I can’t speak to its relative merits but all my Emacs-using colleagues were running it. GNU Emacs eventually caught up and surpassed the XEmacs implementation so XEmacs has pretty much faded into oblivion but it was state of the art back then.
In any event, if you sometimes have nostalgia pangs about the “old days” and would like to see was it was like, here’s an easy way to scratch the itch.
-1:-- A Unix/Emacs Blast From The Past (Post Irreal)--L0--C0--2026-02-26T16:07:01.000Z
I play in a chinese orchestra. Lots of chinese folk instruments don’t use western stave notation to record music, instead using something which in chinese is cheekily called simple notation. Some of the sheet music for the cello was only available as simple notation, which consists of numbers which represent the distance the played note is from a starting note. I tried to play it and it was fine for the first piece, but the second piece changed the starting note and i decided it wasn’t so simple after all. So i downloaded musescore and got to work converting it into something i could understand.
The problem i quickly encountered was that musescore is not very good software. Some of that is probable just a product of the fact that engraving software is necessarily quite complex. Other issues are definitely musescore problems. For example, i couldn’t use my chinese input method within the application, which is quite frustrating when the sheet music has chinese text on it. Eventually i had had enough and remembered lilypond, which describes itself as “music notation for everyone” but is basically tex for music, if tex happened to work perfectly out of the box.
So i downloaded it, exported my work from musescore as musicxml and used the little helper program musicxml2ly to convert it to a lilypond file. As is often the way with these conversion programs, the output was a bit of a mess. I don’t know how much is a problem with the tool and how much is a problem with musescore’s musicxml export option, but there were a lot of messy things like rehearsal markers not using the correct lilypond syntax, every note duration being marked, and all the line and page breaks being marked explicitly. Perhaps this is important for some people, but for me i wanted to take advantage of lilypond’s apparently superior engraving, so i spent a couple of hours cleaning up the files.
During this cleanup process, i used frescobaldi, a graphical program for editing lilypond scores. I admit that i was expecting it to be janky and ugly, but i was delighted to be proved wrong. Frescobaldi is a nice qt6 application that loads fast and works well. It’s not wysiwyg, but it does highlight the note at the cursor on the output, has hyperlinked error links, and so on. A really nice program. But one thing that started to frustrate me was that it wasn’t emacs: why is C-s saving my work instead of searching!?
Of course, when i first opened the lilypond score and saw scheme code at the start, i should have known that as a gnu project it would have a comfy emacs mode. Having got to grips with how lilypond worked, i decided to pop into emacs and continue editing there.
There’s not really much more to say. The process was smooth, i can have a two-pane layout with the code on the left and the engraved score on the right which compiles automatically and refreshes as i make changes. I get all the emacs shortcuts i’m familiar with. The music looks great, and i can happily play this music now!
I still don’t think lilypond is for everyone. Although there’s a pretty large crossover between programmers and musicians, i think that a majority of people would still prefer a wysiwyg composition tool. But if you happen to fit into the niche that lilypond is for, you might be surprised at just how modern-feeling the tooling is.
-1:-- Engraving sheet music with lilypond (Post noa ks)--L0--C0--2026-02-26T16:00:00.000Z
Good news, everyone – copilot.el 0.4 is out!
But that’s just the start of it. This is the most important release for me since I assumed the project’s leadership and I hope this article will manage to make you agree with my reasoning.
Enough empty words – let me now walk you through the highlights.
The single biggest change in this release is the migration from the legacy
getCompletions API (reverse-engineered from
copilot.vim) to the standard
textDocument/inlineCompletion LSP method provided by the official
@github/copilot-language-server.
This might sound like a dry and boring internal change, but it’s actually a big deal. copilot.el started its life as a port of copilot.vim – we were essentially reverse-engineering how that plugin talked to the Copilot server and replicating it in Elisp. That worked, but it was fragile and meant we were always playing catch-up with undocumented protocol changes.
Now we speak the official LSP protocol. We send proper textDocument/didOpen,
textDocument/didChange, and textDocument/didFocus notifications. We manage
workspace folders. We handle server-to-client requests like
window/showMessageRequest and window/showDocument. We perform a clean
shutdown/exit sequence instead of just killing the process. In short,
copilot.el is now a proper Copilot LSP client, not a reverse-engineered hack.
This release, in a way, completes the cycle – from a package born out of reverse engineering copilot.vim to a legitimate Copilot client built on the official API.1
But wait, there’s (a lot) more!
You can now choose which AI model powers your completions via M-x
copilot-select-completion-model. The command queries the server for available
models on your subscription and lets you pick one interactively. The selection
is persisted in copilot-completion-model.
The parentheses balancer – the component that post-processes completions in
Lisp modes to fix unbalanced delimiters – got a complete rewrite. The old
implementation counted parentheses as raw characters, which meant it would
“balance” parens inside comments and strings where they shouldn’t be touched.
The new implementation uses parse-partial-sexp to understand the actual
syntactic structure, so it only fixes genuinely unbalanced delimiters.
Whether the balancer will remain necessary in the long run is an open question
– as Copilot’s models get smarter, they produce fewer unbalanced completions.
But for now it still catches enough edge cases to earn its keep. You can
disable it with (setopt copilot-enable-parentheses-balancer nil) if you want
to see how well the raw completions work for you.
Beyond the core API migration, we’ve improved the server communication in several ways:
didChangeStatus notifications show Copilot’s state
(Normal, Warning, Error, Inactive) in the mode-line.$/progress notifications display progress for
long-running operations like indexing.$/cancelRequest so the server doesn’t waste cycles on abandoned work.copilot-on-request and copilot-on-notification
let you hook into any server message.This release adds a proper test suite using buttercup. We went from zero tests to over 120, covering everything from URI generation and position calculation to the balancer, overlay management, and server lifecycle. CI now runs across multiple Emacs versions (27.2 through snapshot) and on macOS and Windows in addition to Linux.
The README got a (almost) complete rewrite – it now covers installation for every popular package manager, documents all commands and customization options, includes a protocol coverage table, and has a new FAQ section addressing the most common issues people run into. Plenty of good stuff in it!
This might sound like a lot of effort for not much user-visible payoff, but when I started hacking on the project:
Anyways, I hope you’ll enjoy the improved documentation and you’ll have easier time setting up copilot.el.
Too many to list individually, but here are some highlights:
copilot-complete now works without copilot-mode enabled (#450)params, fixing authentication on newer server versions (#445)company-mode dependency is gone – no more void-function company--active-p errors (#243)completion-preview-mode (#377)See the full changelog for the complete list.
There’s still plenty of work ahead. We have three big feature branches in the pipeline, all open as PRs and ready for adventurous testers:
If any of these sound interesting to you, please give them a spin and report back. Your feedback is what shapes the next release.
A big thanks to Paul Nelson for contributing several partial acceptance commands and the overlay clearing improvements – those are some of the most user-visible quality-of-life changes in this release. Thanks also to everyone who filed detailed bug reports and tested fixes – you know who you are, and this release wouldn’t be the same without you.
That’s all I have for you today. Keep hacking!
That’s why I dropped the word “unofficial” from the package’s description. ↩
-1:-- copilot.el 0.4 (Post Meta Redux)--L0--C0--2026-02-26T10:00:00.000Z
For those of you following along, Emacs has been crashing on my Mac (but not on my Linux desktop) for a while, but it seemed too random to pinpoint. This led me into looking for the Darwin version in the Emacs build in Emacs for Mac OS (which was what I was using on my Mac), which was a couple of versions behind that of macOS itself.
I went ahead and attempted to use Emacs Plus from Homebrew, as most people commented. I haven’t noticed much of a difference, though personally I do prefer to use Emacs from Homebrew as I do with my other packages, so I stuck with it a bit longer.
Yesterday I encountered a stubborn crash in my journelly.org file. Journelly, which is basically a large org-file with pictures displayed in-line under some headers (you can get an idea of what Journelly is and how I use it here).
I took a picture of the snow outside with my iPhone using Journelly, which saved it to journelly.org with the image attached. On the Mac, every time I went to open the header, Emacs crashed, time after time. I just couldn’t edit that image. In a collapsed state for the header, where the image didn’t show, it was fine. On Linux, when I tried - fine. Oh, and before you ask - I tried this with emacs -Q, and yes, it crashed every single time as well.
The JPG image on my iPhone was a 7MB file with dimensions of 4284 x 5712. I knew from past experience that such large images slow down Emacs (on Linux too), so I shrunk it down to a 700kb file with dimensions of 604 x 640, and launched Emacs again. No problem. Everything was stable. I tried to load Emacs a few more times and it worked each time.
This was my hunch from the beginning - that something is up with images at least on the Mac, and this is proof enough for me. I don’t know exactly at what point Emacs crashes: is it a matter of how many images the org file has? How big are they? A combination of both? But I can tell you it seems to be more about the dimensions of the image in pixels than the file size. This is fine for me, for my journal, I don’t need large high-resolution images anyway; those are uploaded and displayed on my blog and elsewhere. It seems that some folks have encountered similar issues as well, from Reddit and elsewhere.
If you have similar issues and you’re fine with scaling down your images, a good solution is dwim-shell-commands-resize-image-in-pixels, part of the excellent dwim-shell-command package, which can quickly shrink down a large number of images from inside Emacs. I’m using it constantly.
-1:-- I think I found what crashed my Emacs on macOS (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-02-25T13:46:04.000Z
I’ve been using Emacs for over 20 years and I still keep discovering (and rediscovering) comment-related commands and variables. You’d think that after two decades I’d have comments figured out, but it turns out there’s a surprising amount of depth hiding behind a few keybindings.
What prompted this article was my recent work on
neocaml, a tree-sitter based major mode
for OCaml. OCaml uses (* ... *) block comments – no line comments at all –
and that unusual syntax forced me to dig deeper into how Emacs handles comments
internally. I learned more about comment variables in the past few months than in
the previous 20 years combined.
I wrote about comment-dwim back in my Comment Commands
Redux article, but I don’t
think I did it justice. M-; is genuinely one of the most context-sensitive
commands in Emacs. Here’s a breakdown of what it does depending on where you
invoke it:
With an active region: It calls comment-region to comment out the selected
code. But if the region already consists entirely of comments, it calls
uncomment-region instead. So it’s effectively a toggle.1
On an empty line: It inserts a comment (using comment-start and
comment-end) and places point between the delimiters, properly indented.
On a line with code but no comment: It adds an end-of-line comment, indented
to comment-column (default 32). This is the classic “inline comment”
workflow – write your code, hit M-;, type your annotation.
On a line that already has an end-of-line comment: It jumps to that comment
and reindents it. Pressing M-; again just keeps you there.
With a prefix argument (C-u M-;): It kills the first comment on the
current line.
That’s five distinct behaviors from a single keybinding. No wonder people
find it confusing at first. If you want something simpler, comment-line (C-x
C-;, added in Emacs 25.1) just toggles comments on the current line or region
– nothing more, nothing less.
I also wrote about M-j years ago in Continue a Comment on the Next
Line. The
command (comment-indent-new-line2) breaks the current line and
continues the comment on the next line with proper indentation.
For languages with line comments (//, #, ;;), this works great out of the
box – it just inserts the comment delimiter on the new line. But for languages
with block comments like OCaml’s (* ... *), the default behavior is less
helpful: it closes the current comment and opens a new one:
(* some text about something. *)
(* |
What you actually want is to continue the same comment:
(* some text about something.
|
This is controlled by two variables that I suspect most people have never heard of:
comment-multi-line – when non-nil, tells commands like M-j to
continue the current comment rather than closing and reopening it.
comment-line-break-function – the function that M-j actually calls to
do its work. Major modes can set this to customize the line-breaking behavior
inside comments.
In neocaml, I set comment-multi-line to t and provide a custom
comment-line-break-function that uses tree-sitter to find the column where the
comment body text starts, then indents the new line to match:
(setq-local comment-multi-line t)
(setq-local comment-line-break-function #'neocaml--comment-indent-new-line)
The implementation is straightforward – walk up the tree-sitter AST to find the
enclosing comment node, compute the body column from the opening delimiter, and
indent accordingly. Now M-j inside (** documentation *) produces a new line
indented to align with the text after (**.
While I was at it I also had to teach M-q (fill-paragraph) about OCaml
comments. By default, fill-paragraph doesn’t know where a (* ... *) comment
starts and ends, so it either does nothing useful or mangles things.
The fix was setting fill-paragraph-function to a custom function that uses
tree-sitter to find the comment boundaries, narrows to the body text (excluding
the (* and *) delimiters), and fills within that region. The fill prefix is
computed from the body start column so continuation lines align properly:
(* This is a long comment that gets
wrapped at the fill column, with
continuation lines properly
indented *)
Working on all of this made me realize just how many comment-related variables Emacs exposes for major modes to configure. Here are the ones I ended up caring about:
| Variable | Purpose |
|---|---|
comment-start |
Opening delimiter ("(* " for OCaml) |
comment-end |
Closing delimiter (" *)" for OCaml) |
comment-start-skip |
Regexp to skip the opening delimiter |
comment-multi-line |
Continue comments vs. close-and-reopen |
comment-line-break-function |
What M-j calls |
comment-column |
Column for end-of-line comments |
comment-style |
How comment-region formats comments |
fill-paragraph-function |
Custom fill behavior |
Most of these have sensible defaults for line-comment languages, which is why you
can go 20 years without thinking about them. But the moment you deal with block
comments, especially unusual ones like OCaml’s nested (* ... *), you
discover the full machinery.
I find it remarkable that after all these years Emacs can still surprise me. A
handful of comment commands – M-;, M-j, M-q – and a set of buffer-local
variables give you a comment editing experience that’s both powerful and
deeply customizable. Most users never need to think about the underlying
variables, but they’re there when you need them, and they compose nicely.
If you’re a major mode author, pay attention to these variables. Getting comments right is one of those things that users notice immediately when it breaks and never think about when it works.
That’s all I have for you today. Keep hacking!
This is the one behavior of comment-dwim that’s somewhat inconvenient – to uncomment the current line you have to select it first. comment-line (C-x C-;) handles this more gracefully. ↩
In older Emacs versions this was called indent-new-comment-line. The modern name is comment-indent-new-line, but both work. ↩
-1:-- So Many Ways to Work with Comments (Post Emacs Redux)--L0--C0--2026-02-25T09:30:00.000Z
-1:-- 2026-01 Austin Emacs Meetup (Post Eric MacAdie)--L0--C0--2026-02-25T05:29:34.000Z
Time for a new Bending Emacs episode. This one is a follow-up to Episode 10, where we introduced agent-shell.
Bending Emacs Episode 12: agent-shell + Claude Skills
This time around, we explore Claude Skills and how to use them to teach agents Emacs tricks. I built a handful of skills packaged as a Claude Code plugin at github.com/xenodium/emacs-skills.
The skills use emacsclient --eval under the hood to bridge agent work to your running Emacs session:
/dired - Open files from the latest interaction in a dired buffer with marks./open - Open files in Emacs, jumping to a specific line when relevant./select - Open a file and select the relevant region./highlight - Highlight relevant regions across files with a temporary read-only minor mode./describe - Look up Emacs documentation and summarize findings.emacsclient (auto) - Teaches the agent to always prefer emacsclient over emacs.Hope you enjoyed the video!
Liked the video? Please let me know. Got feedback? Leave me some comments.
Please go like my video, share with others, and subscribe to my channel.
As an indie dev, I now have a lot more flexibility to build Emacs tools and share knowledge, but it comes at the cost of not focusing on other activities that help pay the bills. If you benefit or enjoy my work please consider sponsoring the work.
-1:-- Bending Emacs - Episode 12: agent-shell + Claude Skills (Post Alvaro Ramirez)--L0--C0--2026-02-25T00:00:00.000Z
Another short post today but one that some of you may find useful. Over at the Emacs subreddit, James Cherti offers a set of commit hooks for Elisp programmers. There are hooks that check for parenthesis consistency, byte compile to check for compile errors, native compile to check for errors, and even a hook to check for conformance to Elisp indentation conventions.
The project repository is here. It has a little more information on installing and using the hooks but essentially everything you need to know is in the reddit announcement.
If you’re an Elisp programmer and like to run some rudimentary checks before committing your work, you may find Cherti’s project useful. If, like me, you hate being constrained by arbitrary coding format conventions, you can choose to ignore the indentation checks. In any event, if you’re an Elisp programmer, you should take a look at Cherti’s post.
-1:-- Elisp Pre-commit Hooks (Post Irreal)--L0--C0--2026-02-24T16:29:55.000Z
One of the things that has always slightly bothered me about chatting with a local LLM is that it only knows what it was trained on (although I suppose most LLMs are like that) . Ask it about your own codebase, your org notes, your project docs - and it’s just guessing. Well, not anymore! Ollama Buddy now ships with proper Retrieval Augmented Generation support built-in
If you haven’t come across the term before, the basic idea is simple. Instead of asking the LLM a question cold, you first go off and find the most relevant bits of text from your own documents, then you hand those bits to the LLM along with your question. The LLM now has actual context to work with rather than just vibes. The “retrieval” part is done using vector embeddings - each chunk of your documents gets turned into a mathematical representation, and at query time your question gets the same treatment. Chunks that are mathematically “close” to your question are the ones that get retrieved.
In this case, I have worked to keep the whole pipeline inside Emacs; it talks to Ollama directly to contact an embedding model, which then returns the required information. I have tried to make this as Emacs Org-friendly as possible by storing the embedding information in Org files.
You’ll need an embedding model pulled alongside your chat model. The default is nomic-embed-text which is a solid general-purpose choice:
ollama pull nomic-embed-text
or just do it within ollama-buddy from the Model Management page.
The main entry point is M-x ollama-buddy-rag-index-directory. Point it at a directory and it will crawl through, chunk everything up, generate embeddings for each chunk, and save an index file. The first time you run this it can take a while depending on how much content you have and how fast your machine is - subsequent updates are much quicker as it only processes changed files.
Supported file types (and I even managed to get pdf text extraction working!):
.el)pdftotext from poppler-utils installed)Files over 1MB are skipped (configurable), and the usual suspects like .git, node_modules, __pycache__ are excluded automatically.
The index gets saved into ~/.emacs.d/ollama-buddy/rag-indexes/ as a .rag file named after the directory. You can see what you’ve got with M-x ollama-buddy-rag-list-indexes.
One thing I’m quite happy with here is the chunking. Rather than just splitting on a fixed character count, documents are split into overlapping word-based chunks. The defaults are:
(setq ollama-buddy-rag-chunk-size 400) ; ~500 tokens per chunk
(setq ollama-buddy-rag-chunk-overlap 50) ; 50-word overlap between chunks
The overlap is important - it means a piece of information that sits right at a chunk boundary doesn’t get lost. Each chunk also tracks its source file and line numbers, so you can see exactly where a result came from.
Once you have an index, there are two main ways to use it:
M-x ollama-buddy-rag-search - searches and displays the results in a dedicated buffer so you can read through them
M-x ollama-buddy-rag-attach - searches and attaches the results directly to your chat context
The second one is the useful one for day-to-day work. After running it, your next chat message will automatically include the retrieved document chunks as context. The status line shows ♁N (where N is the number of attached searches) so you always know what context is in play. Clear everything with M-x ollama-buddy-clear-attachments or C-c 0.
You can also trigger searches inline using the @rag() syntax directly in your prompt and is something fun I have been working on to include an inline command language of sorts, but more about that in a future post.
The similarity search uses cosine similarity with sensible defaults (hopefully!)
(setq ollama-buddy-rag-top-k 5) ; return top 5 matching chunks
(setq ollama-buddy-rag-similarity-threshold 0.3) ; filter out low-relevance results
Bump top-k if you want more context, lower the threshold if you’re not getting enough results.
Say you’ve been working on a large Emacs package and you want the LLM to help you understand something specific. You’d do:
M-x ollama-buddy-rag-index-directory → point at your project directoryM-x ollama-buddy-rag-attach → type your search query, e.g. “streaming filter process”The LLM now has the relevant source chunks as context and can give you a much more grounded answer than it would cold.
And the important aspect, especially regarding local models which don’t often have the huge context sizes often found in online LLMs is that it allows for very efficient context retrieval.
The whole thing is self-contained inside Emacs, no external packages or vector databases, you index once, search as needed, and the LLM gets actual information rather than hallucinating answers about your codebase or anything else that you would want to ingest and it will hopefully make working with local LLMs through ollama noticeably more useful and accurate.
-1:-- Ollama Buddy v2.5 - RAG (Retrieval-Augmented Generation) Support (Post James Dyer)--L0--C0--2026-02-24T11:50:00.000Z
A few years ago I wrote about setting up Emacs for OCaml
development.
Back then the recommended stack was tuareg-mode + merlin-mode, with Merlin
providing the bulk of the IDE experience. A lot has changed since then – the
OCaml tooling has evolved considerably, and I’ve been working on some new tools
myself. Time for an update.
Here’s what I recommend today:
tuareg-modemerlin-modeThe key shift is from Merlin’s custom protocol to LSP.
ocaml-lsp-server has matured
significantly since my original article – it’s no longer a thin wrapper around
Merlin. It now offers project-wide renaming, semantic highlighting, Dune RPC
integration, and OCaml-specific extensions like pattern match generation and
typed holes. ocaml-eglot is a lightweight Emacs package by
Tarides that bridges Eglot with these OCaml-specific
LSP extensions, giving you the full Merlin feature set through a standardized
protocol.
And neocaml is my own TreeSitter-powered OCaml major mode – modern, lean,
and built for the LSP era. You can read more about it in the 0.1 release
announcement.
First, install the server-side tools:
1
$ opam install ocaml-lsp-server
You no longer need to install
merlinseparately –ocaml-lsp-servervendors it internally.
Then set up Emacs:
1
2
3
4
5
6
7
8
9
10
11
12
;; Modern TreeSitter-powered OCaml major mode
(use-package neocaml
:ensure t)
;; Major mode for editing Dune project files
(use-package dune
:ensure t)
;; OCaml-specific LSP extensions via Eglot
(use-package ocaml-eglot
:ensure t
:hook (neocaml-mode . ocaml-eglot-setup))
That’s it. Eglot ships with Emacs 29+, so there’s nothing extra to install for
the LSP client itself. When you open an OCaml file, Eglot will automatically
start ocaml-lsp-server and you’ll have completion, type information, code
navigation, diagnostics, and all the other goodies you’d expect.
Compare this to the old setup – no more merlin-mode, merlin-eldoc,
flycheck-ocaml, or manual Company configuration. LSP handles all of it
through a single, uniform interface.
neocaml includes built-in REPL integration via neocaml-repl-minor-mode. The
basics work well:
C-c C-z – start or switch to the OCaml toplevelC-c C-c – send the current definitionC-c C-r – send the selected regionC-c C-b – send the entire bufferIf you want utop specifically, you’re still better off using
utop.el alongside neocaml. Its
main advantage is that you get code completion inside the utop REPL within
Emacs – something neocaml’s built-in REPL integration doesn’t provide:
1
2
3
(use-package utop
:ensure t
:hook (neocaml-mode . utop-minor-mode))
This will shadow neocaml’s REPL keybindings with utop’s, which is the
intended behavior.
That said, as I’ve grown more comfortable with OCaml I find myself using the
toplevel less and less. These days I rely more on a test-driven workflow –
write a test, run it, iterate. In particular I’m partial to the workflow
described in this OCaml Discuss
thread –
running dune runtest continuously and writing expect tests for quick feedback.
It’s a more structured approach that scales better than REPL-driven development,
especially as your projects grow.
If you’re an OCaml programmer using Emacs, I’d love for you to take
neocaml for a spin. It’s available on
MELPA, so getting started is just an M-x package-install away. The project is
still young and I’m actively working on it – your feedback, bug reports, and
pull requests are invaluable. Let me know what works, what doesn’t, and what
you’d like to see next.
That’s all I have for you today. Keep hacking!
-1:-- Setting up Emacs for OCaml Development: Neocaml Edition (Post Bozhidar Batsov)--L0--C0--2026-02-24T10:00:00.000Z
Every now and then someone asks me how to learn Vim.1 My answer is always the same: it’s simpler than you think, but it takes longer than you’d like. Here’s my bulletproof 3-step plan.
Start with vimtutor – it ships with Vim and takes about 30 minutes. It’ll
teach you enough to survive: moving around, editing text, saving, quitting. The
essentials.
Once you’re past that, I strongly recommend Practical
Vim by Drew
Neil. This book changed the way I think about Vim. I had known the basics of
Vim for over 20 years, but the Vim editing model never really clicked for me
until I read it. The key insight is that Vim has a grammar – operators
(verbs) combine with motions (nouns) to form commands. d (delete) + w
(word) = dw. c (change) + i" (inside quotes) = ci". Once you
internalize this composable language, you stop memorizing individual commands
and start thinking in Vim. The book is structured as 121 self-contained tips
rather than a linear tutorial, which makes it great for dipping in and out.
You could also just read
:helpcover to cover – Vim’s built-in documentation is excellent. But let’s be honest, few people have that kind of patience.
Other resources worth checking out:
Resist the temptation to grab a massive Neovim distribution like LazyVim on day one. You’ll find it overwhelming if you don’t understand the basics and don’t know how the Vim/Neovim plugin ecosystem works. It’s like trying to drive a race car before you’ve learned how a clutch works.
Instead, start with a minimal configuration and grow it gradually. I wrote about this in detail in Build your .vimrc from Scratch – the short version is that modern Vim and Neovim ship with excellent defaults and you can get surprisingly far with a handful of settings.
I’m a tinkerer by nature. I like to understand how my tools operate at their fundamental level, and I always take that approach when learning something new. Building your config piece by piece means you understand every line in it, and when something breaks you know exactly where to look.
I’m only half joking. Peter Norvig’s famous essay Teach Yourself Programming in Ten Years makes the case that mastering any complex skill requires sustained, deliberate practice over a long period – not a weekend crash course. The same applies to Vim.
Grow your configuration one setting at a time. Learn Vimscript (or Lua if you’re on Neovim). Read other people’s configs. Maybe write a small plugin. Every month you’ll discover some built-in feature or clever trick that makes you wonder how you ever lived without it.
One of the reasons I chose Emacs over Vim back in the day was that I really hated Vimscript – it was a terrible language to write anything in. These days the situation is much better: Vim9 Script is a significant improvement, and Neovim’s switch to Lua makes building configs and plugins genuinely enjoyable.
Mastering an editor like Vim is a lifelong journey. Then again, the way things are going with LLM-assisted coding, maybe you should think long and hard about whether you want to commit your life to learning an editor when half the industry is “programming” without one. But that’s a rant for another day.
If this bulletproof plan doesn’t work out for you, there’s always Emacs. Over 20 years in and I’m still learning new things – these days mostly how to make the best of evil-mode so I can have the best of both worlds. As I like to say:
The road to Emacs mastery is paved with a lifetime of
M-xinvocations.
That’s all I have for you today. Keep hacking!
Just kidding – everyone asks me about learning Emacs. But here we are. ↩︎
-1:-- Learning Vim in 3 Steps (Post Bozhidar Batsov)--L0--C0--2026-02-24T09:00:00.000Z
I’ve developed a new random software testing technique that I believe will become a staple of my software testing going forward. It is both helpful for agentic AI (LLM) software development, where robust software verification is more important than ever, and implemented with the help of LLMs.
More…-1:-- Inverse Random Test Auditing (Post William Gallard Hatch)--L0--C0--2026-02-24T04:00:00.000Z
I’m training to ride RAGBRAI LIII in Iowa in July 2026. RAGBRAI provides a training plan which, if followed, helps riders get ready for the ride.
Being an Emacs Org geek, I grabbed the provided Excel spreadsheet, exported it as a CSV (comma separated values) file, then converted that to an Org table.
The spreadsheet shows two lines per week, one with the planned mileages, and another to record the actual miles ridden. There’s a total mileage in column 6, which is filled in for the planned rides, and you fill in yourself for the actuals.
The first few lines of the spreadsheet look like
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| | 2026 RAGBRAI® Training Plan | | | | |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Week of: | Weekday 1 | Weekday 2 | Saturday | Sunday | Week Total |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| February 9 | 5 miles | 5 miles | 10 miles | - | 20 miles |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Actual Ridden | | | | | |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| February 16 | 10 miles | 10 miles | 10 miles | - | 30 miles |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Actual Ridden | | | | | |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|I wanted to do two things:
Org tables let you specify formulas for cells, columns, and rows. Getting the sum of columns 2 through 5 for column 6 can be done a couple of ways:
$6=$2+$3+$4+$5$6=vsum($2..$5)I used the former for quite a while, until I realized I could use the latter. Either way, it works.
I had difficulty getting the formula for column 7 working how I wanted
it. Org tables can do formulas using Calc syntax, which is what I used
for column 6, or using Emacs Lisp forms as formulas. I used Calc
syntax to get this working. I wanted to calculate $7 as 100 times the
value of @0$6, the current row’s sum of miles ridden, divided by the
value of the previous row’s sum of planned miles. It’s easy enough to
just do something like $7=100*@0$6/@-1$6, but the values on
the planned rides rows were 100 times the planned miles for that week
divided by the actual miles for the previous week, which is usually
some number over 100. And not that interesting to me.
I also got rid of all instances of “miles” in the table. I ended up with weird things like 70.3 miles / miles, and couldn’t figure out how to get Org to simplify this. Getting rid of “miles” in the spreadsheet made things look much better.
I wanted to key on the “Actual Ridden” string in $1. If $1 is “Actual Ridden”, then use the calculation we’ve been discussing. Otherwise, use 100 or show nothing. I had great difficulty making this work with Calc syntax.
So, I tried Emacs Lisp forms as formulas. I ended up with
$7='(if (string-equal $1 "Actual Ridden") (format "%.2f" (* 100 (/ (string-to-number @0$6) (string-to-number @-1$6)))) "")This is a pretty straightforward translation of the percentage
formula, along with using format to only show a couple
of decimal places. In the case of other lines, just return an empty
string. The final formula works great.
-1:-- Calculating RAGBRAI training actual vs. planned mileage (Post Dave's blog)--L0--C0--2026-02-24T00:00:00.000Z
I started out on a journey to try to get sway to create a new
workspace for me, with a “probably” unique name. How could I do that?
sway, like it’s ancestor i3, has an IPC mechanism that
allows you to query and control sway from other programs. You
can play with this with swaymsg. swaymsg "workspace foo" will switch your current display to show workspace “foo”,
creating “foo” if it doesn’t exist. So one interesting way to achieve
my goal is
swaymsg "workspace $(xkcdpass -n 1)"xkcdpass -n 1 prints a single random word from
xkcdpass’ word file.
One problem with random words is that we don’t typically have nice key
bindings in sway to switch to these. You can use the mouse and
click on the workspace name on your bar, but if you’d prefer to only
use your keyboard, too bad.
So I thought to use rofi to show a list of workspaces and let
you select one. rofi has no builtin mode for this, but it does
have a way to add modes using scripts. Such a script has two modes
itself:
rofi allows you to type a new item, so in our case that would
be another way to switch to a new workspace.
So, how do you get the list of workspaces? Again, the sway IPC
mechanism. You can run swaymsg -t get_workspaces to get the
list of workspaces. By default this will pretty print the list of
workspaces. But if you specify -r or --raw or pipe
swaymsg to another program, it outputs JSON. Here’s an
example:
[
{
"id": 6,
"type": "workspace",
"orientation": "horizontal",
"percent": null,
"urgent": false,
"marks": [],
"layout": "splith",
"border": "none",
"current_border_width": 0,
"rect": {
"x": 0,
"y": 30,
"width": 2560,
"height": 1410
},
"deco_rect": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"window_rect": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"geometry": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"name": "1",
"window": null,
"nodes": [],
"floating_nodes": [],
"focus": [
17
],
"fullscreen_mode": 1,
"sticky": false,
"floating": null,
"scratchpad_state": null,
"num": 1,
"output": "DP-9",
"representation": "H[H[emacs]]",
"focused": true,
"visible": true
},
{
"id": 18,
"type": "workspace",
"orientation": "horizontal",
"percent": null,
"urgent": false,
"marks": [],
"layout": "splith",
"border": "none",
"current_border_width": 0,
"rect": {
"x": 2560,
"y": 30,
"width": 1920,
"height": 1170
},
"deco_rect": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"window_rect": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"geometry": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"name": "2",
"window": null,
"nodes": [],
"floating_nodes": [
{
"id": 9,
"type": "floating_con",
"orientation": "none",
"percent": 0.61330662393162383,
"urgent": false,
"marks": [],
"focused": false,
"layout": "none",
"border": "normal",
"current_border_width": 2,
"rect": {
"x": 2878,
"y": 97,
"width": 1284,
"height": 1046
},
"deco_rect": {
"x": 318,
"y": 40,
"width": 1284,
"height": 27
},
"window_rect": {
"x": 2,
"y": 0,
"width": 1280,
"height": 1044
},
"geometry": {
"x": 0,
"y": 0,
"width": 696,
"height": 486
},
"name": "foot",
"window": null,
"nodes": [],
"floating_nodes": [],
"focus": [],
"fullscreen_mode": 0,
"sticky": false,
"floating": "user_on",
"scratchpad_state": "fresh",
"pid": 5998,
"app_id": "foot",
"foreign_toplevel_identifier": "0483dba85d6ad4c7b88b28653765ab03",
"visible": true,
"max_render_time": 0,
"allow_tearing": false,
"shell": "xdg_shell",
"inhibit_idle": false,
"sandbox_engine": null,
"sandbox_app_id": null,
"sandbox_instance_id": null,
"idle_inhibitors": {
"user": "none",
"application": "none"
}
}
],
"focus": [
12,
9
],
"fullscreen_mode": 1,
"sticky": false,
"floating": null,
"scratchpad_state": null,
"num": 2,
"output": "DP-8",
"representation": "H[google-chrome]",
"focused": false,
"visible": true
},
{
"id": 21,
"type": "workspace",
"orientation": "horizontal",
"percent": null,
"urgent": false,
"marks": [],
"layout": "splith",
"border": "none",
"current_border_width": 0,
"rect": {
"x": 2560,
"y": 30,
"width": 1920,
"height": 1170
},
"deco_rect": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"window_rect": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"geometry": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"name": "4",
"window": null,
"nodes": [],
"floating_nodes": [],
"focus": [
24,
25
],
"fullscreen_mode": 1,
"sticky": false,
"floating": null,
"scratchpad_state": null,
"num": 4,
"output": "DP-8",
"representation": "H[V[foot] V[foot]]",
"focused": false,
"visible": false
}
]Phew, that’s a lot for 3 workspaces! The only part I care about in
this case is the “name” for each. We could do some weird stuff with
grep and sed to get the names, or use jq to
extract what we want.
swaymsg -t get_workspaces | jq '.[] | .name'generates
"1"
"2"
"4"
Perfect!
Putting this all together, here’s a shell script
sway_list_workspaces that we can use with rofi to
switch workspaces without using the mouse.
#!/bin/sh
if [ x"$1" = x ]
then
swaymsg -t get_workspaces | jq '.[] | .name'
else
swaymsg "workspace $1" >/dev/null
fiHere’s the rofi command line to use:
rofi -modes 'Workspaces:/home/davemarq/bin/sway_list_workspaces' -show WorkspacesI bind it to “$mod+Shift+w” in my sway config file:
bindsym $mod+Shift+w exec "rofi -modes 'Workspaces:/home/davemarq/bin/sway_list_workspaces' -show Workspaces"
-1:-- A Rofi workspace switcher for Sway (Post Dave's blog)--L0--C0--2026-02-24T00:00:00.000Z
: Added m-x.app examples, moved el-init to AI category, added retrospective link.
Org Mode is a big part of why I enjoy Emacs, so I'm delighted that there's a new release out (Org 9.8). Thanks to all who contributed! If you would like to help out, Ihor is looking for several volunteers who can try to reproduce bugs and do initial feedback on the new patches.
elfeed-summarizeLinks from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!
You can comment on Mastodon or e-mail me at sacha@sachachua.com.
-1:-- 2026-02-23 Emacs news (Post Sacha Chua)--L0--C0--2026-02-23T15:43:31.000Z
Ihor Radchenko writes to tell us about the release of Org Mode 9.8. It’s a pretty big release as you can see from the Changes file. It’s much too long to list the changes here and, of course, not everyone will be interested in all the changes so be sure to take a look to see what’s of interest to you.
As always, we should pause and give thanks to Ihor, Bastien, and the others—see the announcement for a list of those contributing to this release—who work so hard on our behalf without any recompense but that thanks. These guys are heroes and, as I always say, we owe them a beer whenever he find ourselves in the same bar as any of them.
The announcement has a short list of significant updates so if you don’t want to read the whole changes file, that’s a good place to start.
-1:-- Org 9.8 (Post Irreal)--L0--C0--2026-02-23T15:12:26.000Z
Note: As I was preparing tomorrow’s post, I noticed that I forgot to publish today’s. Sorry. Here it is, better late than never.
Over at Meta Redux, Bozhidar Batsov has announced the release of Flycheck 36. It has, he says, been a long time coming but he’s really happy with it.
He’s added a couple of new language checkers—javascript-oxlint and org-lint—fixed a security issue, and added some features to ShellCheck. There are also quality of life improvements and bug fixes. Additionally, he’s updated the app’s Web site.
Take a look at Batsov’s post for all the details. Batsov notes that many people have assumed that Flycheck is essentially dead because of the builtin Flymake’s integration with Eglot but he says that Flycheck is alive and well and continuing to evolve.
If you’re a Flycheck user or interested in it, all this is good news. Batsov has put a lot of work into improving it and has plans for more. He explicitly asks those who want additional features to let him know or better yet to submit a PR. In any event, you should take a look at Batsov’s post to see all the new features and improvements.
-1:-- Flycheck 36 (Post Irreal)--L0--C0--2026-02-22T22:13:38.000Z
The past year has brought a lot of changes, not the least of which is that I'm now working primarily in C after a few years of web development in NodeJS/React/general JavaScript land. There's some irony to this given that in my first PyCon talk back in 2022, I proudly declared that I wanted to write as little C/C++ as possible when reimplementing a constraint library in Python using Cython.
However, I've never been particularly happy with high levels of abstraction and black-box implementations going back to my days of frustration with structural engineering software, so maybe it's fitting now that I'm working at a lower level and pursuing performant programming in C. Of course, the switch has meant working with a completely different toolset than I've used in the past, and learning a lot about Emacs along the way.
I use Emacs 30.2 installed via the Emacs Plus Homebrew tap with native compilation, along with Doom Emacs to configure most of the features that I want to use.
Some of the packages not included in Doom Emacs that I find particularly useful for general development are:
My main mode when working in C is c-ts-mode, which is the C editing mode powered by tree-sitter
I believe installing tree-sitter per language now is much easier than it used to be, as I only needed to install the C grammar as outlined in this Mastering Emacs article per the "Compiling and Installing with the builtin method in Emacs" section. While the notes say that it doesn't work well unless you're using GCC and running Linux, I didn't have any problems with Clang and MacOS.
I still don't think I'm using Tree-sitter to its full capabilities in Emacs, but it has been fun to explore the code base using treesit-explore-mode and treesit-inspect-mode. I'd like to do more with its code navigation and highlighting, as I'm still relying primarily on projectile for code navigation. While researching some of the links for this blog post, I discovered the Combobulate package (disclaimer, from the same author as Mastering Emacs), which looks like it could be helpful.
Unfortunately, I can't use .clang-format, as there is an unorthodox set of formatting requirements that don't fit into any of the common C standards. As a result, I needed to add the following .clang-format to my base directory to avoid significant frustration.
DisableFormat: true
One of the biggest boons for navigating a large, unfamiliar codebase (in a new language!) has been utilizing org mode to its fullest. It's how I write out work plans, write code snippets for investigation or debugging, and add in-line images to track current application state.
Early on, I added a function to copy an org mode link to the current file and line number (appropriately named copy-org-link-to-line). This has made it easy to navigate to functions I need to modify in my work plan and include additional context as my understanding of the codebase improves.
(defun copy-org-link-to-line ()
"Copy an Org mode link to the current file and line number.
Format: [[file+emacs:/absolute/path/to/file.txt::$line_number][file.txt:$line_number]]"
(interactive)
(if-let ((file (buffer-file-name))
(line (line-number-at-pos)))
(let ((org-link (format "[[file+emacs:%s::%d][%s:%d]]" file line (file-name-nondirectory file) line)))
(kill-new org-link)
(message "Copied %s" org-link))
(message "Buffer is not visiting a file")))
I've also struggled with tab/space presentation conflicting across different minor modes (probably an artifact from Doom, which has electric-indent-local-mode and ws-butler-mode), so I have a custom display setting for tabs in c-ts-mode:
(standard-display-ascii ?\t "••••")
dapeI initially loaded and unloaded debugging files drafted in org-mode and loaded just into the command line lldb. This was largely because I couldn't get debugging with dap-mode to work at all with C. Surprise! This was because I use eglot instead of lsp-mode, as eglot seems to have won out for using language servers within Emacs, and dap-mode seems to be tied to lsp-mode.
In the interest of KISS, I opted to just use dape since it uses eglot out of the box.
I have two 'dape-configs set up in my config.el. One for launching/debugging the GUI, and another for launching/debugging a single test suite, which look something like this:
(after! dap-mode
(after! dape
:ensure t
:config
(add-to-list 'dape-configs
`(debug-gui
modes (c-ts-mode c-mode)
ensure dape-ensure-command
command "lldb-dap"
command-cwd "/Users/mclare/workspaces/prog/bin"
:type "lldb-dap"
:request "launch"
:program "prog_dev"
:initCommands ["command source -s 0 .lldbinit"]
:args ["absolute/path/to/test/file"]
)
)
(add-to-list 'dape-configs
`(debug-test
modes (c-ts-mode c-mode)
ensure dape-ensure-command
command "lldb-dap"
command-cwd "/Users/mclare/workspaces/prog/bin"
:type "lldb-dap"
:request "launch"
:program "prog_test"
:initCommands ["command source -s 0 .lldbinit"]
:args ["--test_suite" "$test_suite_name"]
)
)
)
)
After my frustration trying to configure dap-mode, dape just worked! By default, the run adapter fields are easy to figure out before transferring that information to a dape-config as I did.
The dape-many-windows buffer layout is intuitive and easy to work with, providing immediate insight with 3 smaller buffers for Local/Global/Registers/Watch variables, the Stack/Modules/Sources, and Breakpoints/Threads, as well as positioning the dape-repl in a buffer at the bottom of the screen.

Dape Many Windows (from readme)
lldb shortcuts do not work in the provided dape-repl. This can get annoying with having to write out breakpoint --file file.c --line $line_number --condition "i == 200" over and over, rather than the shorthand br l c.
Watch variables or conditional statements set in the repl do not appear in the Breakpoints/Threads window, where you can easily delete them, which can be confusing while debugging.
It also seems to be impossible to "reset" breakpoint counts set using the dape command M-x M-a b (These are the ones that populate in the Breakpoint/Threads buffer) wwithout manually visiting the file. I've had to navigate to the breakpoint location and then toggle on/off to achieve this.
Editing watch variables via dape-info-watch-edit-node is really weird. I can't figure out how to get it to commit my changes despite the hints indicating it should just be C-c C-c.
Eglot as a process disconnects a lot. I don't know if this is due to dape or just in general while working in my project. I'd say 80% of the time I reach for xref-find-definition, I get a warning that eglot is not connected, which is very frustrating. (I don't get an error upon firing eglot-reconnect though!).
Especially in the age of LLMs, I'm becoming more and more leery of trusting online tutorials or resources. Since I'm working in C99, it seemed like a good investment to get some C standard texts, since the language is small enough to fit into pretty slim volumes.
Before starting my new job I read:
Effective C - though this was a bit of a challenge because it includes updates to cover all the way to C23!
The C Programming Language (also known as K&R) - skimmed this one mostly since I've heard the style is outdated
Since starting, I've also acquired:
and just for fun (related but not specifically C)...
-1:-- Six Months of C (Post Maryanne Wachter)--L0--C0--2026-02-22T16:07:40.000Z
I’ve finally published Gnosis version 0.7.0 that brings some much
needed changes and integration with org-gnosis. You can get the
latest version of gnosis via GNU ELPA or directly from upstream.
r), with configurable
forward-link and backlink depth (R or C-u r).gnosis-review-topic accepts separate forward/backlink depth to
include themata from related nodes.gnosis-save-hook runs after saving a thema, called with the
thema ID.gnosis-links-check, gnosis-links-sync).gnosis-center-content-during-review option.#+THEMATA: header with thema count.Yes No instead of 1 0.gnosis-algorithm-synolon-max (default 3.0),
floor at 1.3.gnosis-monkeytype hook leak (now buffer-local).vc-pull to reopen database properly after successful pull.emacsql-sqlite-builtin to fix cl-generic dispatch crash
on Emacs 29+.org-gnosis
(write notes, create themata, link, review).-1:-- Gnosis 0.7.0 Release Notes (Post Thanos Apollo)--L0--C0--2026-02-21T22:00:00.000Z
I just pushed a new version for org-gnosis, the note taking module of gnosis, which you can find here. The update should be available to all users via GNU ELPA.
mtime and hash for both
nodes and journal files.org-gnosis-database-file custom variable to specify custom
database file location.org-gnosis-db-sync with GC
optimization during sync.mtime check, then accurate hash
comparison.
org-gnosis-db-sync with prefix arg
C-u. e.g C-u M-x org-gnosis-db-sync RET.org-gnosis-journal-file as level 1 headings.
nil, creates separate files for each date entry.org-gnosis-journal-find for an
event, non-date) creates separate files in org-gnosis-journal-dir
regardless of this setting.org-gnosis-journal-as-gpg that when non-nil journal files
will be created as gpg encrypted files.org-gnosis-get-nodes-data for retrieving node information
with backlink counts.-1:-- Org Gnosis 0.2.0 Release Notes (Post Thanos Apollo)--L0--C0--2026-02-21T22:00:00.000Z
-1:-- Org links’ targets (Post Marcin Borkowski)--L0--C0--2026-02-21T17:07:05.000Z
The fates are still punishing me for leaving home for travel. Today I had two errands to run:
The DMV is, of course, a red flag but it turned out taking 3 hours. Three hours at the DMV is no one’s idea of a good time. After I staggered out of the DMV I went to my provider to drop off the TV set top box. That should take about 5 minutes, right? Silly you and me. That took an hour and a half.
In any event, I’m exhausted and ready for bed so today’s post is going to be ultrashort. The other day I wrote about Prot’s post on using --init-directory as an aid in debugging. Serendipitously, I ran across this post about ready-player freezing. The developer, Álvaro Ramírez, asks the user to provide information on his configuration and Emacs version.
This is a perfect example of where Prot’s advice would be useful. The user could easily build and install a temporary init.el to eliminate everything but what’s needed to produce the problem. Doing so might even allow the user to discover the problem on their own.
-1:-- A Case In Point (Post Irreal)--L0--C0--2026-02-21T17:02:04.000Z
It’s been a while since Flycheck 35, but Flycheck 36 is finally here! This is a pretty big release with a lot of new features, bug fixes, and some much-needed cleanup of legacy checkers.
In fact, I’d say it’s the biggest and most important Flycheck release since I became the project’s primary maintainer a couple of years ago. For a while I had mostly gone with the flow (adding/improving linters and fixing bugs), but by now I feel more confident to make bigger and bolder changes.
Anyways, you’re probably interested to learn more about Flycheck 36, so let me walk you through the highlights.
We’ve added a couple of new checkers:
javascript-oxlint – a checker using oxlint, the
blazing-fast JavaScript/TypeScript linter written in Rust. If you haven’t
tried oxlint yet, you really should – it’s impressively fast.org-lint – an Org mode checker using Emacs’ built-in
org-lint. It detects issues
like invalid links, dead links, and duplicate IDs. This one went through a
rewrite mid-cycle to run in the current Emacs process instead of a --batch
subprocess, which eliminates false warnings for source block languages
provided by external packages.We’ve mitigated
CVE-2024-53920 in the
emacs-lisp checker by disabling local eval directives and restricting local
variables to safe values during byte-compilation. This was an important fix –
byte-compilation involves macro expansion, which means untrusted Elisp files
could potentially execute arbitrary code during syntax checking. A follow-up fix
for Emacs 30+ using trusted-content is in the works.1
The sh-shellcheck checker got some love in this release:
flycheck-shellcheck-infer-shell option to let ShellCheck auto-detect the shell dialectflycheck-shellcheck-args for passing extra command-line argumentsflycheck-shellcheck-enabled-checks to enable optional checks via --enableA bunch of small but welcome improvements:
flycheck-command-map now works as a prefix command with keymap-set and friends – no more need for the define-key workaroundrevert-buffer, so global-auto-revert-mode users get up-to-date diagnostics without manual interventionerrors|warnings|infos)python-ruff checker got an error explainer, so you can quickly look up what a rule meansreportGeneralTypeIssues) now show up as error IDsThis release includes a massive number of bug fixes – over 25 of them. Some highlights:
LC_ALL=C for checker processesrust checker on Windows (no more /dev/null errors)flycheck-navigation-minimum-level being ignored in some casespython-ruff, tex-chktex, emacs-lisp, and awk-gawk checkersCheck the release notes for the full list.
We’ve removed a number of checkers for tools that have been dead or deprecated for years:
typescript-tslint (deprecated since 2019, use ESLint with typescript-eslint)sass, scss, sass/scss-sass-lint, scss-lint (Ruby Sass is dead, use Stylelint)eruby-erubis, eruby-ruumba (Erubis abandoned since 2011)css-csslint (abandoned since ~2017, use Stylelint)coffee-coffeelint, protobuf-prototool, nix-linterIf you’re still using any of these tools… well, it’s probably time to upgrade!
flycheck.org got a visual refresh – we’ve switched from the old Alabaster Sphinx theme to Furo, which gives the site a clean, modern look with proper dark mode support and better mobile responsiveness. We’ve also added prominent GitHub links at the top of every page, making it easier to jump to the source or file issues.
I hate the old theme, so this one is a major win in my book! I’ve also took some effort to make sure the documentation accurately reflects the current state of project, as here and there things were out-of-sync.
I know that many people wrote Flycheck off when Flymake got tight integration with Eglot (they share a maintainer, after all), and I understand why – it’s hard to compete with something that’s built into Emacs and “just works” out of the box. But Flycheck continues to advance and evolve. We have excellent Eglot support via flycheck-eglot, a massive library of built-in checkers,2 and a feature set that Flymake still can’t match in many areas.
More importantly, Flycheck’s biggest strength has always been that it’s community-driven and easy to contribute to. Adding a new checker is straightforward, PRs get reviewed quickly, and you don’t have to go through the FSF copyright assignment process. That matters, and it’s why the project keeps growing.
I’m pretty happy with this release. The combination of new features, security hardening, and cleanup of dead checkers puts Flycheck in a much healthier state. Looking ahead to Flycheck 37, I’m thinking about tree-sitter integration, more new checkers (OCaml and Swift are already in the pipeline), and potentially dropping support for Emacs 27 so we can take advantage of newer APIs. The plan is still hazy, but it’s starting to take some shape…
As always, contributions are very welcome! If there’s a lint tool you use that Flycheck doesn’t support yet, PRs are the fastest way to make it happen. As mentioned above, Flycheck’s biggest strength remains its community, and are the community.
That’s all I have for you today. Keep hacking!
See https://www.flycheck.org/en/latest/languages.html for the full list. ↩
-1:-- Flycheck 36 (Post Meta Redux)--L0--C0--2026-02-21T13:20:00.000Z
Almost 3 years after the rebirth of adoc-mode I’m happy to announce that adoc-mode 0.8 is finally out! This is a massive release that has been cooking for way too long, but I think it was worth the wait.
Let me walk you through the highlights.
This was the flagship feature I teased at the end of my 0.7 announcement, and
it’s easily the most impactful change in this release. Source code blocks now get
fontified using the appropriate language major mode – Ruby code looks like it
does in ruby-mode, Python code looks like python-mode, and so on.
The feature is enabled out-of-the-box with a sensible default – only code blocks
of 5000 characters or fewer are fontified natively, to avoid performance
issues with very large blocks. You can set adoc-fontify-code-blocks-natively
to t if you want unlimited fontification, or to nil to disable it entirely.
You can now see images directly in your buffer instead of just staring at
image::path/to/screenshot.png[]. Image previews are displayed automatically
when you open a file (controlled by adoc-display-images), and you can toggle
them on and off with adoc-toggle-images.
Right-clicking on an image link gives you a context menu to generate or remove
the preview for that specific image. Remote images are supported too – set
adoc-display-remote-images to t if you want to fetch and display images from
URLs.
A couple of welcome additions here:
adoc-follow-thing-at-point (bound to C-c C-o and M-.) lets you follow
URLs (opens in browser), include:: macros (opens the referenced file), and
cross-references (jumps to the anchor). It’s one of those features that once
you have it, you can’t imagine living without.adoc-goto-ref-label (C-c C-a) jumps to an anchor by ID, with a smart
default pulled from the xref at point.imenu index is now hierarchical – headings are nested under their parent
sections, which makes navigating large documents much more pleasant.adoc-mode now highlights Asciidoctor-specific inline macros like kbd:[],
btn:[], menu:[], pass:[], stem:[], latexmath:[], and asciimath:[].
These were previously ignored by font-lock and would just blend in with regular
text.
I’m a heavy user of Antora, and I really appreciate this one.
This release squashes a lot of long-standing bugs:
auto-fill-mode from breaking section title lines.I’ve also spent some time cleaning up the internals:
adoc-mode-image.el and adoc-mode-tempo.el), making the main file more
manageable.The codebase is in much better shape than it was 3 years ago, but there’s still some room for improvement.
I did eventually act on that idea I mentioned in my 0.7 post – I created
asciidoc-mode, a tree-sitter-based
AsciiDoc major mode for Emacs 30.1+. It’s a very different beast from adoc-mode
– focused on the essentials (highlighting, navigation, folding) and leveraging
tree-sitter for accurate and performant parsing.
If you’re on Emacs 30+ and prefer a lighter-weight editing experience,
asciidoc-mode might be a good fit. If you want the full kitchen sink –
image previews, tempo templates, native code block highlighting – adoc-mode is
still the way to go. Both packages are maintained by me and they complement each
other nicely.
adoc-mode 0.8 is available on NonGNU ELPA, MELPA Stable,
and MELPA. Upgrading is just a M-x package-install away.
I’d like to thank everyone who contributed bug reports, pull requests, and feedback over the past 3 years. Open-source is a team sport and this release wouldn’t have happened without you. You rock!
That’s all I have for you today. Keep hacking! And keep writing… in AsciiDoc, of course!
-1:-- adoc-mode 0.8 (Post Meta Redux)--L0--C0--2026-02-21T10:15:00.000Z
Most Emacs Org users would concur that Org mode is a magnificent tool for capturing and communicating thought. That said, Org mode’s vast set of features can be daunting to master. A common guidance for new users is to take it slow: incrementally learn a subset of Org’s features as you need them.
A big reason for Org mode’s steep learning curve is that it adopts Emacs’ unfortunate culture of compelling users to memorize keybindings. Learning a distinct keybinding for each Org command (remember I said vast feature set?) is onerous, so a different tack is made: reuse the same keybinding but have it possess different behavior based on context. This context is usually tied to the type of structure the point (aka cursor) is in. For example, if the point is in a source block, the binding C-c C-c would execute it, but if the point is on a checkbox item, then C-c C-c would toggle its checked state. Taking this approach lowers the effort to recall a keybinding at the cost of recalling what its contextual behavior would be. In practice, using such overloaded keybindings is…okay. But I’d argue that we could have a more usable interface, leading to the point of this post:
Announcing support for Org mode in the v2.14.0 update for Casual, now available on MELPA.
Readers of this blog will know that Casual is my project to re-imagine the primary user interface for Emacs using keyboard-driven menus. If this is new to you, I highly recommend reading this introduction to it.
Primary to the design of the Casual Org menus is to be context-sensitive, only showing a subset of Org mode commands that are relevant. This set of context-sensitive commands is opinionated, but seeks to provide utility to users both new and experienced with Org.
Shown below is a demo of Casual Org at work:
While the design of this UI has been months in the making, there is nothing like real-world use and feedback. Constructive input is appreciated, especially if you are relatively new to using Org.
A great deal of thanks goes out to the maintainers of and contributors to Org mode and to the community that uses it. If you are able, please support it. Also, if you find Casual useful, I’d appreciate a coffee as well.
—
Video music: Burna Boy - Last Last (JON REYES FINDAWAY BLEND) | Jon Reyes
-1:-- Announcing Casual Org (Post Charles Choi)--L0--C0--2026-02-19T23:00:00.000Z
The standard advice people get when they complain about some bug or difficulty with Emacs is to restart it with -q or -Q to see if the problem persists. The problem with that advice is that the difficulty may be with a package. In that case you want to load at least the offending package and its configuration. That used to involve moving or renaming your init.el so that you could install the minimal init.el that produced the problem. As of Emacs 29 that got easier with the introduction of the --init-directory parameter that tells Emacs to load it’s configuration from a different directory.
Protesilaos Stavrou (Prot) has a nice post that explains how to use this feature when you experience a problem and want to file a bug report. The main problem, of course, is what to put in your new init.el. Prot recommends using use-package with :ensure t (and whatever configuration you’re using) to load the package.
Sometimes you might have to build the package from source rather than simply loading it from one of the ELPA repositories. Prot discusses how to do this using package-vc-install to grab the source directly from its git repository. He has some sample code showing how to do this.
Finally, he has some general recommendations that you can use to make the developer’s life a bit easier. On the other hand, if you are the package developer, the --init-directory trick can save a lot of time in locating bugs. In either case, take a look at Prot’s post. There’s a lot of good information in it.
-1:-- Using –init-directory To Debug (Post Irreal)--L0--C0--2026-02-19T17:30:42.000Z
-1:-- Introducing ~elfeed-summarize~ (Post Fritz Grabo)--L0--C0--2026-02-19T00:00:00.000Z
Switched to emacs-plus to try it again for a few days. Darwin version is now up to date. Let’s see what the differences are, if there are indeed any - and of course, if things keep crashing.
-1:-- (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-02-18T20:23:07.000Z
Much of the maintenance work I do for my packages involves correspondence with users about potential bugs. Sometimes, a user will encounter a problem that I cannot reproduce on my end. I thus try to recreate the bug in a pristine environment and ask my correspondent to do the same.
This has become easier since Emacs 29, which introduced a command-line
flag called --init-directory. It is responsible for loading the
init.el that is present in the given directory. For example:
# From a terminal or shell, run something like this:
emacs --init-directory=/tmp/test-emacs/
In other words, you can keep your regular configuration intact while launching Emacs with another set of options.
Have a directory that is unrelated to your regular Emacs
configuration. Then write the init.el inside of it.
Because I do this frequently, I prefer to use the standard Linux path
/tmp/. Its files get deleted as soon as I switch off the computer,
which is exactly what I want in this case.
As such, if there is a bug with, say, the modus-themes, I will work
with this file path /tmp/modus-themes/init.el.
But the exact location of the directory does not matter, so choose what makes sense to you.
In that init file, include only the code that is needed to reproduce the bug.
Since you want to have the package installed, it makes sense to write
a use-package declaration for it. Include the :ensure t directive
as it instructs the built-in package manager to install the package if
it is not already available.
;; Contents of the init.el...
(use-package modus-themes
:ensure t
:config
(setq modus-themes-common-palette-overrides
'((fringe unspecified)
(border-mode-line-active unspecified)
(border-mode-line-inactive unspecified)))
(setq modus-vivendi-palette-overrides
'((bg-main "#1e1f22")
(fg-main "#bcbec4")))
(load-theme 'modus-vivendi t))
If you are using an alternative to package.el like straight or
elpaca, then the aforementioned :ensure t will likely not suffice:
you need to build the package from source. To this end, Emacs has the
function package-vc-install. Some of my recent packages have sample
code that relies on this approach. For instance:
(use-package gnome-accent-theme-switcher
:demand t
:init
;; Then upgrade it with the command `package-vc-upgrade' or `package-vc-upgrade-all'.
(unless (package-installed-p 'gnome-accent-theme-switcher)
(package-vc-install "https://github.com/protesilaos/gnome-accent-theme-switcher.git"))
:bind
(("<f5>" . gnome-accent-theme-switcher-toggle-mode)
("C-<f5>" . gnome-accent-theme-switcher-change-accent))
:config
(gnome-accent-theme-switcher-mode 1))
In the above snippet package-vc-install will pull the latest commit
from the main branch, though it can even get a specific commit. Read
its documentation with M-x describe-function.
What matters is that you fetch the version which you are running in your personaly configuration.
From the command-line, run something like the following:
emacs --init-directory=/tmp/test-emacs/
This will launch a new instance of Emacs. The use-package you placed
there will do the work to install the package. After that you are
ready to reproduce the bug in this clean setup.
To help the maintainer identify the source of the trouble, keep a record of all the steps you followed. Some bugs show up when the package is loaded, but others are triggered only after a specific action is performed.
Normally, Emacs will pop up a *Backtrace* buffer when it encounters
an error. Copy its contents and send them to the maintainer, together
with the init.el you used, and the list of the steps you followed.
It sometimes happens that you install a package and it is completely broken. Although this looks bad, it may not even be a bug, but an issue with the old bytecode you had on your system from the previous version of the package.
Do M-x package-delete, select the package, restart Emacs, and then
M-x package-install to install the package anew. If everything
works, then the problem is gone and you do not need to tell the
maintainer about it.
With this knowledge, you can provide high quality bug reports for the packages you rely on. Good luck!
-1:-- Emacs: confirm package bugs with –init-directory (Post Protesilaos Stavrou)--L0--C0--2026-02-18T00:00:00.000Z
At times, even purchased music excludes album covers in track metadata. For those instances, ready-player-mode offers M-x ready-player-download-album-artwork, which does as it says on the tin. The interactive command offers a couple of fetching providers (iTunes vs Internet Archive / MusicBrainz) to grab the album cover. The thing is, I often found myself trying one or the other provider, sometimes without luck. Today, I finally decided to add a third provider (Deezer) to the list. Even then, what's the point of manually trying each provider out when I can automatically try them all and return the result from the first successful one? And so that's what I did.
In addition to offering all providers, M-x ready-player-download-album-artwork now offers "Any", to download from the first successful provider. Now, why keep the option to request from a specific provider? Well, sometimes one provider has better artwork than another. If I don't like what "Any" returns, I can always request from a specific provider.
While on the subject, I also tidied the preview experience up and now display the thumbnail in the minibuffer. In any case, best to show rather than tell.

Enjoying your unrestricted music via Emacs and ready player mode? ✨sponsor✨ the project.
-1:-- Ready Player cover download improvements (Post Alvaro Ramirez)--L0--C0--2026-02-18T00:00:00.000Z
I've used projectile ever since I created my own Emacs config. I have a vague memory choosing it because some other package only supported it. (It might have been lsp-mode, but I'm not sure.) Anyway, now that I'm trying out eglot, again, I thought I might as well see if I can switch to project.el, which is included in Emacs nowadays.
Projectile allows using a file, .projectile, in the root of a project. This
makes it possible to turn a folder into a project without having to use version
control. It's possible to configure project.el to respect more VC markers than
what's built-in. This can be used to define a non-VC marker.
(setopt project-vc-extra-root-markers '(".projectile" ".git"))
Since I've set vc-handled-backends to nil (the default made VC interfere
with magit, so I turned it off completely) I had to add ".git" to make git
repos be recognised as projects too.
The first thing to solve was that the xref stack wasn't per project. Somewhat
disappointingly there only seems to be two options for xref-history-storage
shipped with Emacs
xref-global-historyxref-window-local-historyI had the same issue with projectile, and ended up writing my own package for it. For project.el I settled on using xref-project-history.
(use-package xref-project-history
:ensure (:type git
:repo "https://codeberg.org/imarko/xref-project-history.git"
:branch "master")
:custom
(xref-history-storage #'xref-project-history))
Projectile has a function for jumping between implementation and test. Not too
surprisingly it's called projectile-toggle-between-implementation-and-test. I
found some old emails in an archive suggesting that project.el might have had
something similar in the past, but if that's the case it's been removed by now.
When searching for a package I came across this email comparing tools for
finding related files. The author mentions two that are included with Emacs
ff-find-other-filefind-sibling-fileSo, there are options, but neither of them are made to work nicely with project.el out of the box. My most complicated use case seems to be in Haskell projects where modules for implementation and test live in separate (mirrored) folder hierarchies, e.g.
src
└── Sider
└── Data
├── Command.hs
├── Pipeline.hs
└── Resp.hs
test
└── Sider
└── Data
├── CommandSpec.hs
├── PipelineSpec.hs
└── RespSpec.hs
I'm not really sure how I'd configure find-sibling-rules, which are regular
expressions, to deal with folder hierarchies like this. To be honest, I didn't
really see a way of configuring ff-find-other-file at first either. Then I
happened on a post about switching between a module and its tests in Python.
With its help I came up with the following
(defun mes/setup-hs-ff ()
(when-let* ((proj-root (project-root (project-current)))
(rel-proj-root (-some--> (buffer-file-name)
(file-name-directory it)
(f-relative proj-root it)))
(sub-tree (car (f-split (f-relative (buffer-file-name) proj-root))))
(search-dirs (--> '("src" "test")
(remove sub-tree it)
(-map (lambda (p) (f-join proj-root p)) it)
(-select #'f-directory? it)
(-mapcat (lambda (p) (f-directories p nil t)) it)
(-map (lambda (p) (f-relative p proj-root)) it)
(-map (lambda (p) (f-join rel-proj-root p)) it))))
(setq-local ff-search-directories search-dirs
ff-other-file-alist '(("Spec\\.hs$" (".hs"))
("\\.hs$" ("Spec.hs"))))))
A few things to note
ff-other-file-alist is important, the first match is
chosen.(buffer-file-name) can, and really does, return nil at times, and
file-name-directory doesn't deal with anything but strings.ff-search-directories have to be relative to the file in the
current buffer, hence the rather involved varlist in the when-let*
expression.
With this in place I get the following values for ff-search-directories
src/Sider/Data/Command.hs("../../../test/Sider" "../../../test/Sider/Data")test/Sider/Data/CommandSpec.hs("../../../src/Sider" "../../../src/Sider/Data")
And ff-find-other-file works beautifully.
My setup with project.el now covers everything I used from projectile so I'm fairly confident I'll be happy keeping it.
-1:-- Switching to project.el (Post Magnus)--L0--C0--2026-02-17T23:09:00.000Z
Over the last year or so I’ve ended up with various SVG functions that I think may be of some general utility, so I’ve separated them out into its own file and put it on Microsoft Github.
Some are pretty involved, but most are pretty straightforward. Of course, one can add an infinite number of these, and the more flexible the functions are, the more you can just end up “open-coding” them for your specific project, but I think these have some utility, so there you go.
-1:-- A little collection of SVG tricks for Emacs (Post Lars Ingebrigtsen)--L0--C0--2026-02-17T21:34:05.000Z
I am happy to announce that my command line program for binding plain text writing files is live on my GitHub, with full documentation here, and I could not be happier with how it turned out.
But, you may ask, what is it, and why does it exist?
Quoting from the docs:
Binder applies source/build separation to writing—you author in plain text files and compile to a formatted manuscript.
This upends the word processing (or “WYSIWYG” editor) model that requires the writer to edit a build file (delivery format) as a source file. So you can write with the precision of a software engineer rather than the brute force labor of a printer’s devil.
There’s nothing wrong with using word processors like Microsoft Word, but these tools were designed for print formatting first and foremost. That’s why you have so many options for font sizes, page styles, and flow parameters, but very few text navigation or file organization features. Using such antiquated tools for writing today is like composing a novel by hand-setting movable type on a letterpress printer.
Composing in plain text frees you from those constraints and opens you up to the wide world of text editors with advanced text manipulation features, file and chapter organization, portability, and version control.
For my readers already familiar with Emacs and Org Mode, separating source files from build files is quite common. We can do that by exporting Org Mode documents to whatever format we’d like.
However, I wanted a tool that was text editor agnostic and focused more specifically on fiction writing and manuscript generation. So now, it doesn’t matter if you like Emacs, Vim, VS Code, Sublime, Gedit, or whatever, you can make good use of this program.
I particular like the --stats feature that outputs not only the total word count of all files, but also paragraph, sentence count, and a Flesch reading ease score.
Example output:
binder --stats Manuscript Statistics ────────────────────────────── Words: 45,678 Paragraphs: 1,234 Sentences: 2,345 Words/Paragraph: 37.0 Sentences/Paragraph: 1.9 Flesch Reading Ease: 65.2 (Standard)
Nice.
As always, thanks for reading. And be sure to check out my eBooks.
If you have any comments or questions feel free to drop them below.
The post Writing a novel in plain text has never been easier appeared first on Chris Maiorana.
-1:-- Writing a novel in plain text has never been easier (Post Chris Maiorana)--L0--C0--2026-02-17T05:00:41.000Z
I recently built a little package to flash Emacs windows as you switch through them, so I might as well showcase it in a new Bending Emacs episode, so here it goes:
Bending Emacs Episode 11: winpulse
In addition to showcasing winpulse, we showed some of the built-in window-managing commands like:
C-x 3 split-window-rightC-x 2 split-window-belowC-x 0 delete-windowC-x ^ enlarge-windowC-x } enlarge-window-horizontallyC-x { shrink-window-horizontallyC-x o other-windowIt's worth noting the last four commands are can be optimized by repeat-mode. Check out Karthink's It Bears Repeating: Emacs 28 & Repeat Mode post.
Hope you enjoyed the video!
Liked the video? Please let me know. Got feedback? Leave me some comments.
Please go like my video, share with others, and subscribe to my channel.
If there's enough interest, I'll continue making more videos!
Enjoying this content or my projects? I am an indie dev. Help make it sustainable by ✨sponsoring✨
Need a blog? I can help with that. Maybe buy my iOS apps too ;)
-1:-- Bending Emacs - Episode 11: winpulse (Post Alvaro Ramirez)--L0--C0--2026-02-17T00:00:00.000Z
I noticed that in all terminal emulators I tried (iTerm, Ghostty, kitty), I rely on a system-global shortcut to show me a session that I use whenever I need to terminalize something right now, right there, like ffmpeg something or bulk delete with a pattern or convert magick an image. I had this for ages, and I miss it when it’s not there.
I’ve had iTerm show this in the style of a classic Quake Console – yes, from the game; a console that slides in from the top of the screen, and which in games I mostly used for cheat codes. I used this a lot in Unreal to make the intro cinematic interactive and play in that cool castle. (Ghostty has settings that made the sizing a bit trickier but workable. But eventually Kitty’s settings convinced me to try a centered window instead of one sticking to the top. Kitty also convinced me of making the console not hide automatically when it loses focus, which makes pasting into it much nicer.)
A couple of week after trying terminal emulators, I inevitably wonder why I’m spawning a window that only shows zsh when so many file operations and image manipulation and … well, basically everything is nicer in Emacs.
Wouldn’t it be possible to dedicate a GUI window just to this purpose?

So here we are. It’s possible, and now it’s a package.
It took me a few hours to over-engineer a spec-driven approach to make this possible with the help of LLM’s, Claude Code in particular. I archived the whole history of the thought-process of planning and then let Claude Code orchestrate an implementation with subagents in ~50min while I went groceries shopping with my daughter. The whole process taught me how to make named Emacs daemon (aka server) sessions and use them in end-to-end testing without interfering with my own setup. I learned a bit about how Emacs frames can be configured. And it renewed my love for Emacs and its malleability – there were so many cool alternatives to implement the same feature along the way that it sometimes was genuinely hard to pick a path!
Having a stochastic apparatus come up with fringe ideas to use and configure the package made me curious to try a couple of these myself, too. So all in all, the Claude Code driven experience was a success for me.
I even like the tagline that was generated:
Press a key. Get Emacs. Press again. It’s gone.
That’s it.
Get the quake-frame.el package from Codeberg:
https://codeberg.org/ctietze/quake-frame.el
Note: I only tested this on macOS because I don’t have a Linux with any GUI ready at the moment.
Extensive documentation in the readme will tell you how to configure things, and it comes with recipes to get you started quickly.
Unlike other visor-style packages that existed already, this one is not animating anything. You could get transparency if you want with a simple tweak.
I still stick to the center location with a slight offset. I believe there’s value in caring for small UI details like this, to nudge the window a couple of percentage points off center. The window sizing should just work and make sense and do the right thing.
The customize group is self-documenting and covers all the settings.
You can reset the frame when you dismiss it, or restore whatever was inside. You could force it to always show your favorite eshell session.
In the end, it’s just managing a GUI window for you so that you can bind a system-global hotkey to it. (I use Keyboard Maestro on Mac.)
Hire me for freelance macOS/iOS work and consulting.
Buy my apps.
Receive new posts via email.
-1:-- Introducing quake-frame.el: My Take on a Terminal Visor, But Running Emacs (Post Christian Tietze)--L0--C0--2026-02-16T21:06:59.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!