Meta Redux: Making CIDER More Discoverable

This series about the notable changes in CIDER 2.0 continues with the change you’ll bump into first, whatever your workflow: the transient menus, and the broader push to make CIDER’s functionality discoverable.

The problem: CIDER is huge

CIDER has well over 300 interactive commands. I’ll admit something I’ve said before: there are features in CIDER that even I forget exist, and I wrote half of them. For users, historically, the options for finding functionality were:

  • memorize cryptic key chords (C-c C-w i, anyone?)
  • grep the (very long) manual
  • read the source
  • stumble on a feature by accident three years in and feel robbed

That’s not great for a tool whose whole pitch is making you more productive. The Emacs answer to this problem was demonstrated years ago by Magit: transient menus, which turn every prefix into a self-documenting popup. It took us embarrassingly long to follow suit, but CIDER 2.0 finally does.

Transient everywhere

Every command group in CIDER now opens a transient menu: cider-eval-menu at C-c C-v, cider-doc-menu at C-c C-d, and likewise for test, namespace, macroexpand, profile, trace and references. A top-level cider-menu ties them all together, and even the debugger (? mid-session) and the inspector (m) got menus of their own. Jack-in and connect live in cider-start-menu at C-c C-x.

One design constraint was non-negotiable: your muscle memory is safe. These menus replace bare prefix keymaps, so every existing keybinding works exactly as before, at full speed - C-c C-v e still evaluates instantly, menu or no menu. And if you’d rather not see the menus at all unless you actually hesitate mid-chord, set transient-show-popup to a short delay and they’ll appear only in that moment of doubt - which is precisely when you need them.

Transient also gave us something the old keymaps never could: arguments. Menus now carry flags for the things that vary per invocation - pick a pretty-printer with --print-fn=, set test selectors with --include=/--exclude= and reuse them across runs, toggle cider-ns-refresh’s modes explicitly, pass Clojure CLI aliases at jack-in time. All those “this command behaves differently with a prefix argument” paragraphs in the manual are becoming visible checkboxes instead.

Discovery beyond menus

The menus are the headline, but the discoverability push in the 2.0 cycle went wider:

  • A new keybindings reference page collects every binding in one place, and the printable refcard was brought back up to date.
  • The REPL’s shouty welcome banner is gone, replaced by a one-line hint; the getting-started material now lives in a summonable reference card (C-c C-h, or the ,refcard REPL shortcut) - available when you want it, invisible when you don’t.
  • CIDER now warns (once per session) when you use a deprecated keybinding, so bindings can actually be retired someday without silently breaking people. M-x cider-list-deprecated-keybindings shows what’s on the way out.
  • The 1.22 cycle’s big audit already made the mode menus expose dozens of commands that were technically present but practically invisible; 2.0 builds on that foundation.
  • Even cider-doctor is discoverability of a sort - it surfaces the problems in your setup that you’d otherwise discover one confusing bug report at a time.

The philosophy

If I had to compress the 2.0 discoverability work into one sentence: the features were always there; now the tool tells you about them. Documentation is where knowledge goes to be forgotten - the only reliable place to teach a user about a feature is inside the workflow itself, at the moment of hesitation. Transient menus are exactly that, and Magit proved the pattern scales to enormous command sets.

If some menu feels wrong - a missing command, a flag that should exist, a grouping that doesn’t match how you think - please file an issue. This part of CIDER is young and very much open to feedback.

The keybindings docs have the full picture. Keep hacking!

-1:-- Making CIDER More Discoverable (Post Meta Redux)--L0--C0--2026-07-23T15:50:00.000Z

Andros Fenollosa: Emacs is a Lispboard

What would have happened if Emacs' default buffer had been a terminal emulator? Conceptually, from the outside, it would be perceived as a terminal on steroids with Lisp as its scripting language. Maybe we would be talking about iTerm2 vs Emacs, or Kitty vs Emacs, or even Bash vs Emacs. And what if the default buffer had been Dired, the file manager? History might have created rivalries such as Total Commander vs Emacs, or Finder vs Emacs.

But history took another path. We already know that if you launch an Emacs instance without any configuration whatsoever, what you see is a buffer with the following message:

;; This buffer is for text that is not saved, and for Lisp evaluation.
;; To create a file, visit it with ‘C-x C-f’ and enter text in its buffer.

A notepad with an embedded Lisp interpreter. Its natural rival is Vim with an embedded interpreter (Vimscript). However, the editor that made us all memorize :q! stayed in the buffer, while Emacs kept expanding with more and more software until it became an interface to the operating system.

I'm an Emacs enthusiast, and maybe that costs this article some credibility. However, I'm not here to tell you how fantastic the GNU editor is, nor to convince you to use it. My goal is to explain why the web browser is its true natural rival, and why Emacs is a Lispboard where you build the way you want to work.

Let's imagine a perfect working environment

To set the stage, let's do a little thought experiment.

Suppose I want to work in a single place for all my contexts, both personal and professional. That way I memorize my shortcuts, apply my configurations and never have to switch contexts. Everything lives under the same window.

It's a problem, because it means merging programs that have nothing to do with each other but need to collaborate. For example, a terminal emulator, an email client and a code editor (or IDE). With a layout system, inside the same window, I could have one column with my email inbox, another with the terminal and a third with my IDE ready to write whatever I need. In this space, it would be easy to write a bash script, launch it from the terminal and receive an email with the result. All in the same window, without switching context, with the same shortcuts, the same typography and the same theme. I could even add a fourth column with a Git client to version the script and push it to a remote repository. Or while we're at it, a fifth column with my favorite AI agent, able to read both my code and the rest of the panels.

We have just created an interface.

But this wouldn't be the most useful part: the programs would be integrated with each other, each one living in its own scope and, at the same time, in a global one. That means that if I enable the spell checker in the code editor with a certain configuration, it also becomes active in the terminal, in the email client when composing messages, or when I write commits. All with the same interactivity and configuration. They are not four isolated binaries connected with pipes: they are programs written in the same language, running on the same runtime and sharing the same event bus.

We have just created a connector.

Now let's remove an unnecessary layer: the bash script. If I already have an interpreter embedded in this very window, I can create my own scripts. I don't need an external binary. I no longer need to leave the window, nor touch the operating system. All my scripts, prototypes and workflows can live together.

We have just created a habitat.

There's only one problem left with the runtime: tomorrow I'll change laptops, or I'll want to work from a server, and I have no intention of rebuilding my environment from scratch. I want my scripts to work exactly the same regardless of the operating system and the hardware. A script should behave the same way on Windows, on a Raspberry Pi or on a cheap Android device. I need the whole environment to be a cross-platform runtime with bytecode that runs anywhere.

We have just created a virtual machine.

Now, to round off the idea, we'll use Lisp as the scripting language and we'll call the whole thing Emacs. Although I prefer to call it something else: a Lispboard. Like an electronics breadboard, a board where every module you plug in shares a bus with all the others, except here the modules are written in Lisp.

And if during this exercise you thought "this sounds familiar: tabs, applications and a cross-platform virtual machine... that's my browser", hold on to that intuition. We'll come back to it at the end of the article.

What is Emacs not?

With everything mentioned so far, I want to debunk some myths surrounding Emacs:

  • It's not a text editor: it's a complete working environment, with an embedded scripting language, its own software and a cross-platform runtime. The editor is just one more program inside that environment, as are the terminal, the email client, IRC, the file manager or the web browser (yes, Emacs ships with one).
  • It's not an operating system: it's a working environment that runs on top of an operating system, and that can replace many of the programs it offers. But it has no drivers, no kernel, no low-level tools. It cannot run without an operating system.
  • It's not an IDE: there is no default set of tools focused on programming in a specific language. For that you need LSP backends, debuggers and linters, which are installed as packages. But you can certainly get there; it's one of its most common uses.

What it is: a Lisp interpreter and a framework that run on top of the operating system in the most agnostic way possible, and that reimplement a good part of the programs it offers.

My workflow

Scripting and prototyping

When you think: "I'm going to write a simple bash script to automate a task", I think: "I'm going to write an Elisp function". And on top of that, I'll give it a graphical interface if I need flags or text input.

For example, suppose you need to create a script for yourself, not for a CI/CD process or cron. The goal is to back up a specific folder (maybe to the cloud). You could end up with a command like this:

./backup.sh /home/user/folder /home/user/backup

But you also include a flag to create a zip:

./backup.sh -z /home/user/folder /home/user/backup

Well, instead of going through the terminal, I create an Elisp function that does the same thing, and I add a small panel to it.

I run:

M-x my/backup RET

And a buffer will show up with an interface like this:

Folder backup

Folder: [ /home/user/folder ]
Target: [ /home/user/backup ]
[ ] Compress into zip

[ Cancel ] [ Run ]

With inputs, checkboxes and buttons. Without having to remember where it was, or its parameters, or their order.

If you have worked with Lisp before, you'll see the code is very basic:

(require 'widget)

;; Logic

(defun my/backup-run (folder target zip)
  "Sync FOLDER into TARGET with rsync, or compress it to a zip when ZIP."
  (let* ((folder (expand-file-name folder))
         (target (expand-file-name target))
         (command
          (if zip
              (format "zip -r %s %s"
                      (shell-quote-argument
                       (expand-file-name
                        (format-time-string "backup-%Y-%m-%d.zip") target))
                      (shell-quote-argument folder))
            (format "rsync -a %s/ %s"
                    (shell-quote-argument folder)
                    (shell-quote-argument target)))))
    (async-shell-command command "*Backup log*")))

;; UI

(defun my/backup ()
  "Open a panel to back up a folder."
  (interactive)
  (switch-to-buffer "*Backup*")
  (kill-all-local-variables)
  (let ((inhibit-read-only t))
    (erase-buffer))
  (remove-overlays)
  (widget-insert "Folder backup\n\n")
  (let* ((folder (widget-create 'editable-field
                                :format "Folder: %v\n"
                                "/home/user/folder"))
         (target (widget-create 'editable-field
                                :format "Target: %v\n"
                                "/home/user/backup"))
         (zip (widget-create 'checkbox nil)))
    (widget-insert " Compress into zip\n\n")
    (widget-create 'push-button
                   :notify (lambda (&rest _) (kill-buffer))
                   "Cancel")
    (widget-insert " ")
    (widget-create 'push-button
                   :notify (lambda (&rest _)
                             (my/backup-run (widget-value folder)
                                            (widget-value target)
                                            (widget-value zip))
                             (kill-buffer))
                   "Run"))
  (use-local-map widget-keymap)
  (widget-setup))

And this scales beyond toys. With this same approach I have built systems of all kinds. For example, this blog's newsletter: I manage subscribers, generate the emails' HTML from org files, send them out, send myself previews and analyze the data. All in Elisp, local, and with a simple control panel integrated with other tools.

The key of the example is that I'm not writing a script for an operating system or an interpreter, but a function for my working environment. I'm building a new workflow, in a space where I can program my own tools, connect them to each other or build one on top of another. I could create an asynchronous notification system to alert me when something finishes, and later connect it to the previous tool at no cost.

And I can do all of this because I have an embedded scripting language, a set of libraries (like widget.el to build graphical interfaces) and a cross-platform runtime that guarantees my scripts will work the same on any operating system. That is a Lispboard.

Programming and development

I want to stress that I separate tasks or environments by tabs. And inside each tab I have a layout with the buffers (or software) I need.

In the IDE tab I usually open:

  • A text editor with some LSP backend and debugger (dap-mode, Eglot, etc.).
  • An AI pair programming partner (chatgpt-shell connected to Claude).
  • A Git client (Magit) to version and review the AI's changes.

Although I keep weaving in temporary ones such as the file manager (Dired, with or without TRAMP), the terminal (vterm), the HTTP client (verb), Docker (docker.el), the web browser (eww) or the project manager (Projectile).

Working in Emacs with this level of integration is a pleasure that's hard to describe. You understand why it makes those who program in it fall in love.

Notes and study

Everything I think, plan or study ends up in plain text. The foundation is org-mode: notes, tasks and planning. On top of it, I manage my study notes with Denote: each note is a file with the metadata in its own name, all linked to each other. And when the study material is a PDF or an EPUB, I read it without leaving, with pdf-tools or nov.

Around them orbit small pieces that complete the routine, like jit-spell underlining my mistakes on the fly (remember the spell checker you configure once and it works everywhere? This is it) and wallabag to read or save articles I want to study later, calmly.

Communication: chats, email and social networks

Although it may not seem like it, over the decades the community has built an excellent pack of communication tools. Even for the same protocol there are several implementations, and each one has its advantages.

In my case, I have a tab exclusively for email with mu4e. It covers every need I can expect from an email client: composing, replying, forwarding, filtering, searching, tagging, marking as read/unread, etc. And it had better do it well, because it's the most important communication channel I have: mailing lists, discussions around a Pull Request, messages from other developers, service notifications, and everything that comes with a personal and professional life.

In another tab I keep a set of chats:

  • IRC with ERC connected to Libera.Chat. Curiously, this is where I've met the most people and been able to talk with many free software developers. I don't understand the eagerness to consider it obsolete.
  • Mastodon with mastodon.el for the Fediverse.
  • Org Social with org-social.el to read the posts on my favorite social network (I'm not impartial, I know, and I don't care).
  • Telegram with telega.el, which lets me read and reply to messages from groups and channels.

I only leave for Teams and Discord, both communication channels at my job. And WhatsApp, whose use in Emacs still doesn't convince me.

News and reading

My main source of information is RSS with Elfeed, almost all personal blogs. I use clients for the aggregators, hackernews-modern and lobsters (both made by me). With Gnus I read some Usenet newsgroups (it's still alive, and it reads wonderfully) and with Elpher I wander around Gemini and Gopher. When I want to open a web link, I give eww, the Emacs web browser, a chance. If it's unreadable, then I go to Firefox or the like (I have a shortcut).

Leisure

The workday is more pleasant with some music playing, hence EMMS backed by mpv. And when I want to disconnect for a while, I like playing chess with chess, either alone or with a friend online. After all, the board is also there to have fun.

Emacs is a Lispboard

The community has an expression for this way of working: living in Emacs. People have been writing about it for decades, and at the end of the article I leave you the readings that have marked me the most. But notice that living in Emacs describes a habit; what I have tried to show you is the consequence. Everything you've seen is not a collection of programs, it's a single board where every module shares a bus with the rest. An interface, a connector, a habitat and a virtual machine (literally: Elisp compiles to bytecode). A Lispboard to play with, or to make your work more efficient.

Its rival is the web browser. It meets more of the requirements than we like to admit: it's a cross-platform virtual machine (for JavaScript) and half of humanity lives inside it, with their email, their chat, their music and their office suite. But it fails at the essential part: tabs are silos that don't share a bus, you can't wire your email to your chat, and no module is yours: each one belongs to a third party that can change it, fill it with ads or shut it down tomorrow. On top of that, access to the host itself is limited: it's not easy to touch the disk or talk to the operating system. The browser is a rented Lispboard, with a landlord. Emacs is the only one you can own.

To wrap up, I want to make clear that using Emacs is not nostalgia: it's that nothing comparable has appeared yet. The 0.1% of us who use it and squeeze it daily do so because it lets us work in a way no other software allows. We will probably have a lifelong relationship with our configuration. There's always something to tweak, improve or automate. And if there isn't, we create it. Emacs is not free because of its GPL license, but because it's the only place where you can live and work your way.

References


Will you buy me a coffee? This is how I keep writing without ads or paywalls. Sure, it's on me!

Send an email to comment+article-06bfd107@andros.dev to leave a comment. The subject will be ignored.

-1:-- Emacs is a Lispboard (Post Andros Fenollosa)--L0--C0--2026-07-23T13:48:45.000Z

Irreal: Making A Window Dedicated

Bozhidar Batsov has been a writing a very nice series of posts on recent Emacs additions that are worth knowing. His latest is on making a window dedicated. A dedicated window is one that is locked to its current buffer. That means, for instance, that if you have a particular window configuration in place and call some other function, the new function can’t hijack the dedicated window and spoil your configuration. Batsov has an animated Gif that shows this in action.

The notion of a dedicated window has been in Emacs for some time but what’s new is a quick way of dedicating a window. That way is toggle-window-dedicated, which is bound to Ctrl+x w d by default. As shown in Batsov’s Gif, that toggles the dedicated state for the current window on and off.

Sadly, I have Ctrl+x w bound to a function that starts up elfeed. I use that several times a day and have a significant investment in muscle memory so I either have to change the toggle-window-dedicated binding or simply call it directly. Since I doubt I’ll be using it that often, I’ll probably just call it directly. With my fuzzy command completion I need only enter Meta+x tog win to get the desired result.

There’s one other aspect that Batsov mentions but doesn’t explain: strongly dedicated windows. If a window is strongly dedicated, you can’t change the buffer displayed by the window. You can make a window strongly dedicated by specifying the universal argument in the call to toggle-window-dedicated.

If you like to set up a specific window configuration for some of your workflows and you don’t like random commands stepping on it, toggle-window-dedicated is just what you need.

-1:-- Making A Window Dedicated (Post Irreal)--L0--C0--2026-07-23T12:54:41.000Z

Raymond Zeitler: The Problem With Tags Lists

A Tags List is an alternative to the Agenda that the user can configure with org-agenda-custom-commands. The list shows subheadings that have a certain tag. You can edit these list items with the same commands used for the item on the standard Agenda View. For example, you can invoke org-agenda-todo (bound to t) while on a tag list item to change its TODO state. However, the display doesn't always reflect the action that was performed. That can mislead the user into thinking the command had no effect.

For example, you can change an item's TODO state to DONE. When you do that for an Agenda item, usually Org Agenda removes the item from the Agenda. However, that doesn't happen to an item on a Tags List. The TODO keyword changes to DONE, but the item remains visible. However, if it's a recurring task, the state will remain at "TODO" as if nothing happened. That's because Org reverts the state back to TODO after advancing the date in accordance with the repeater.

The lack of visual feedback can lead the user to invoke the command a second time. Then the item's deadline (or schedule) will be advanced more than once, skipping the next reminder.

Here's a real world example. Suppose the TODO item is a reminder to pay the rent. It's set up with a +1m repeater so that it shows up monthly. The user pays the rent and marks it done. The deadline advances to the next month, which is fine. But nothing changes on the Tags List -- the TODO item is still there. Then the user becomes distracted1 and forgets that "Pay the Rent" was marked DONE, and so the user marks it DONE again. The deadline advances another month, so the reminder for next month's rent never appears.

A slightly better approach is to filter the Agenda View with org-agenda-tag-filter-preset to avoid generating a Tags List. But I found it tricky to use. I couldn't figure out how to use Customize to include it in one of my org-agenda-custom-commands. Instead I edited my init file to change the custom command from:

;; Dangerous!
("Bm" "Bills for the Month (Recurring)" agenda "Bills"
 ((org-agenda-span 'month) (org-agenda-files '("~/bills.org"))))

…to:

;; Less Dangerous
("Bm" "Bills for the Month (Recurring)" agenda ""
 ((org-agenda-tag-filter-preset '("+bills") nil)))

This is a better approach because when marked DONE, an item's TODO status changes to DONE even if it had a repeater, so the user has a clear indication of its status. Unfortunately, this isn't ideal because the date of the deadline doesn't advance according to the repeater; it's necessary to edit the org file manually to change DONE to TODO and then mark it DONE using org-todo2 (which enacts the repeater).

If there were such a thing as org-agenda-todo-before-hook, I'd add a call to org-agenda-check-type and abort if it returned anything other than agenda.

Another approach is to disable org-agenda-todo. I tried it. It works, but it's annoying.

My eventual solution was to modify org-agenda-todo in org-agenda.el3 so that it aborts unless the item is an agenda item. The only required change was adding (org-agenda-check-type nil 'agenda) to the begining of the function. Now when I invoke org-agenda-todo on an item in a Tags List, Emacs responds with "Not allowed in `tags'-type agenda buffer or component." Perfect!

(defun org-agenda-todo (&optional arg)
  "Cycle TODO state of line at point, also in Org file.
This changes the line at point, all other lines in the agenda referring to
the same tree node, and the headline of the tree node in the Org file."
  (interactive "P")
  (org-agenda-check-no-diary)
  (org-agenda-check-type t 'agenda) ;; add this!
  (org-agenda-maybe-loop
   #'org-agenda-todo arg nil nil
   (let* ((col (current-column))

Although this is only a one-line change, it's a good example of why I appreciate Free and Open Source Software (FOSS). Users have the freedom to choose how it operates! In a sense, we're all owners and collaborators. And even if you're not able to make the change on your own, you're part of a supportive community that will help you.

Is there something that irks you about Emacs? Have you asked for help on how to change it? Let's do that now!


1 The hypothetical user is me.

2 org-todo operates within org files; org-agenda-todo works from within the Agenda.

3 When modifying an Emacs source file, save a backup of the original and rename the corresponding .elc file. Otherwise Emacs will continue loading the compiled version, making it appear that your changes had no effect. Recompile the modified source if desired.

-1:-- The Problem With Tags Lists (Post Raymond Zeitler)--L0--C0--2026-07-23T01:32:36.624Z

Charles Choi: Malleable Computing, Emacs, and You

It all started with a routine task. I use GitHub issues for all of my public-facing projects but my preference is keeping track of things in Org Agenda. To reconcile the two, I would manually copy over the GitHub issue to an Org file, typically the title and description, like an animal.

Despite this separation, the duplicated issue allowed me to treat it as a scratchpad for anything I could express in Org. In this way, I used the duplicated issue in Org as both a dedicated area to take notes and a “staging” area to compose follow-up comments that I’d want to share in the public facing issue.

I manually copied for far longer than I care to admit. Repeat a task enough times in Emacs and the inevitable thought arises: “I should automate this.”

This post recounts how I automated this task and in doing so, highlight the malleable computing capabilities of Emacs. It should also be considered a follow-up to my earlier post “In Emacs, Everything Looks Like a Service”.

Requirements

An essential question to ask in any automation exercise is “What do I want done?”

I wanted to be able to:

  • Easily copy a GitHub issue (title, description, some metadata) as an Org task that can be tracked in an Agenda view.
  • Work primarily from Emacs to minimize the context switching between it and a web browser.
  • Express my thoughts in Org syntax.
  • Create a new GitHub issue.
  • Avoid dealing with GitHub authentication.
  • From Emacs, open a GitHub issue in a web browser.

Another essential question to ask is “What do I not want to do?”

  • Install or write a full-featured GitHub client.
    • Worry too much about synchronization logic between local (Emacs) and server (GitHub) state.
  • Spend a lot of time on this (ideally have something working in a day, no more than a week).

Specification

With the above requirements in place, the next question is “how do I build this?”

For this particular exercise, I decided to leverage my existing install of the GitHub command line utility gh. The benefits for this are:

  • GitHub authentication is delegated to gh; no need to mess with it directly from Emacs.
  • Emacs can treat gh as a REST service to GitHub as shown in the diagram below

    img

To flesh out the rest of our tool, we can leverage using different Elisp packages and programs:

  • For the user interface, the Transient and Variable Pitch Table (vtable) packages are used for menus and display respectively.

  • For Org to Markdown translation, ox-gfm will be used.

  • For Markdown to Org translation, Pandoc will be used.

  • Elisp native JSON support will be used for deserializing the JSON responses from gh.

Implementation

The implementation of the above is published as the package fj with its source available for examination in the file fj.el. Of note is the function fj-request-issues which does the work of retrieving GitHub issues via gh as shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(defun fj-request-issues (repo)
  "Request issues for REPO."
  (let* ((fields fj-browser-fields)
         (cmd-list (list "gh"
                         "--repo"
                         (format "'%s'" repo)
                         "issue"
                         "list"
                         "--limit"
                         (number-to-string fj-request-issue-count)
                         "--json"
                         (string-join fields ","))))

    (json-parse-string (shell-command-to-string
                        (string-join cmd-list " "))
                       :null-object nil)))

Consider the high degree of abstraction provided by fj-request-issues:

  • The list cmd-list forms the request (in this case, the arguments to invoke gh with).
  • shell-command-to-string dispatches the request to gh.
  • The returned JSON response is handled by json-parse-string to deserialize the JSON into an Elisp hash table.
  • All of the above is accomplished in less than 20 lines of code.

The returned hash table result is subsequently processed to populate a vtable as shown below. From the vtable, the user can navigate the list of issues with an ancillary window updated to show the details of a selected issue.

img

Multiple commands and functions working with said hash-table were made to satisfy the requirements above. They are accessible via the Transient menu shown below:

img

Malleable Computing Observations

As Elisp is a dynamic programming language, the above function (or some variant of it) can be coded and evaluated within a running Emacs session. In Emacs it is routine practice to prototype code behavior without the need to restart it. Contrast this with tools that are built with a static language and have no extensibility, where the edit-compile-debug development cycle must be applied to exercise behavior, provided the source code is available.

Emacs provides numerous ways to edit and evaluate Elisp code, among them:

  • A scratch buffer
  • An Elisp file
  • An Org source block
  • IELM REPL
  • Eshell
  • eval-expression (M-:)

Because there is no isolation between loaded Elisp code, all of it can be orchestrated together in an improvised fashion. Any program accessible to Emacs via shell further adds to this mix.

Provided high abstractions, the amount of code needed to be written for desired behavior can be small. At the time of this writing, fj.el has approximately 400 lines of code has been measured by cloc:

1
2
3
4
5
6
github.com/AlDanial/cloc v 2.08  T=0.01 s (146.4 files/s, 76550.1 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Lisp                             1             93             38            392
-------------------------------------------------------------------------------

It took me about 2½ hours to build the basic behavior I wanted (request issues from GitHub, then display) and the rest of the day to cover all the original requirements. Everything after that was just refactoring.

Motivated readers are invited to examine fj.el to understand its details. At this point though, I’d like to take the opportunity to muse on software engineering and malleable computing.

Software Scope - Some Percentage Anecdotes

The 90/90 rule posits that “The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time”. Closely related is the Pareto principle (aka the 80/20 rule) as applied (or more mis-applied) to software development: only 20% of your features will be actually used by 80% of your users.

From control theory there is the concept of bounded input, bounded output (BIBO) stability. If the system is BIBO stable, then whatever input that is bounded will have an output that is also bounded.

Coupling these ideas together, if the functionality you want (BIBO stability) is within that 20% deliverable then you are positioned to get a desired result faster.

Unfortunately, this observation is not available for many producers shipping a tool that must satisfy a large audience. In Sinofsky’s post “What is Software Bloat, Really?”, he describes the product definition issues with shipping Microsoft Office, particularly with feature set. From their user research, they were led to this finding: “… the data was entirely conclusive: Most of Office was used. But no one person used the entire product.”

When discussing product definition, it is helpful to distinguish two informal dynamics: supply and demand.

With software that is only supply-provided (such as the case with MS Office), the weight of product definition is shouldered by the producer. If the producer wants to serve a vast audience, it is likely their feature set and corresponding development scope will be vast as well. On the demand-side, consumers can make requests for new features, but their prioritization is controlled by the producer. In this situation, the roles of producer and consumer are strongly delineated.

Malleable software, where users have agency to adapt and reshape their digital tools, offers a different possibility: producers provide the building blocks and let consumers make their own tools. In this, both existing code and programs are recombined to make new behavior. Stated another way, malleable software takes advantage of combinatorial explosion in that a desired subset of behavior can likely be found within the state space of combining existing libraries (in this case Elisp) with different programs. With malleable software, the roles between producer and consumer are less delineated. A consumer building a new tool out of malleable building blocks now must shoulder the weight of product definition.

Build for 1 or N

The scope to build for 1 vs N can be an order of magnitude with the kicker that N need not be large. Choosing to write code for another person (N=2) escalates concerns that could otherwise be brushed aside:

  • Error handling
  • Code maintainability
    • Modularity/Reuse
  • Documentation
  • Unit and Integration Testing
  • Packaging
  • Distribution

Both a blessing and curse of malleable techniques is that they allow for “just good enough” capability. Malleable techniques encourage building for 1 as reaching a state of “it works for me” is often sufficient to declare victory.

For most software that is only supply-provided, building for N is a requirement. For most software built with malleable techniques, building for N is a choice.

Malleable Software and User Agency

Returning back to fj, the benefits of building for 1 are evident:

From within Emacs I can now easily:

  • Peruse GitHub issues for a specified repository.
  • Copy a GitHub issue into a Org file.
  • Create a new GitHub issue using Org syntax.
  • Open a GitHub issue in a web browser.

Implementing fj in Emacs was straightforward as I could leverage Elisp packages (both built-in and 3rd party) and external applications (gh, pandoc) to build it. Within a day I had a tool that did what I wanted. I did not have to ask for permission nor required privileged materials (source code) to build fj. With Emacs, I could just do it. Such individual empowerment advanced by malleable techniques is liberating in contrast to working with siloed applications with little to no integration between them.

Closing Thoughts

This post muses about the malleable computing capabilities provided by Emacs, showing by concrete example (fj.el) how new behavior can be created leveraging both code and program reuse in an improvised fashion. With measured expectations (requirements, feature set, audience), malleable techniques allow for building tools that would otherwise be infeasible without it.

Links

-1:-- Malleable Computing, Emacs, and You (Post Charles Choi)--L0--C0--2026-07-22T21:00:00.000Z

Lars Ingebrigtsen: It’s time to wake up: An Emacs alarm clock

I’ve been using some variation of the same system for my alarm clock since at least 2011. Probably earlier — I didn’t have a blog before that, so who knows? As the saying goes: What hasn’t been blogged about is lost to the mysteries of time…

YouTube Video

(Here’s some music to play while reading this blog post (because of the “I feel like an alarm clock” lyrics)… This is gonna be one of those posts that require a lot of scrolling. You’ve been warned.)

My alarm clock idea has been the same since the start, but with a varying assortment of components.

A numpad to input the alarm…

… and a screen/computer to display time and the alarm. (And the temperature. Brrr!) This particular hardware combination has been going since 2019.

So I tap in the time I want to wake up (like 9) and then at 09:00 it turns on the stereo to play whatever was playing. I’m kinda proud of the interface; I’ve found nothing that is so easy to use in any commercial system. That is, if the alarm is at 09:00, I just type 9, and if it’s at 09:30, I type 930, and if it’s at 13:00, I type 13. *tap* *tap* done. (I’m very good at numpad keying.)

(I guess most people get up at the same time every day, so it doesn’t matter as much, but I always want to eke out the most sleep I can in the morning, so the alarm time changes every day, basically.)

The problem here, though, is that there’s too many moving components. In particular, the system relies on telling the central music server to start playing again, which requires 1) the network to work on the alarm clock computer, 2) the network to be up, (2b) the external network to be up to do DNS until I removed that requirement), 3) the music server to be up, 4) me not having chosen something extremely ambient to be playing, like Music To Sleep By, and 5) me not having futzed around with something that just breaks everything.

Some of these things can be ameliorated, but all of these things have failed over the years, so if I really have to get up, I’ve started using an Iphone:

Ewww! This thing has the worst interface imaginable for an alarm clock. That the interface is geared towards making these long lists of possible alarm times is an admittance of failure: It’s so futzy to set up an alarm that people choose to have these moronic lists instead.

Not to mention how many times I’ve lain there for half a minute trying to disable an alarm before it goes off, half awake. (I wake up five minutes before the alarm 87% of the time.)

So… that’s what this blog post is about: I’m building a new alarm clock.

I want to keep the good parts of the old system: The numpad. That’s just the best input device. But I want to jettison the rest, which is too unreliable, and have a totally self-contained system that doesn’t rely on the network or any external components.

I know! Revolutionary! A self-contained alarm clock! I bet nobody’s thought of that before! I must be some kind of genius!

Yeah yeah yeah yeah, I hear you…

Anyway:

I got the components!

Oh, wow, I’ve been thinking about this for quite a while… I got that numpad over a year ago! But it’s still very nice. I bought various small screens while trying to think of a design for the clock, and I ended up with this 4″ screen. I didn’t want an OLED one, because of burn-in issues, so I wanted a small one so the amount of light would be more limited.

(My current setup has an epaper screen which is nice light pollution wise, but it means that if I wake up in the middle of the night and want to know what time it is, I have to hit a key on the numpad to turn a light on, and that’s not ideal, and is another reason to ditch the old system.)

And I got a Raspberry Pi 5 for this. You may think that this is total overkill for an alarm clock… and it is, but I want to run Emacs on it, and I’ve just had a lot of problems doing so reliably on weaker Raspberries. I mean, it works, but suddenly something happens and it’s out of memory etc, and it’s just a pain. Reliability is the point here, so I’m over provisioning.

I also got a light sensor from Yoctopuce. I’m wondering whether it’s a good idea to dim the screen when it’s dark and increase the backlight when it’s light, but I’m not sure whether that’s practical or indeed supported. But it’s another fun gadget to play with, so what the hey.

This model connects via Mini-USB, but I see there’s a USB-C version now, too, which is probably a better idea these days.

Uh-oh! I’m filling up all the USB ports… let’s see… it’s power to the screen, the numpad, the Yoctopuce, the USB speaker… NOOO! I was also gonna put a Tellstick in there, so that I can also use this as part of my home automation network (the old alarm clock machine is, but that’s going away). OK, I have to add a li’l bus-powered USB hub, too. That’s a shame.

I’ve got the cables!

It works!

I’m not quite sure what I’m going to use the knobs for. Well, one is easy enough — that’s going to be a volume adjuster for the music in the bedroom. And then tap it to pause/unpause the music. The other one… I can use to turn the lights on/off? Eh. Dunno.

The numpad is, as described, to set the alarm clock, and then the two remaining keys I use for “cancel alarm”.

So the screen uses a full HDMI connector, and then is USB-C powered.

There’s all these connectors included — for if you have a Raspberry Pi screwed to the back of the screen, but that isn’t going to be an option here. Instead I’m going to use cables like this very, very thin USB-C for both the numpad and the screen. It’s too bad that there apparently are no USB-powered small USB hubs that have USB-A toward the host (i.e., the Pi) and USB-C toward the devices. Obviously not USB 3 speeds, but USB 2 would be fine for all of these things. I guess it’s just too niche, so I guess I see some USB-C to USB-A adapters in my future.

It’s installed!

So this is basically the topology I was thinking of. I’m building a box where the front of the box is the screen, and then on top of the box, the numpad is sitting. Then I have that box on the nightstand, and I can tap on the numpad before going to sleep. And possibly twiddle the knobs to adjust volume on the stereo or something. (Heh heh, he said knobs.)

OK, I think I’ve got the hardware under control now, so it’s time to do some programming. (You can find the software on Microsoft Github.)

As a designer once said: “Futura, centred, and you’re done”.

But perhaps I can use the currently playing album as a background? Eh… gotta down the opacity on the image a bit, I think, but it’s not worth fiddling with until it’s running on the real li’l screen. (I don’t want the screen to be too bright.)

OK, now running on the Pi… Looking promising.

But one think I’m not sure about is how I’m going to fasten the screen to the box. The really isn’t anything to “grab onto” here… There’s the screen, and then a gap, and then there’s electronics that extend equally far out.

So… er… I wondered whether I might stick something between the board and the screen or something, but what? It can’t be metal, because that would risk shorting out some circuits. Hm…

So for that light sensor… I had Claude write a small Python script to just dump the luminance (or whatever) values, and then I can write something in Emacs that listens to that and adjust screen brightness.

But:

While ddcutil is able to talk to the screen, apparently, it doesn’t get much more than the EDID. So it doesn’t seem possible to adjust the backlight after all. (Lenovo L1950wD!? Lenovo made the li’l screen?)

Uhm… I could still use the sensor to make the text on the screen less bright. I.e., add a black alpha channel on top that’s controlled by the luminance… Hm. Not ideal, but perhaps better than nothing?

Oh, I’d forgotten that I’d gotten these USB-A to USB-C cables. They consist of a ribbon cable, and then you clip them onto the connectors. Looking at them now, I thought perhaps the point was that you could cut the ribbon to the required length?

But no? Because the ends of the cable look kinda connectorey…

Anyway, I’ve set up the alarm clock for a test run. In “deconstructed form”. Just to give it a go in realistic environments to see whether I’ve forgotten something…

If you’re making a light sensor device, what’s the one extra thing you absolutely have to put on the device? Yes, exactly! A blinking LED! Perfect!

But now it’s carpentry time!

This is the first time I’ve worked with this material — it’s laminated wood with MDF and whole wood things alternating through? And just 1cm thick, because 1) the thinner the less sawing, right, and 2) it makes the clock a bit smaller in all directions.

I’ve worked with thinner wood before, and it’s just not fun. It splinters easily, and you can’t really screw anything into it (and expect it to stay screwed). (So to speak.) So I’m trying this now and see whether it’s a happy medium between Too Much Work and Totally Splinters All The Time.

Errr… my jigsaw totally chewed through the wood. That’s not nice.

So I’ve got these blades…

… “basic” and “fast” and “clean”. I used the “basic” first.

Man, that “clean” isn’t any better, especially on the reverse side, which is just chewed to bits. Am I just using the wrong blades, or is this wood really shitty?!

Oh, there’s something called a two side clean blade. That sounds like what I want?

A li’l trip to the store, and I now have some T308TB blades. Let’s try one out:

Wow, that’s some difference. The cut to the right is the “basic”, then to the left is “clean cut”, and then in the middle is the “hardwood 2 side clean” T308BF. That’s incredible. And it does almost look equally good on both sides — the other side is even better! Pristine.

Let’s do some cutting of the lid for the box! I’ve measured so precisely, and…

Oh. That’s not… that’s not right. Did I really mis-measure that badly? OK, I’m following the old adage of “measure once and cut twice”, so…

Yeah, that’s really bad. I’m off by 1.5cm at one end while being correct on the other end.

Is there a trick to cutting straight with a jigsaw? There is!

Now let’s see…

I’ve got these… what are they called? Hand held vises? Nah, can’t be right. Squeezey thingies. That’s the ticket.

So I squeeze the wood, using one as the guide for the other…

But there’s a lot of mathing involved, since I have to add the width of the guide thing on the jigsaw…

Nooo! It’s the wrong thickness… I want 85mm, but this cut varies between 84mm and 87mm… how did that happen.

OK, do it one more time — cut a “clean” bit off, and then guide it to 85mm… *phew* I thought I was going insane there for a minute — the math wasn’t mathing! Things just had to have shifted while I was fastening the vise or something.

YES! CORRECT BOTH ENDS!

So there we are: Two sides, 200x125mm. Bottom and lid 200x85mm. Or… er… was the lid supposed to be 10mm shorter? Uhm… Oops, yes. Grr! OK, gotta cut 10mm off the lid.

But I want some funky feet for the box.

I’m inspired by the Hollie Fingold box I’ve got. It’s so funny. So I want some triangle feet.

Oh, I’ve got this thing! It’s meant for cutting things at an angle… but… er… hm.

OK, I just cut some freehand: As long as they’re vaguely the same height, that’s fine, because I can just sand off the tips… or rather, I guess I’m adding some rubbery substance at the tips, anyway — Sugro or something — so I can get the feet to the same heights by adjusting the Sugro.

There! All wooden bits assembled! And I changed my mind on the lid length — I think it’d be amusing if it peeks out on the front top a bit. A jaunty hat lid or something.

(That it means that I don’t have to make another cut didn’t affect this decision at all! I’m not at all afraid of losing any fingers! *counts* Nine, that’s the normal number, I think? And using 1cm wood made cutting these things child’s play. Much less drama than jigsawing thicker wood.)

Now I just have to nail it all together.

I was worried that the wood would splinter when I nailed in the nails, but nope — I’m doing a test run with some cutoff pieces to see whether this works at all.

It’s all coming together nicely. The problem is keeping the nails going in straight: Since the wood is only 1cm wide, and these nails are 2.5cm, if I’m hitting things at a slight angle, they pop out of the wood on the other side.

But here’s why using nails instead of screws make sense: Using finishing nails, I then use a nail punch to drive them further into the wood…

… so I can then use filler to completely hide the nails. Doing that with screws isn’t really possible.

OK, time to start working for real. All bits assembled.

Bang bank bang and oops. Once again, a nail going in at an angle… *sigh* This happened three times during the mounting, and I had to pull the nails and do a new one for the worst ones. Is there a tool to guide or something? To guarantee 90 degree angles?

Tada!

The screen fits! Wow!

OK, time to swap out the cables I was using during the test run with production cables, which means thinner cables that are angled, for the most part. So I got some from AliExpress, and…

WTF. That’s two angled USB-C connectors, and one has a plug that’s more than twice as long as the other. So that’s one more measurement to take into consideration when trying to find cables that can do the job.

But while a new shipment of cables is winding its way from China, it’s time to do some sanding and painting and stuff.

See? I messed up one of the corners during cutting, I think.

But a bit of sandpapering and it’s fine.

It’s spackling time!

I need to spackle all the ends (to hide the composite nature of the wood), and of course all the nails and stuff.

Annoying… need several turns with spackling and sanding…

And then I’m just gluing on the feet using wood glue. I guess I could have nailed them in, too, but…

Hey what’s that outside the window.

Double rainbow! Or triple! Man. This alarm clock was born lucky or something.

Three of the feet actually turned out exactly the same length, but one is sliightly too short. That’s a lot better than I had expected.

So I added some Sugru-like substance to the short leg… and man, that Sugro knock-off from Locktite just isn’t as good. For one, it sticks to your fingers like crazy — I had to do the “OUT DAMNED SPOT” for like five minutes to make the gunk part ways with my hands. Yuck.

Damn monopolists — Sugru was so good, but then all the big companies launched similar products, so now you can’t find Sugru in shops. You just find the inferior knock-offs.

Oh, I see that Tesa has bought Sugru now? Well, they’re a big company — why can’t they make the shops carry it?

Grrr.

Then after doing two coats of primer and two coats of paint… it turns out that the lid is now too big! D’oh! I didn’t take into account the millimeters added by the spackle, primer and paint… So now I have to plane off some millimeters.

Oops! I went shopping again. And bought this thing.

It’s works! Got the lid a few millimeters narrower and now it fits again.

OK, more cables arrived from China… but how does this HDMI connector work anyway? Do you just flip the white thing up?

OOPS. Nope. *sigh* The black things slide out instead. Oh well, I guess I gotta order more stuff from China.

I painted the clock black, because my inclination is to always paint things black. But I wasn’t careful enough when letting the box dry, and it bonded to some surface and now I have more to fix.

But back to that Hollie Fingold box — I was going to do something fun and whimsical for a change, right? So how about painting it gold on the inside? I mean, you can only see a bit of gold when it’s finished, but some poking up will be whimsical?

And… are lines whimsical?

They better be!

Tada! Plenty whimsical. In an orderly fashion.

And then it’s the moment of truth. Does the screen still fit after all this painting?

YES! It’s totally snug — I was going to mount it into the box with some Sugro or something (there really aren’t any good options), but it fits so precisely that I don’t think I have to do anything?

Everything fits! *phew* So it’s time to put the lid on the clock. I was wondering how to do that — very few things work well with wood this thin.

Like, I got these shelf rests, but the screws here are both too long and too thick — it would totally splinter the wood.

So I cut some bits off and painted them gold, and then I could glue them to the walls and then let the lid rest on them? Two problems: If they aren’t totally the right height, the lid is going to be wobbly when I’m using the numpad. And if the glue isn’t strong enough, they’re going to fall off when I’m typing.

Ideally I would screw them in, but I’d have to do that from the inside. Possible, but… I could also use a nail on them, but I’d have to do that from the outside, and then redo the entire paint job. *sigh*

But… what about a hinge?

Finding screws that are short and thin enough locally turned out to be difficult, so I ordered this set. 1200 parts! Whoho!

Look at all these tiny screws… just what I need.

OK, test screwing — do these splinter the wood? Nope.

And I also managed to find hinges that are small enough. There’s a huge variety of hinges out there, but (of course) 99.7% are geared towards real solid things, and are inappropriate here. But these are less than 1cm wide, and so perfect. The screws that were included were too long, of course, but I had my set now.

Huh… would it be fun to mount the lid this jaunty way?

That is, have the numpad be on a slight incline? Might make it easier to type… But… nah…

I thought it would be fiddly to mount the lid, but it turned out to be incredibly easy.

(Also note masterful paint job. I’m a master painter.)

Tada!

Yes. The concept works!

And the cables arrived from China this morning. And this time around I managed to assemble them without destroying them.

So connecting from the screen to the Pi… but, er, like, I had gotten the Micro HDMI side angled the wrong way. Even after re-checking five times before ordering. Gah! But it’s OK; I can just have the Pi the other way around.

Jamming all the bits in there…

It works!

And it seems solid and stable enough — I can tap away at the numpad without any wobbling.

I also got a package of these in the mail today… What was I going to use these for? The lid? Hm… No! Now I remember — I was going to use them to fasten the screen: Screw them into the wood and then slide the screen down on them. But that’s not necessary, it turns out, and it wouldn’t have worked, anyway — they’re only 1mm thick, but that’s still too thick. I would have had to have taken that into account (is that the correct tense?) when sawing.

Oops, how did that running black paint happen? I thought I was more careful about sanding those mishaps away…

Anyway, this is the back of the clock — I cut out a bit for the speaker. That’s probably not necessary — it would be loud enough anyway. But what the hey. And it’s lucky that the speaker is exactly the same width as the screen, eh?

And that’s the light sensor poking out at the top. It’s possible to break off the tip there (where the sensor is) and then do some wiring between the main part and the sensor, but… I don’t wanna go soldiering.

And the power cable: I was going to drill a hole in the side of the box and just plug it in that way, but because the Micro HDMI was angled the wrong way, that’s not possible. But I could get a new Micro HDMI plug… I dunno. Either works, really.

Time for a test drive:

It works! The numpad is clicky and nice, and the menu interface for Other Functions feels very intuitive with the dial knob.

Lessons learned:

1) I have to get better at painting. Too many blobs.

c) I need a better work station for stuff like this — I’ve been working off of random pieces of furniture, which really isn’t a good idea when using a jigsaw and stuff:

XIV) There’s a bunch of fun tools one can buy to make woodworking more fun.

Oh yeah… I forgot about the light sensor. The sensor works fine, and I’m using it to make everything on the screen dimmer. That works, but it really would be better if I could actually control the backlights, because even a totally black IPS screens emit a lot of light.

But I can’t see any screens of this size that allows controlling the backlights, so… I mean, OLED would be better, except for the burn-in — which would be extreme with a static display like this.

Anyway, I hereby declare alarm clock victory! It’s a build that my thirteen year old self would have been proud to have done!

-1:-- It’s time to wake up: An Emacs alarm clock (Post Lars Ingebrigtsen)--L0--C0--2026-07-22T16:46:25.000Z

Irreal: Aligning Mode Line Elements

Mode lines. Everybody complains about them and a few brave souls—like Prot—have even taken the time and effort to do something about them. The problem is that they’re so tricky to configure that it’s not worthwhile so we just suffer with the default.

Now Bozhidar Batsov writes about an easy change that anyone can make. As of Emacs 30, it’s possible to right align given elements of the mode line. That was always possible, of course, but it required some tricky padding that was particularly fragile. Now there’s mode-line-format-right-align, which serves as a marker in your mode line definition. Everything before it is left aligned and everything after it is right aligned.

Even the right edge is customizable. You can choose the right edge of the window, the left end of the fringe, or the left edge of the right margin. This facility is going to be useful to those who want a customized mode line.

My main problem with the mode line is getting all the items I want to fit on it. Even though I make liberal use of diminish, a lot of useful information always seems to fall off the right end. That probably means I need to watch Prot’s video again and buckle down to customizing it the way I want it.

In any event, it you’d like to place some items at the right side of the mode line, take a look at Batsov’s post to see how to do it.

-1:-- Aligning Mode Line Elements (Post Irreal)--L0--C0--2026-07-22T14:08:18.000Z

Emacs Redux: Dedicate Windows with toggle-window-dedicated

You’ve carefully arranged your windows just the way you want them - source code on the left, tests on the right, maybe a shell at the bottom. Then you run some command that calls display-buffer, and suddenly one of your carefully placed windows gets hijacked to show a *Help* buffer or a compilation log. We’ve all been there.

The display-buffer machinery is one of Emacs’s most powerful features, but also one of its most frustrating. Emacs 30 adds a simple, practical tool to help tame it: toggle-window-dedicated.

How it works

The concept of “dedicated” windows has existed in Emacs for a long time - a dedicated window tells display-buffer “don’t touch me, display your buffer somewhere else.” But setting this up required Elisp:

(set-window-dedicated-p (selected-window) t)

Not exactly something you’d do interactively in the middle of a coding session. Emacs 30 changes this with a simple keybinding - C-x w d toggles the current window’s dedicated status.

That’s it. Press it once, and the window is dedicated. Press it again, and it’s back to normal.

Here’s the whole thing in action. I dedicate the code window on the left (watch the d appear in its mode line), then ask for help with C-h f - and instead of hijacking my code window, the *Help* buffer politely opens on the right:

Dedicating a window so C-h f opens the Help buffer elsewhere instead of taking over

Mode line indicator

When a window is dedicated, you’ll see a small indicator in the mode line:

  • d - the window is dedicated (soft dedication)
  • D - the window is strongly dedicated

The indicator appears before the buffer name, right after the modification indicators (the usual --- area).

Practical uses

  • Lock your source code windows so that help buffers, grep results, or compilation output land in other windows instead.
  • Protect your shell or REPL window from being taken over.
  • Stabilize complex window layouts when running commands that spawn new buffers.

A workflow tip

A nice pattern is to set up your window layout, then quickly dedicate the windows you want to protect:

  1. Arrange your windows
  2. In each window you want to lock, press C-x w d
  3. Go about your work - display-buffer will respect your dedicated windows

When you’re done and want to return to a more flexible layout, just toggle them off again.

I put off learning the display-buffer machinery for years - it’s famously fiddly - so having a one-keystroke escape hatch for the most common annoyance is a real relief. It won’t replace a properly tuned display-buffer-alist, but for quickly protecting a window in the middle of a session it’s hard to beat.

How do you keep display-buffer from scribbling over your carefully arranged layout - dedicated windows, a hand-tuned display-buffer-alist, or something else entirely? I’d love to hear about it in the comments!

That’s all I have for you today. Keep your windows in their place!

-1:-- Dedicate Windows with toggle-window-dedicated (Post Emacs Redux)--L0--C0--2026-07-22T06:30:00.000Z

Protesilaos: Emacs: pulsar version 1.4.0

This is a small Emacs package that automatically highlights the current line after certain functions are invoked. It can also highlight a line or region on demand. The idea is to make it easier to find where the point is, what was affected, and also to bring attention to something in a buffer. Watch the original demo (2022-03-14).

Below are the release notes.


Version 1.4.0 on 2026-07-22

This version fixes a few bugs and brings quality-of-life improvements to an already stable package.

Correctly pulse the full line even in M-x shell and related

In certain buffers, we cannot use regular motions to reach a portion of text. This is done by text properties that make the propertized text inaccessible. One use-case is to prevent the user from deleting the shell prompt in M-x shell.

Pulsar is now designed to handle inhibit-field-text-motion correctly so that it works as intended in all relevant cases.

Thanks to Jake Miles for telling me that the shell buffer was not pulsing, which made me investigate the underlying problem. This was done in issue 40: https://github.com/protesilaos/pulsar/issues/40.

The pulsing behaviour of Evil commands is more refined

In short:

  • editing commands should pulse the region, not the entire line;
  • evil-delete-char should not produce a pulse;
  • evil-delete-marks is not an editing command, as it removes invisible marks from the buffer.

Thanks to Steven Allen from modifying the relevant variables in pull request 39: https://github.com/protesilaos/pulsar/pull/39. Steven has assigned copyright to the Free Software Foundation.

Also thanks to Javier Pacheco for adding more Evil-related commands to the list, in pull requests 37 and 38:

The pulsar-face is no longer hardcoded in one case

It is always meant to be a variable, specifically a user option, meaning that we want to use its value as defined by the user.

Thanks to irisjae for reporting the bug in issue 36: https://github.com/protesilaos/pulsar/issues/36.

Pulsing is done correctly on the TTY and other frames

The technicalities are covered in issue 33: https://github.com/protesilaos/pulsar/issues/33. Thanks to the participation of Sébastien Delafond who provided feedback about their Emacs environment and helped me test the snippets I had provided.

In a TTY we cannot use what we normally do, so I arrange for a standard colour to be used instead. My rationale for the choice of colour is this:

  • we are in an environment which does not guarantee accurate colour reproduction;
  • it is almost a given that the frame supports at least 8 colours;
  • of those 8 colours we have black, red, green, yellow, blue, magenta, cyan, white;
  • excluding black and white, the colour that contrasts best against both black and white is red;
  • picking red means we do not need to know the background-mode of the frame, which saves us the extra computations.

The contrast table for the six colours, using Org, where Λ is an alias for modus-themes-contrast:

|         | #000000 | #ffffff |
|---------+---------+---------|
| #ff0000 |    5.25 |    4.00 |
| #00ff00 |   15.30 |    1.37 |
| #0000ff |    2.44 |    8.59 |
| #ffff00 |   19.56 |    1.07 |
| #ff00ff |    6.70 |    3.14 |
| #00ffff |   16.75 |    1.25 |
#+TBLFM: $2='(Λ $1 @1$2);%.2f :: $3='(Λ $1 @1$3);%.2f

Pulsing is consistent even when the Emacs daemon is involved

In short, things should work as intended in all sorts of configurations.

Thanks to Koloszár Gergely, Mehrad Mahmoudian, and Nikolaos Bezirgiannis for reporting that the Emacs daemon would produce static highlights instead of pulsing them in and out of effect. This was done in issue 32: https://github.com/protesilaos/pulsar/issues/32.

In this context, also thanks to Gautier Ponsinet for removing an unnecessary let binding. This was sent to me as a patch via email.

The ace-window command is added to the default value of pulsar-pulse-functions

This means that it will produce a pulse effect after it is invoked.

-1:-- Emacs: pulsar version 1.4.0 (Post Protesilaos)--L0--C0--2026-07-22T00:00:00.000Z

Alvaro Ramirez: agent-shell 0.63 updates

It's been a little over a month since the 0.55 update, but plenty has landed since. Let's go through the highlights as of v0.63.

What's agent-shell?

agent-shell is a native Emacs mode to interact with AI agents powered by ACP (Agent Client Protocol).

Supported agents

The roster continues to grow.

This time we get two new agents supported:

Additionally:

  • Claude regains rendering thinking output, since its ACP server started omitting by default.
  • Cursor now runs on the official Cursor CLI (agent acp). Note a recent CLI is required, as the acp subcommand was only added in 2026.
  • Codex now uses @agentclientprotocol/codex-acp.
  • Cline now accepts a default model and session mode via defcustoms (#693 by @nhojb).
  • Goose no longer requires an OpenAI key by default (#711 by @hoyon).
  • Qwen now has explicit support for OpenAI-compatible keys.

Preferred agent

agent-shell-preferred-agent-config gained finer control over agent selection. Set to an agent identifier to skip the picker entirely, or wrap it to keep the picker with a preselected default:

  • (setq agent-shell-preferred-agent-config 'claude-code) always starts Claude, no prompt.
  • (setq agent-shell-preferred-agent-config '(preselect . claude-code)) keeps the picker but preselects Claude as the default.

Custom markdown renderers

The new markdown renderer introduced in 0.55 is now extensible. Third-party packages can claim and render specific markdown constructs (source blocks, inline code ranges, etc) through agent-shell-markdown-render-functions.

Thanks to Andrea Alberti for driving the first integration in agent-shell-math-renderer, which renders LaTeX math equations as SVGs.

agent-shell faces

If you've wanted more theming control over agent-shell buffers, this is now possible via agent-shell-faces.el. Additionally, check out agent-shell-markdown.el for Markdown theming.

Less chatter, more grouping

If your agent session requires lots of tool use (fairly typical), you may notice that shell output is fairly verbose or chatty. This can be quite distracting, so from now on, tool usage as well as thinking are grouped together under an "Activity" section (collapsed by default). If you're a fan of the chatty output, not to worry. Use agent-shell-activity-group-expand-by-default to expand the lot by default.

The new "Activity" section has customisable headers (via agent-shell-activity-group-header-label-function). You now have three ways of rendering the activity header:

Descriptive

Collapsed

Expanded

(setq agent-shell-activity-group-header-label-function
      #'agent-shell-activity-group-descriptive-label)

Count

Collapsed

Expanded

(setq agent-shell-activity-group-header-label-function
      #'agent-shell-activity-group-count-label)

Tally

Collapsed

Expanded

(setq agent-shell-activity-group-header-label-function
      #'agent-shell-activity-group-tally-label)

Inline image and rich content rendering

agent-shell can now render agent-supplied image content inline, whether base64-encoded, a remote URL, or a resource_link/resource content block (#676 by @melito). Audio and other binary resources now render as links that can open externally.

Per-agent MCP servers

You can now attach MCP servers per agent kind through its config using the :mcp-servers field, as requested in #593.

Diff improvements

Section titles now summarize lines added and removed.

In addition, pressing RET in a diff buffer jumps to the file location where the change would apply, while multi-diff permission requests are now supported.

Improved out-of-turn handling

Some agents emit notifications outside of the usual prompt session request/response turn. Historically, these have been dropped by agent-shell. These out-of-turn notifications should now render as expected. Please file a bug if you continue to run into issues.

New copy commands

A few new commands for grabbing hidden content out of the shell buffer:

  • M-x agent-shell-copy-as-markdown copies buffer text as markdown.
  • M-x agent-shell-copy-source-block-at-point copies the source block at point.
  • M-x agent-shell-copy-link-url-at-point copies the URL of the link at point.

Viewport tweaks

The viewport picked up a few refinements:

  • agent-shell-viewport-dismiss-on-send dismisses the viewport once you send.
  • Submitting prompts via C-u C-c C-c now enables you to continue queuing additional requests.
  • History navigation now preserves your in-progress prompt.

Session choices

The new agent-shell-session-choices-function lets you customize what options are presented when starting a new shell. This is useful if you'd like to hide some of these options:

Agent-specific notification adapters

A new notification-adapter mechanism enables agent-specific logic to preprocess notifications before handing back to agent-agnostic handling, starting with a Cursor notification adapter (#702 by @aburtsev).

Narrow to block

M-x agent-shell-narrow-to-block narrows the buffer to the most recent N blocks (defaulting to 1), handy for focusing on the latest exchange (#672 by @arthurgleckler).

Keeping the system awake while busy

To keep a long agent turn from being cut short by idle sleep, agent-shell now keeps the system awake while an agent is busy, releasing it as soon as the turn finishes (the display may still blank). It won't override a hard sleep such as closing a laptop lid. This needs the system-sleep library (Emacs 31.1+) and can be disabled via agent-shell-inhibit-system-sleep. Thanks to @shipmints for proposing the idea.

Artist support

M-x agent-shell-artist-insert-text-drawing lets you sketch simple text drawings to drop into a prompt. Check out Bending Emacs episode 14 for a demo, crafting iOS UI.

New third-party packages

The agent-shell package family keeps expanding. Recent additions:

Maintenance and housekeeping

Keeping up with the project requires daily work. Luckily, I'm getting a bit better at time management since my new 24-hour job, so I've been catching up on general project maintenance, delivering bugfixes and feature requests, merging pull requests, and general bookkeeping.

At its most backed-up period, in mid-May, agent-shell's open issues peaked at 75 while pending PRs peaked at 29.

As of today, we are down to 13 open issues and 4 open PRs.

Side note: this chart was generated using the /github-activity skill shared in my emacs-skills repo.

agent-shell needs your support

Vendor-neutral tooling matters, and there are a couple of ways to help keep agent-shell going. Some cost money, others just a click. All are appreciated ;)

Sponsorships for agent-shell longevity

agent-shell is just me, an indie dev, while the tools it competes with have well-funded teams behind them. If this project is useful to you, please consider sponsoring the project. And if your employer benefits from your agent-shell use, nudge them to chip in too, they can typically contribute at a scale individuals can't.

GitHub stars for Anthropic credit

Anthropic offers 6 months of free Claude Max 20x for qualifying open-source projects with at least 5,000+ GitHub stars. Starring agent-shell costs nothing and can save me some money, so if you don't mind a couple of clicks, the project can really use another GitHub star.

Pull requests

Thank you to all contributors for these improvements!

Make it sustainable, sponsor the effort

Liking agent-shell? Would like to see it evolve? Consider sponsoring the effort.

-1:-- agent-shell 0.63 updates (Post Alvaro Ramirez)--L0--C0--2026-07-22T00:00:00.000Z

Raymond Zeitler: Modeling Browser History in Emacs

If there were an underappreciated web browser feature, it would be this: history.

On many browsers, history is invoked with C-h (Ctrl+h). Vivaldi shows history as a calendar. The user can choose from time intervals of Day, Week or Month and search for a keyword within that time interval.1

I tend to write about a topic after I've forgotten where I read about it. Suppose I recall reading about "nslookup" but can't remember when or where. Simple. Vivaldi remembered that I visited a page with "nslookup" on Windows Central on July 3 at 21:26. Being able to find such references helps.

History helped me track my hours when my employer required bi-weekly timesheets. I would combine browser history with Org's clock report to get a result I was actually proud to submit.

I still track my time with Org, which stores start and stop times in a drawer called LOGBOOK. But I tend to forget to start or stop the clock, so the times are unreliable. Sometimes a file's timestamp can help me reconstruct the LOGBOOK. But at times like these I'd wish that Emacs had a history feature.

Actually Emacs does have a few history-like features. One example is savehist-mode,2 which "save[s] the values of minibuffer history variables." Unfortunately, timestamps are not included.

So I configured Emacs to write a timestamp to the *Messages* buffer every time it loads or saves a file. This frees me from switching to a file manager to check a file's timestamp. Using Customize, I added functions to find-file-hook and after-save-hook:3

(find-file-hook
 '(lambda nil
     (message "%s: Loaded %s"
              (format-time-string "%Y-%m-%d %H:%M:%S" (current-time))
              (buffer-file-name))))
(after-save-hook
 '(lambda nil
    (message "%s: Saved %s"
             (format-time-string "%Y-%m-%d %H:%M:%S" (current-time))
             (buffer-file-name))))

Whenever I realize I'm not clocked in, I visit the *Messages* buffer and look for the first occurrence of something like this:

2026-07-20 12:32:33: Saved c:/Users/RayZ/AppData/Roaming/HOME/blog.org

Then I clock in and change the start time to 12:33.4

Similarly, if I've been away from the computer and realize I'm still clocked in, I'll look for the last occurrence of the message, clock out, and change the time accordingly.

The *Messages* buffer is temporary; timestamps from an earlier Emacs session are lost. But this simple system does what I need.

What methods do you use to track project time?


1 The user also can view history as a simple list in reverse chronological order. And the search can span the entire history, if desired.

2 https://doc.endlessparentheses.com/Fun/savehist-mode.html

3 Note that these expressions are arguments to custom-set-variables.

4 Timesheets required time entries in units of hours rounded to one decimal. A tenth hour is 6 minutes. So I configured Org to round clock times to 3 minutes by setting org-clock-rounding-minutes to 3. This affords more granularity. With several clock entries per heading, an even number of 0:03 clock entries would produce the desired tenth hour accuracy.

-1:-- Modeling Browser History in Emacs (Post Raymond Zeitler)--L0--C0--2026-07-21T21:37:58.035Z

Irreal: Flymake Diagnostics

Bozhidar Batsov has a quick post on a recent enhancement to Emacs Flymake. The TL;DR is that you can arrange for Flymake to append a diagnostic at the end of any line with a problem. It’s a quick way of seeing what the problem is and maybe fixing it without a lot of extra work.

As Batsov (and the documentation) says, that can get a little noisy so (of course) Emacs gives you a setting to control how much information is displayed. Take a look at Batsov’s post for the details.

I’ve never warmed up to Flymake but lots of people, I know, love it. If you’re one of those people, be sure to take a look at Batsov’s post. Everything is controlled by a single variable so it’s easy to try out the various options.

This feature is available only with Emacs 30 and later. Getting it may be a good reason to upgrade if you haven’t already.

-1:-- Flymake Diagnostics (Post Irreal)--L0--C0--2026-07-21T15:03:50.000Z

James Dyer: Borrowing from Jasspa MicroEmacs : Dired Sort Keybindings

I've been exploring Jasspa MicroEmacs lately - a lightweight Emacs like editor with a surprisingly thoughtful set of defaults, its footprint is surprisingly small, 1-2MB on your disk, useful for a portable Emacs or in my case allows me to run up an Emacs like editor on my work laptop (as it blocks GNU Emacs). No installation, just a single very small executable and it comes in many different varieties, windows, linux and GUI or terminal based.

20260721093224-emacs--Borrowing-from-Jasspa-MicroEmacs-Dired-Sort-Keybindings.jpg

For those unfamiliar with the lineage, MicroEmacs originally dates back to 1985 when Dave Conroy built a tiny, portable Emacs clone, later maintained for decades by Daniel M. Lawrence (and famously still used by Linus Torvalds today!). Around 1988, Jon Green and Steven Phillips forked MicroEmacs 3.8 to build Jasspa, it uses a subset of full emacs command set and it can emulate the keybindings from Microemacs, GNU Emacs, and even Windows CUA keybindings. It's a much enhanced version of the Daniel Lawrence's original MicroEmacs 3.8 of 1988 and they have grown it into a remarkably feature-packed environment with syntax highlighting, windowing, and a macro language and its still regularly updated!

One thing that immediately caught my eye in Jasspa was its file manager (a dired equivalent that, while not a direct clone of GNU Dired is surprisingly functional). Specifically: single numeric keystrokes to instantly re-sort directory listings by size, time, name, or extension.

Back in GNU Emacs, I usually reach for s in Dired to cycle the sort, or C-u s to manually type ls switches. It works, but it's the kind of subtle friction you don't notice until you've tried a direct alternative. Once I'd spent an afternoon in Jasspa, the muscle memory was already forming:

  • 3 for size when cleaning up disk-hungry directories
  • 4 for date when looking for recent modifications
  • 5 to reset back to alphabetical
  • 6 to group by extension

Bringing this to GNU Emacs took less than thirty lines of Elisp - four small wrapper functions around dired-sort-other and a few keybindings:

(defun my/dired-sort-by-size ()
  "Sort Dired buffer by file size."
  (interactive)
  (dired-sort-other "-alGghS"))
(defun my/dired-sort-by-date () "Sort Dired buffer by last modification date." (interactive) (dired-sort-other "-alGght"))
(defun my/dired-sort-by-name () "Sort Dired buffer alphabetically by name." (interactive) (dired-sort-other "-alGgh"))
(defun my/dired-sort-by-extension () "Sort Dired buffer by file extension." (interactive) (dired-sort-other "-alGghX"))
(with-eval-after-load 'dired (define-key dired-mode-map (kbd "3") #'my/dired-sort-by-size) (define-key dired-mode-map (kbd "4") #'my/dired-sort-by-date) (define-key dired-mode-map (kbd "5") #'my/dired-sort-by-name) (define-key dired-mode-map (kbd "6") #'my/dired-sort-by-extension))

More posts to come I think on Jasspa!

-1:-- Borrowing from Jasspa MicroEmacs : Dired Sort Keybindings (Post James Dyer)--L0--C0--2026-07-21T08:32:00.000Z

Emacs Redux: Right-aligned Mode Line Elements

Emacs users have long wanted the ability to push certain mode line elements to the right edge of the window. Think of it like having a left-aligned and right-aligned section in your mode line - buffer name on the left, maybe the time or some status info on the right.

Before Emacs 30, achieving this required various hacks involving padding calculations and propertized strings. It was doable, but fragile and ugly.

Emacs 30 makes it trivial with mode-line-format-right-align.

How it works

Just insert the symbol mode-line-format-right-align into your mode-line-format. Everything before it is left-aligned (as usual), and everything after it gets pushed to the right edge.

(setq-default mode-line-format
              '("%e" mode-line-front-space
                mode-line-mule-info
                mode-line-modified
                " "
                mode-line-buffer-identification
                mode-line-format-right-align
                mode-line-modes
                mode-line-misc-info
                mode-line-end-spaces))

In this example, the buffer identification stays on the left, while modes and miscellaneous info are pushed to the right.

Controlling the alignment edge

By default, right-aligned elements are pushed to the extreme right of the window. You can control this with mode-line-right-align-edge:

;; Align to the left edge of the right fringe (default is 'window)
(setq mode-line-right-align-edge 'right-fringe)

The available values are:

Value Alignment point
window Extreme right edge of the window (default)
right-fringe Left edge of the right fringe
right-margin Left edge of the right margin

A practical example

Here’s a minimal setup that puts the buffer name and modification status on the left, and the current line/column and major mode on the right:

(setq-default mode-line-format
              '(" %+ "
                mode-line-buffer-identification
                mode-line-format-right-align
                mode-line-position
                " "
                mode-line-modes))

And here’s the result - the buffer name sits on the left while the position and major mode are pushed all the way to the right:

A mode line with the buffer name on the left and the position and modes right-aligned

Clean, readable, and no hacks required.

For years I put up with a mode line that was either cluttered on the left or propped up with fragile padding hacks that broke the moment I resized a window. Having a proper right-aligned section built into Emacs feels almost too easy - the kind of small quality-of-life fix that makes you wonder what took so long.

What do you like to keep on the right side of your mode line - the time, the battery level, some VC info? I’d love to hear how you’ve arranged yours in the comments!

That’s all I have for you today. Keep your mode line in line!

-1:-- Right-aligned Mode Line Elements (Post Emacs Redux)--L0--C0--2026-07-21T05:00:00.000Z

Marcin Borkowski: Hideshow mode

Recently I discovered a very cool feature of Emacs. Apparently, it’s been present in Emacs for over 30 years, although the Git log shows that it’s been heavily worked on very recently. Meet Hideshow mode. It’s purpose is simple: it allows to, well, hide or show blocks of code.
-1:-- Hideshow mode (Post Marcin Borkowski)--L0--C0--2026-07-20T19:48:18.000Z

Irreal: A Small Jinx Tweak

As I’ve written recently [1, 2, 3, 4], I’ve been changing the Irreal Bunker’s spelling corrector to Jinx. I’ve had a couple of issues—mostly with choosing the correction—but things have mostly been smooth and Jinx has a lot of nice features.

One thing I don’t like is the face that Jinx chooses for misspelled words. Ispell used red, which stood out and made it easy to pick out the misspellings. Jinx uses a color that is almost the same as the text and even though misspellings are underlined with a wavy line they’re harder to pick out. There are two colors: one for dark themes and one for light themes. Oddly, there are no options for changing the colors.

Happily, this is Emacs so the source code is only a click away. It turns out the color is defined by a defface in the jinx.el file. I copied that to my init.el and changed the color to red Here’s the result:

(defface jinx-misspelled
  '((((class color) (min-colors 88) (background dark)
      (supports :underline (:style wave)))
     :underline (:style wave :color "#d2b580"))
    (((class color) (min-colors 88) (background light)
      (supports :underline (:style wave)))
     :underline (:style wave :color "#ff0000")) ; <-- changed color
    (t :underline t :inherit error))
  "Face used for misspelled words.")

I was going to change the color for the dark theme too just for completeness but the Minions wouldn’t allow it. You know how they are about dark themes. If you want to change the color in your Emacs, just paste the above into your init.el and choose whichever color works for you. You can, of course, also fix things up for dark themes. Just don’t tell the Minions.

-1:-- A Small Jinx Tweak (Post Irreal)--L0--C0--2026-07-20T14:34:46.000Z

Sacha Chua: 2026-07-20 Emacs news

Completion seems to be the topic of the day. Check out the new videos and resources people have shared on both completion in buffer and in the mini-buffer. Also, there's been lots of discussion for or against Human Emacs; links are in the Community section.

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

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

-1:-- 2026-07-20 Emacs news (Post Sacha Chua)--L0--C0--2026-07-20T12:27:43.000Z

Emacs Redux: Flymake Can Now Show Diagnostics at the End of the Line

If you’ve used modern editors like VS Code, you’ve probably noticed how they display error and warning messages inline, right at the end of the offending line. It’s a nice touch - you don’t have to hover over a squiggly underline or check a separate diagnostics buffer to see what’s wrong.

Emacs 30 brings this capability to Flymake with the new flymake-show-diagnostics-at-end-of-line option.

Enabling it

(setq flymake-show-diagnostics-at-end-of-line t)

With this set, Flymake will display diagnostic summaries right at the end of each affected line, in addition to the usual fringe indicators and underlines. Here’s what it looks like in an Emacs Lisp buffer with a few issues (an error, a warning, and a checkdoc note):

Flymake showing an error, a warning and a note at the end of the offending lines

Display options

The option accepts three values:

Value Behavior
nil Don’t show inline diagnostics (default)
short Show only the most severe diagnostic per line
t Show all diagnostics for the line

If a line has both a warning and an error, the short setting will only show the error. Using t shows everything - which can get a bit noisy on lines with multiple issues, but gives you the complete picture at a glance.

;; Only show the most important diagnostic per line
(setq flymake-show-diagnostics-at-end-of-line 'short)

Is this distracting?

Honestly, it depends on your taste. The Emacs documentation itself acknowledges the trade-off:

Depending on your preference, this can either be distracting and easily confused with actual code, or a significant early aid that relieves you from moving the buffer or reaching for the mouse to consult an error message.

I’d suggest trying it for a few days. If you find it too noisy, the short setting is a good middle ground - you still get inline feedback, but limited to the most severe issue on each line.

I was sure I’d hate this - text floating around in my buffer sounded like pure clutter - but the short variant won me over surprisingly fast. Having the message right there, without reaching for the mouse or popping open the diagnostics buffer, is one of those small conveniences you stop noticing precisely because it’s always quietly helping.

How do you like to consult your diagnostics - inline, in the fringe, or in a dedicated buffer? I’d love to hear your thoughts in the comments!

That’s all I have for you today. Keep those errors in plain sight!

-1:-- Flymake Can Now Show Diagnostics at the End of the Line (Post Emacs Redux)--L0--C0--2026-07-20T06:30:00.000Z

Raymond Zeitler: Emacs Tip Of The Day in a Popup Frame

The Emacs commands that create a new frame disappoint me. They create a frame that shares the same set of buffers as the main frame. I don't think a Tip Of The Day (TOTD) popup should have access to all the buffers in a session. I want a popup to display some help and be easily dismissed.1

That's why I designed my Tip Of The Day to run in a separate Emacs instance. Instead of invoking the TOTD function directly, Emacs asks the operating system to launch a second Emacs process. One of the command line switches loads the Lisp file in which the TOTD function is defined, and another runs the function. This is achieved by invoking call-process-shell-command to run a one-line batch file2 that contains this:

"path_to_emacs\emacs.exe" -Q -g 80x42-2+2 -l totd.el --eval (ztotd)

The -l switch loads totd.el, defining both totd and ztotd. The --eval switch then invokes ztotd. (The other function, totd is not used; it's retained for reference.) The -Q switch starts Emacs "quietly." -g defines window geometries:3 WxH+X+Y, where

  • W and H specify the frame's width and height in character units.
  • X and Y, if ≥0, specify the pixel coordinates of the upper left corner of the window. If <0, specify the distance from the right and bottom of the screen.

This is the statement in my init file that runs the batch file:

(call-process-shell-command "cmd.exe /Q /D /C invoke-emacs-totd.bat" nil 0)

Here's the listing for totd.el. The first function, totd, is based on code that Dave Pearson posted on EmacsWiki.4 To make it work with modern Emacs, I replaced (require 'cl) with (require 'cl-lib) and (loop for s...) with (cl-loop for s...).

;; Time-stamp: "2026-07-19 17:00:17 RayZ"
;; totd.el by DavePearson
;; This small code snippet displays a “tip of the day”
;;
;; 2026-07-14 Downloaded from https://www.emacswiki.org/emacs/TipOfTheDay
;; 2026-07-19 Derive ztotd to run in separate session

(require 'cl-lib)

(defun totd ()
 (interactive)
 (with-output-to-temp-buffer "*Tip of the day*"
   (let* ((commands (cl-loop for s being the symbols
                          when (commandp s) collect s))
          (command (nth (random (length commands)) commands)))
     (princ
      (concat "Your tip for the day is:\n========================\n\n"
              (describe-function command)
              "\n\nInvoke with:\n\n"
              (with-temp-buffer
                (where-is command t)
                (buffer-string)))))))

(defun ztotd ()
  "Display documentation of a random Emacs function to provide
a Tip Of The Day.  It is intended to run in a separate
session of Emacs where it modifies the frame to resemble a
minimalist popup.  Invoke at the command line with (for example):

  emacs.exe -Q -g 80x42-2+2 -l totd.el --eval (ztotd)"
 (interactive)
 (set-buffer (generate-new-buffer "Tip of the day"))
   (let* ((commands (cl-loop for s being the symbols
                          when (commandp s) collect s))
          (command (nth (random (length commands)) commands)))
     (insert
      (concat (describe-function command)
              "\n\nInvoke with:\n\n"
              (with-temp-buffer
                (where-is command t)
                (buffer-string))))
     (tool-bar-mode 0)
     (menu-bar-mode 0)
     (setq frame-title-format '("Gnu Emacs Tip of the day"))
     (pop-to-buffer (current-buffer))
     (goto-char (point-min))
     (delete-other-windows)
     (message "Press C-x C-c to dismiss")))

totd is very intrusive by design -- it's supposed to put a help page "in your face." But I wanted a "clean" look. So I eliminated the toolbar and menu from the frame, moved "Tip of the Day" from the buffer to the title, and got rid of other windows. Plus I call it with call-process-shell-command instead of shell-command to prevent a shell output window from appearing in the main frame.5

The batch file allows the OS itself to initiate the display of TOTD. It can be set up to run right after logon, or combined with Tea Timer or Scheduler for a periodic display.

I like how this performs. However, I hardly ever close and restart Emacs. As a result, I may go a week or two without seeing a tip. Is that why Emacs has never included a built-in Tip Of The Day? Or is it just so easy to implement that "the proof has been left as an exercise to the user?" Or maybe the help that's shown is too specific. For example, "What does image-dired-delete-tag actually do?" you might wonder. I think that's the whole point of a TOTD -- to inspire wonder!

If you try this on a non-Windows system, please let me know whether it works.


1 I tried display-buffer-pop-up-frame. Somehow the popup lost focus and got buried behind the main frame. When I closed the main frame, I thought that all my files were saved and Emacs was closed. Only then did I realize the popup was still running. When I tried to close it, Emacs warned about open files. So nervously I maximized the small popup frame to review the unsaved buffers.

2 In fact the batch file contains several lines of comments, too, as well as the cherished @echo off statement at the very beginning. @echo off prevents the many lines of comments from getting dumped to standard output; otherwise those comments would end up in a shell buffer.

3 For more information on command line switches for Emacs, see: https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Invocation.html

4 Please see https://www.emacswiki.org/emacs/TipOfTheDay

5 Thanks to Jackson Ray Hamilton for this suggestion. https://stackoverflow.com/a/22982525

-1:-- Emacs Tip Of The Day in a Popup Frame (Post Raymond Zeitler)--L0--C0--2026-07-19T22:29:30.872Z

Sacha Chua: Mon flux de travail de programmation

en français

Pour le Carnaval d'Emacs de juillet sur la programmation (merci à AndyDrop de l'accueillir !) :

Je m'occupe principalement de ma fille (particulièrement pendant les pauses scolaires comme l'été), donc mon temps, mon attention et mon énergie sont très limités. Je dois avancer petit à petit car c'est difficile de trouver plus d'une heure pour du travail profond. Je suis chanceuse si je finis une ou deux de mes propres tâches pendant la journée.

Presque toute ma programmation est pour mes propres objectifs à l'exception de quelques heures de temps en temps quand je travaille comme consultante. Pour mon client, j'écris des prototypes en utilisant JavaScript (ou Python, rarement).

Je préfère écrire la plupart de mes outils dans Emacs Lisp parce qu'Emacs me donne une interface interactive et l'intégration potentielle avec tous mes autres outils Emacs. Ça signifie que je peux peut-être en tirer un grand bénéfice pour un petit effort.

À cause de mes limites, je suis souvent interrompue. Utiliser Org Mode me permet d'inclure des notes et des liens en cours de route, ce qui est absolument essentiel pour moi car j'oublie souvent ce à quoi je pensais une fois que je peux retourner à ma tâche après les interruptions d'autres idées ou de mes activités de maman. Si je finalise la tâche (ou au moins avance un peu), je l'ajouterai à ma configuration ou à mes notes, comme un flux de travail. Par exemple, mes tâches récentes ont inclus :

  • Extraire mes tentatives de virelangues de mon enregistrement pour en faire une courte vidéo pour m'aider à me réécouter et pour suivre ma progression ; j'ai utilisé subed-record
  • Compter mes tâches par état avec org-ql pour écrire un article

Je commence souvent par une tâche dans ma boîte de réception Org Mode, où j'ajoute un ou plusieurs blocs de code, ce qui est facilité par la configuration de la variable org-structure-template-alist où je remplace l'entrée pour « l » par « src emacs-lisp » au lieu de « export latex », comme ça :

(setq org-structure-template-alist
      '(("a" . "export ascii")
        ("C" . "center")
        ("c" . "comment")
        ("d" . "my_details")
        ("e" . "example")
        ("E" . "export")
        ("m" . "export md")
        ("M" . "media-post")
        ("h" . "export html")
        ("j" . "src js :spookfox t")
        ("l" . "src emacs-lisp")
        ("p" . "src python")
        ("n" . "notes")
        ("q" . "quote")
        ("s" . "src")
        ("S" . "src sh")
        ("u" . "update")
        ("v" . "verse")))

De temps en temps, la tâche est plus facile si j'utilise des bibliothèques qui sont disponibles seulement pour Python ou JavaScript. Par exemple, Python a beaucoup de fonctions pour analyser des données ou faire des visualisations, et JavaScript est utile pour modifier des sites. Heureusement, je peux les utiliser avec les commandes shell-command-to-string ou make-process. Je peux aussi utiliser d'autres blocs de source et je peux rediriger les résultats vers d'autres blocs en utilisant la directive #+NAME et les paramètres :results et :var. Ça signifie que je peux choisir le langage le plus efficace. Par exemple, quand je veux analyser mes entrées de journal en français, j'utilise Python parce que la bibliothèque SpaCy peut identifier et classifier les lemmes de chaque mot et Pandas et Matplotlib peuvent les visualiser.

L'interaction directe avec des sites est facilitée par Spookfox, qui me permet de contrôler le navigateur Firefox d'Emacs, y compris l'exécution de JavaScript. (Ma configuration) Par exemple, je l'utilise pour extraire des données d'une page ou simplifier le code HTML pour le copier dans un document.

Si je veux utiliser la fonctionnalité en ligne de commande (comme mes scripts pour utiliser la reconnaissance vocale pour transcrire les enregistrements longs), j'écris un script séparé au lieu d'utiliser Org Mode.

L'implémentation d'une idée génère naturellement plusieurs idées. Je m'y habitue. Je ne finaliserai jamais ma liste de tâches. De temps en temps, je relis mes tâches et j'annule les tâches dont je n'ai pas besoin ou dont je n'ai pas envie.

Un jour, je voudrais configurer un flux de travail moderne avec toutes les améliorations que d'autres utilisateurs aiment, comme LSP ou Eglot, et je veux aussi finaliser des correctifs pour contribuer à Emacs et à des paquets. Mais pour le moment, ça suffit pour m'aider à survivre à cette étape de ma vie.

English

For the July Emacs Carnival on programming (thanks to AndyDrop for hosting!):

I mainly take care of the kiddo (especially when she doesn't have school, like this summer), so my time, attention, and energy are pretty limited. I have to make progress a little at a time because it's hard to find more than an hour for deep work. I'm lucky if I finish one or two of my own tasks during the day.

Almost all of my programming is for my personal stuff, except for a few hours from time to time when I work as a consultant. For my client, I write prototypes using JavaScript (or occasionally Python).

I prefer to write most of my tools in Emacs Lisp because Emacs gives me an interactive interface and I can integrate things with all my other Emacs tools. This means I can potentially get a big benefit for a small effort.

Because of my constraints, I often have to deal with interruptions. Using Org Mode allows me to include notes and links along the way, which is essential for me because once I've finally gotten back to my task after interruptions from other ideas or from family life, I've often forgotten what I was thinking about. If I actually finish the task (or at least make a little progress), I add it to my configuration or my workflow notes. For example, I recently:

  • Extracted my tongue twister attempts from my recording to make a short video so that I could re-listen to it and track my progress; I used subed-record
  • Counted my tasks by status with org-ql in preparation for writing a post

I often start with a task in my Org Mode inbox. I add one or more code blocks. This is easier since I've configured the org-structure-template-alist variable to replace the entry for "l" with "src emacs-lisp" instead of "export latex", like this:

(setq org-structure-template-alist
      '(("a" . "export ascii")
        ("C" . "center")
        ("c" . "comment")
        ("d" . "my_details")
        ("e" . "example")
        ("E" . "export")
        ("m" . "export md")
        ("M" . "media-post")
        ("h" . "export html")
        ("j" . "src js :spookfox t")
        ("l" . "src emacs-lisp")
        ("p" . "src python")
        ("n" . "notes")
        ("q" . "quote")
        ("s" . "src")
        ("S" . "src sh")
        ("u" . "update")
        ("v" . "verse")))

Sometimes it's easier to do something if I use libraries that are only available for Python or JavaScript. For example, Python has a lot of functions for analyzing data or creating visualizations, and JavaScript is useful for modifying websites. Good thing I can use them with the shell-command-to-string or make-process commands. I can also use other source blocks and pipe the results to other blocks using the #+NAME keyword and the :results and :var parameters. This means I can choose whichever language makes sense. For example, when I want to analyze my journal entries in French, I use Python because the SpaCy library can identify and classify the lemmas of each word, and Pandas and Matplotlib can visualize them.

I use Spookfox to interact directly with websites. This allows me to control the Firefox browser from Emacs, including executing JavaScript. (My configuration) For example, I use it to extract data from a page or simplify the HTML code to copy it into a document.

If I want to use this from the command-line (like my scripts that use speech recognition to transcribe long recordings), I write a separate script instead of using Org Mode.

Implementing one idea naturally leads to several more ideas. I'm mostly used to that now. =) I'll never finish my to-do list, and that's okay. From time to time, I review my tasks and cancel the ones I don't need or don't want.

One day, I'd like to set up a modern workflow with all the enhancements that other folks enjoy, like LSP or Eglot. I also want to actually finish patches so I can contribute to Emacs and other packages. For now, this is just enough to help me survive this stage of my life!

View Org source for this post

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

-1:-- Mon flux de travail de programmation (Post Sacha Chua)--L0--C0--2026-07-19T01:41:48.000Z

Protesilaos: Emacs: Completion in the buffer with completion-at-point-functions

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

In this ~22-minute video I explain how Emacs performs completion in a buffer. I cover what happens behind the scenes with the variable completion-at-point-functions. Then I also go to the frontends, which affects the modalities of interaction. I cover t built-in Completions buffer, the packages Corfu and Company, and the built-in completion-preview-mode. Finally, I comment at some further length on how to control the grouping of results in the completion-at-point-functions. In that context I also mention the Cape package, as well as some extra functionality that the Company package provide. I wrote an article which covers all of what is in the video as well as code samples. Its text is below.

Table of Contents

  1. Make TAB complete when it can
  2. The in-buffer completion backends (completion-at-point-functions)
    1. Common providers of CAPFs
  3. The completion frontends
    1. Corfu for an in-buffer completion popup window
      1. My preferred corfu setup plus cape
    2. Company for an in-buffer completion popup window
      1. My preferred Company setup
    3. The built-in completion-preview-mode for a minimalist single suggestion at a time
      1. My preferred setup for completion-preview-mode

Make TAB complete when it can

By default, Emacs uses TAB to align the current line or selected region of text. It does so based on the relevant rules provided by the major mode (e.g. a programming mode may have specific requirements for how the body of a function definition is indented).

We can tell Emacs to also use TAB to perform completion once no further alignment is possible in the given context.

(setq tab-always-indent 'complete)

The in-buffer completion backends (completion-at-point-functions)

The completion-at-point-functions is a variable (technically an abnormal hook) that takes as its value a list of functions. We can call those “CAPFs”, for “completion at point functions”. Those are used to perform completion in the buffer, with the text right before the cursor as their input.

When we request completion for the text at point, either via TAB or the command completion-at-point (C-M-i by default), Emacs looks at the value of the completion-at-point-functions, and calls the functions found there in order from first to last (or left to right).

Normally, once a function returns results, Emacs will display those and will not move to the remaining functions in the completion-at-point-functions. So if we have many functions, we may not see of them at once. To do so we need more advanced solutions, which I comment on further below.

The completion-at-point-functions typically has a buffer-local value. This means that the functions we run in one buffer will likely be different than what we have in another buffer. For example, in a programming mode it makes sense to use the Language Server Protocol to get comprehensive suggestions for the language we are programming in, whereas in Org mode we may only wish to see the correct spelling of a word.

When the completion-at-point-functions has a buffer-local value, it will typically include a t. This is a reference to its corresponding global value. So if Emacs tries to complete and reaches the t, it then goes through the list with the global CAPFs.

Common providers of CAPFs

  • specialised minor modes;
  • the major mode;
  • the Language Server Protocol via its Emacs client (e.g. eglot or lsp-mode);
  • the cape package;
  • the company package;
  • other packages that may extend company or related.

The completion frontends

For a few years now Emacs has a unified infrastructure at its core for how completions are done. Users should not mess around with that. What they can do instead is choose how the completions are displayed as well as the relevant modalities for filtering and selecting candidates.

Corfu for an in-buffer completion popup window

A package by Daniel Mendler, the developer of vertico and consult, among others, which is built on top of the completion-at-point-functions. It is designed to be fast and effective, with a good out-of-the-box experience and reasonable extensions.

By default, corfu does not automatically pop up its window. Users must manually invoke completion or opt in to the auto-completing functionality.

;; Read the comprehensive documentation of Corfu for further customisation options.
(use-package corfu
  :ensure t
  :init
  (setq tab-always-indent 'complete)
  :config
  ;; ;; If you want the popup window to appear automatically as you type:
  ;;
  ;; (setq corfu-auto t)
  (global-corfu-mode 1))

My preferred corfu setup plus cape

This is just for your information. It fits my use-case, but I do not recommend it as a starting point. The cape package is also developed by Daniel Mendler.

(use-package corfu
  :ensure t
  :init
  (setq tab-always-indent 'complete)
  :config
  (setq corfu-preview-current nil)
  (setq corfu-min-width 20)

  (setq corfu-popupinfo-delay '(1.25 . 0.5))
  (corfu-popupinfo-mode 1) ; shows documentation after `corfu-popupinfo-delay'

  (global-corfu-mode 1)

  ;; I also have (setq tab-always-indent 'complete) for TAB to complete
  ;; when it does not need to perform an indentation change.
  (define-key corfu-map (kbd "<tab>") #'corfu-complete)

  ;; Sort by input history (no need to modify `corfu-sort-function').
  (with-eval-after-load 'savehist
    (corfu-history-mode 1)
    (add-to-list 'savehist-additional-variables 'corfu-history)))

(use-package cape
  :ensure t
  :after corfu
  :config
  ;; This is for the global value.
  (setq completion-at-point-functions '(cape-dabbrev cape-file))

  (defun prot/cape-super-set-local (capfs &optional individual-capfs)
    "Set `completion-at-point-functions' to current value plus CAPFS.
Treat CAPFS and the default value as a super CAPF.  Then append the
INDIVIDUAL-CAPFS to the list."
    (let* ((all-for-super (append completion-at-point-functions capfs))
           (all-minus-global (delq t all-for-super))
           (cape-super (apply #'cape-capf-super all-minus-global)))
      (setq-local completion-at-point-functions (append (list cape-super) individual-capfs (list t)))))

  (defun prot/cape-prog-setup ()
    "Set up Cape for programming."
    (prot/cape-super-set-local '(cape-dabbrev) '(cape-file)))

  (add-hook 'prog-mode-hook #'prot/cape-prog-setup)

  (defun prot/cape-text-setup ()
    "Set up Cape for prose."
    (prot/cape-super-set-local '(cape-dict cape-dabbrev cape-emoji) '(cape-file)))

  (add-hook 'text-mode-hook #'prot/cape-text-setup))

Company for an in-buffer completion popup window

This package by Dmitry Gutov is older than corfu though remains in active development and has all the features of Corfu plus some extras. Fundamentally though, it does the same thing, so if you are already using one there is no pressing need to switch to the other.

As of this writing (2026-07-19), company version 1.1.0 is still in development. It comes with many nice features, such as native support for tab-always-indent, child frames, and in-buffer completion in the minibuffer.

By default, and unlike corfu, Company automatically pops up its window. You can opt out if you wish to.

;; Remember to read the comprehensive documentation of Company for all
;; the customisation options and extra features.
(use-package company
  :ensure t
  :init
  (setq tab-always-indent 'complete)
  :config
  (global-company-mode 1))

My preferred Company setup

The company includes several CAPFs, unlike corfu which spins them out into the separate cape pacakge. Company CAPFs can easily be grouped to collect the results of many functions into a single popup window.

This is what I am currently using. Again, I am sharing it for your information and do not meant to imply that it is useful in general.

(use-package company
  :ensure t
  :init
  (setq tab-always-indent 'complete)
  :config
  ;; NOTE 2026-07-19 with the next version of Company, which may be
  ;; 1.1.0, (setq tab-always-indent 'complete) is supported
  ;; out-of-the-box, which means that we disable the automatic popup
  ;; feature and rely on TAB to perform completion in the buffer.
  (setq company-idle-delay nil)

  (setq company-tooltip-limit 6)
  (setq company-tooltip-minimum-width 25)
  (setq company-tooltip-align-annotations t)
  (setq company-dabbrev-code-completion-styles t) ; use the `completion-styles'

  (setq company-transformers '(company-sort-by-occurrence
                               company-sort-by-backend-importance
                               company-sort-prefer-same-case-prefix))

  (setq company-frontends '(company-childframe-unless-just-one-frontend
                            company-preview-if-just-one-frontend
                            company-echo-metadata-frontend))

  ;; Notice the `(company-capf company-dabbrev)', which represents a
  ;; grouping of CAPFs, whereas `company-files' shows results of its own.
  (setq company-backends '((company-capf company-dabbrev) company-files))

  ;; IGNORE THIS AS IT IS RELEVANT ONLY FOR MY CONFIGURATION which
  ;; does a lot with the `display-buffer-alist'.
  (define-advice company-show-doc-buffer (:around (&rest args) prot)
    (let ((help-window-select nil)
          (display-buffer-alist nil)
          (display-buffer-overriding-action
           '((display-buffer-reuse-mode-window display-buffer-below-selected)
             (window-height . 0.3)
             (post-command-select-window . nil))))
      (apply args)))

  (defun prot/company-prog-backends ()
    "Set `company-backends' locally to my preferred value for `prog-mode' and derivatives."
    (setq-local company-backends '((company-capf company-dabbrev-code) company-files)))

  (add-hook 'prog-mode-hook #'prot/company-prog-backends)

  (defun prot/company-text-backends ()
    "Set `company-backends' locally to my preferred value for `text-mode' and derivatives."
    (setq-local company-backends '((company-dabbrev company-ispell company-capf) company-files)))

  (add-hook 'text-mode-hook #'prot/company-text-backends)

  (global-company-mode 1))

(use-package company-statistics
    :ensure t
    :after company
    :config
    (company-statistics-mode 1))

The built-in completion-preview-mode for a minimalist single suggestion at a time

Since Emacs 30, there is the completion-preview-mode by Eshel Yaron. Unlike the other options, it displays only one suggestion at a time. It does it inline as you type. You can still cycle through suggestions or display more of them with either the default *Completions* buffer or one of the aforementioned packages.

Note that Company also has the equivalent of completion-preview-mode. Check the company-frontends for more.

completion-preview-mode does not provide its own CAPFs directly or through some companion package. You will have to use one of the previous options or simply rely on what other providers (major modes, LSP, …) give you.

;; Do M-x describe-package and then search for completion-preview to
;; read its commentary, or M-x find-library and then
;; completion-preview to go straight to its source code (where you can
;; read the commentary and browse through all the options).
(use-package completion-preview
  :ensure nil
  :config
  (global-completion-preview-mode 1))

My preferred setup for completion-preview-mode

And here is what I have for this minor mode. Again, it is only for your information.

(use-package completion-preview
  :ensure nil
  :demand t
  :bind
  ( :map completion-preview-active-mode-map
    ("M-n" . completion-preview-next-candidate)
    ("M-p" . completion-preview-prev-candidate)
    ("M-<return>" . completion-preview-insert)
    ;; This way you can combine it with another interface (like the
    ;; generic Completions buffer or `corfu-mode') to display more
    ;; candidates.
    ("<tab>" . completion-preview-complete))
  :config
  (setq completion-preview-exact-match-only nil)
  (setq completion-preview-commands '(self-insert-command
                                      insert-char
                                      analyze-text-conversion
                                      completion-preview-insert-word))
  (setq completion-preview-minimum-symbol-length 2)
  (setq completion-preview-idle-delay 0.3)
  (setq completion-preview-ignore-case t)
  (setq completion-preview-sort-function #'identity)

  (global-completion-preview-mode 1))
-1:-- Emacs: Completion in the buffer with completion-at-point-functions (Post Protesilaos)--L0--C0--2026-07-19T00:00:00.000Z

Andros Fenollosa: Your Denote notes on the iPhone

All my notes live in Denote, the fantastic Emacs package by Protesilaos Stavrou. The system is perfect while I am at the computer... but I wanted to read and edit my notes from my phone. I did not find anything that respected the Denote convention, so I built it myself: Dotdenote, an iOS app that reads, edits and creates notes following the Denote naming scheme over WebDAV. It is open source, you can browse the code at git.andros.dev/andros/dotdenote.

Dotdenote note list with search, showing Denote notes with their tags and dates

Dotdenote note detail rendering an Org note with headings, lists and a table

Dotdenote settings screen with the server URL, notes path, credentials and the test connection button

The app does not lock your notes anywhere: it talks to any WebDAV server, so they remain plain text files under your control. In this mini tutorial we will set up a WebDAV server with Docker, put an Nginx in front of it, and connect the app to your existing Denote notes. In less than 10 minutes you will be browsing your Zettelkasten from your phone.

What you need

  • A machine to host the server: a home server, a Raspberry Pi, a VPS, or even your laptop.
  • Docker with the Compose plugin installed. You don't need to use Docker, you can run the WebDAV server and Nginx directly on your machine, but Docker makes it easier to set up and maintain.
  • Your Denote notes folder (.org, .md or .txt files).
  • An iPhone with Dotdenote installed.

Step 1: Create the project structure

We will use hacdias/webdav, a small and solid WebDAV server, behind an Nginx reverse proxy. Both run as containers.

Create a folder for the stack and copy your notes inside data/notes/:

mkdir -p denote-server/data/notes
cd denote-server
cp -r ~/Documents/notes/* data/notes/

The notes subfolder is important: the app uses /notes/ as its default notes path, so this layout works out of the box.

Step 2: Configure the WebDAV server

Create config.yaml with the server settings and your credentials:

address: 0.0.0.0
port: 6065
directory: /data
permissions: CRUD
users:
  - username: denote
    password: change-me-please

A couple of notes:

  • directory is the key used since v5 of hacdias/webdav (older tutorials mention scope, which no longer works).
  • permissions: CRUD gives the user full read and write access, which is what the app needs to create and edit notes.

Step 3: Configure Nginx

Create nginx.conf. WebDAV uses HTTP methods and headers that a default proxy config does not forward, so we pass them explicitly:

server {
    listen 80;
    client_max_body_size 1g;

    location / {
        proxy_pass http://webdav:6065;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # WebDAV specific headers. Destination arrives as an absolute URL,
        # rewrite it to a path so the backend accepts it.
        set $destination $http_destination;
        if ($destination ~ ^https?://[^/]+(/.*)$) {
            set $destination $1;
        }
        proxy_set_header Destination $destination;
        proxy_set_header Depth $http_depth;
        proxy_set_header Overwrite $http_overwrite;
    }
}

Step 4: The Compose file

Create compose.yaml gluing everything together:

services:
  webdav:
    image: hacdias/webdav:latest
    restart: unless-stopped
    volumes:
      - ./config.yaml:/config.yaml:ro
      - ./data:/data

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - webdav

Your folder should now look like this:

denote-server/
├── compose.yaml
├── config.yaml
├── nginx.conf
└── data/
    └── notes/
        ├── 20240322T131856--my-note-title__emacs_notes.org
        └── ...

Start it:

docker compose up -d

Step 5: Test the server

The app checks the connection with a PROPFIND request, so let's do exactly the same with curl (replace the IP with the address of your server):

curl -u denote:change-me-please -X PROPFIND -H 'Depth: 1' http://192.168.1.50:8080/notes/

If you get an XML response listing your notes, the server is ready. If you get a 401, check the credentials in config.yaml; if you get a 404, check that your notes live inside data/notes/.

Step 6: Connect the app

Download Dotdenote from the App Store: https://apps.apple.com/us/app/dotdenote/id6773266542

Open the app and fill in the setup form:

Field Value
URL http://192.168.1.50:8080
NOTES PATH /notes/ (the default)
USERNAME denote
PASSWORD change-me-please

Tap [ test connection ]. When you see the check mark, save, and your Denote notes will appear in the list: you can search them, open them, edit them, and create new ones. Every note the app creates follows the Denote convention (IDENTIFIER--title__keywords.ext) with the proper front matter for Org, Markdown or plain text, so everything stays perfectly compatible with Emacs.

The password is stored in the iOS Keychain, and the notes never touch any third-party service: the app only talks to your server.

Optional: expose it to the internet

The setup above is fine inside your home network or over a VPN like WireGuard or Tailscale. If you want to reach your notes from anywhere, do not expose plain HTTP: put a domain in front of the server and add TLS. The quickest path is to run Nginx on the host (or use a proxy like Caddy or Traefik) with a Let's Encrypt certificate from Certbot, pointing to 127.0.0.1:8080. Then use https://notes.example.com as the URL in the app, everything else stays the same.

Enjoy it!


Will you buy me a coffee? This is how I keep writing without ads or paywalls. Sure, it's on me!

Send an email to comment+article-9abcdcfe@andros.dev to leave a comment. The subject will be ignored.

-1:-- Your Denote notes on the iPhone (Post Andros Fenollosa)--L0--C0--2026-07-18T15:27:18.000Z

Irreal: Embark

Some time ago, I installed Embark but I never got around to learning it and it has sat unused ever since. Then I saw this post by Bozhidar Batsov and decided to take another look. The problem with Embark is that it’s hard to understand and the documentation is long and complex. Too long, I discovered, to read and understand in one sitting.

I approached the problem by reading a section at a time and trying out examples in my Emacs. Even so, it still took some experimentation to completely figure things out. The process is ongoing but I’m making progress.

All of the above sounds pretty negative but I actually think Embark is a real win. Once you figure things out it’s really useful. For example, one of the things I’ve never been able to get working reliably is following a link in an arbitrary buffer to its target. Embark does that easily and if you use embark-dwim it’s a single keystroke. I use Hyper+. for embark-act and Hyper+, for embark-dwim. I have Hyper bound to the right ⌘ Cmd key so it’s very easy to invoke either of those commands.

Batsov is all in on Embark. He and I often share preferences and this is an example of that. The more I find out about Embark, the more I like it. The commands that work on a group of files can be difficult to grok but once you do they’re very powerful. I’ve resigned myself that learning Embark is an ongoing process but I think it’s worth it.

-1:-- Embark (Post Irreal)--L0--C0--2026-07-18T15:10:57.000Z

Sacha Chua: 6 août: Emacs Chat: Richard Bonichon

Nous allons parler en direct avec Richard Bonichon (Github) d'Emacs, de ses flux de travail, de sa configuration, et d'autres sujets en français. Je suis un peu intimidée, mais si vous posez vos questions dans le chat, je pense que nous pouvons survivre à mon faible niveau de français. =)

La diffusion en direct aura lieu le dans le fuseau horaire America/Toronto, soit 0730H EDT / 0630H CDT / 0530H MDT / 0430H PDT / 1130H UTC / 1330H CEST / 1430H EEST / 1700H IST / 1930H +08 / 2030H JST.

Si vous voulez parler d'Emacs avec Prot et moi en français, n'hésitez pas à m'envoyer un courriel !

View Org source for this post

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

-1:-- 6 août: Emacs Chat: Richard Bonichon (Post Sacha Chua)--L0--C0--2026-07-18T13:53:46.000Z

Sacha Chua: 20 août: Emacs Chat: Abdallah Maouche

Nous allons parler en direct avec Abdallah Maouche (heiwiper) (Gitlab, Github) d'Emacs, de ses flux de travail, de sa configuration, et d'autres sujets en français. Je suis un peu intimidée, mais si vous posez vos questions dans le chat, je pense que nous pouvons survivre à mon faible niveau de français. =)

La diffusion en direct aura lieu le dans le fuseau horaire America/Toronto, soit 0730H EDT / 0630H CDT / 0530H MDT / 0430H PDT / 1130H UTC / 1330H CEST / 1430H EEST / 1700H IST / 1930H +08 / 2030H JST

Si vous voulez parler d'Emacs avec Prot et moi en français, n'hésitez pas à m'envoyer un courriel !

View Org source for this post

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

-1:-- 20 août: Emacs Chat: Abdallah Maouche (Post Sacha Chua)--L0--C0--2026-07-18T13:53:00.000Z

Emacs Redux: which-key is Now Built-in

which-key is one of those packages I recommend to pretty much everyone - newcomers and grizzled Emacs veterans alike. You start a key sequence like C-x, pause for a moment, and it pops up a handy cheat sheet of every key that can follow. I’ve had it in my config for ages, and I suspect a lot of you have as well.

Here’s the good news - as of Emacs 30 you don’t need to install it anymore. which-key is now a built-in package, so there’s one less thing to fetch from MELPA when you set up Emacs on a fresh machine.

Enabling which-key

Being built-in doesn’t mean it’s enabled by default, though. You still have to turn it on explicitly:

(which-key-mode 1)

That’s the whole setup for most people. Now start any prefix key - C-x, C-c, C-h - wait a moment, and a popup appears at the bottom of the frame listing every binding under that prefix.

Here’s what I get when I press C-c p, the prefix for projectile’s command map:

which-key showing projectile's C-c p command map

Instead of straining to remember whether it’s C-c p f or C-c p s s, I just pause and Emacs reminds me. Projectile has a lot of commands, so which-key even paginates them - notice the 1 of 3 in the bottom corner.

Configuration

The defaults are sensible, but here are the knobs I reach for most often:

;; show the popup faster (the default is a full second)
(setq which-key-idle-delay 0.5)

;; where the popup shows up
;; options: side-window (default), minibuffer, frame, custom
(setq which-key-popup-type 'side-window)

;; and where that side window sits
;; options: bottom (default), top, left, right
(setq which-key-side-window-location 'bottom)

I like a slightly snappier delay, but otherwise the stock configuration serves me just fine.

which-key and Transient

People sometimes ask me how which-key relates to Transient - the menu library that powers Magit’s famous popups. The two look superficially similar (both drop a bunch of keys and descriptions at the bottom of your frame), but they solve rather different problems.

which-key is a passive, automatic reminder. It doesn’t really know anything about your commands - it simply reads whatever keymap you happen to be in and shows you what’s there. You get it for free, across every prefix in Emacs, with zero per-keymap work.

Transient is an interactive command builder. You design a transient deliberately, for a particular workflow, and unlike which-key it can hold state

  • toggle switches on and off, accumulate arguments - before finally running a command with all of that baggage attached.

Projectile happens to ship both, which makes for a nice side-by-side. The C-c p keymap you saw above is a plain keymap - which-key displays it and then gets out of the way. But Projectile 3.0 also added projectile-dispatch (bound to C-c p m), a Transient menu that mirrors the very same command map:

The projectile-dispatch Transient menu

Notice the Modifiers section at the top - those are switches you flip before picking a command. Want your next search to be case-sensitive, or your next file lookup to invalidate the cache first? Toggle -c or -i, then press the command key. That’s something a plain keymap (and therefore which-key) simply can’t do.

So the way I see it - reach for which-key as an always-on safety net for the thousands of bindings scattered across Emacs, and reach for Transient when you’re building a polished, self-contained command center for a specific task. They’re complementary, and there’s no reason not to run both. In fact, if you use Magit or Projectile (or a dozen other modern packages), you’re already using Transient whether you realized it or not.

But wait, what about C-h?

Long-time Emacs users might point out that you’ve always been able to press C-h after a prefix key to see the available bindings. That’s true! The difference is that which-key does it automatically, no extra keypress required - which makes it far more discoverable, especially for newcomers.

And the two don’t step on each other. C-h after a prefix still works and gives you the traditional describe-bindings buffer, which you can scroll and search to your heart’s content. Think of which-key as the quick glance and C-h as the full reference.

Closing Thoughts

I’ve been a happy which-key user for years, and having it in the box means one less package to install and keep updated. If you’ve somehow never tried it, do yourself a favor and flip it on - it’s the kind of small quality-of-life feature you stop noticing precisely because it’s always quietly helping you.

Do you keep which-key on all the time, or do you prefer to lean on C-h? And have you started building your own Transient menus yet? I’d love to hear about it in the comments.

That’s all I have for you today. Keep hacking (and let Emacs remember the keys for you)!

-1:-- which-key is Now Built-in (Post Emacs Redux)--L0--C0--2026-07-18T07:00:00.000Z

Meta Redux: Sayid 0.8

Sayid 0.8 is out! It’s the third release since I brought Sayid back from the dead a couple of weeks ago, and it has a clear theme: making the tool easy to pick up. The revival releases were mostly about the engine - bounding the recording, consolidating the API, getting the data out. This one is about the experience. If you’ve ever bounced off Sayid because you couldn’t figure out what to press, or what it was trying to tell you, 0.8 is for you.

It started with a bug report

Shortly after the revival post, someone reported that pressing c i in the workspace view - “inspect this captured value” - printed Def'd as $s/* and then… nothing. The fix turned out to be a one-liner: Sayid was calling a CIDER function whose signature changed years ago, and nobody had noticed since. Which tells you everything about how many people were actually using that command.1

The one-liner was easy, but the report got under my skin. If the inspector integration could sit broken for years, what else about Sayid was quietly hostile to anyone trying it for the first time? So I sat down and did a proper UX audit of the Emacs client, wrote down everything that made me wince, and 0.8 is the result.

Here’s the current state of affairs in one take - trace, run, explore:

The Sayid workflow - trace some namespaces, run your code, explore the recording

Catching up: 0.6 and 0.7

Before we get to the UX work, a quick recap of the two releases I never got around to announcing (the revival post covered things up to 0.5).

Sayid 0.6 rebuilt inner tracing - the mode that records every intermediate expression inside a function - on top of tools.analyzer.jvm. The old implementation re-read your source and rewrote raw forms, with special cases for individual macros; the new one works off the analyzed AST. That killed the long-standing bug where an inner-traced try/catch would swallow exceptions, along with the per-macro special-casing that made the old instrumenter so fragile.

Sayid 0.7 made the trace itself data. sayid.data/trace-data returns the recorded call tree as plain Clojure data with the live captured values, and tap-trace! sends it to tap>, so you can explore a recording in Portal or your data tool of choice. It also added sayid.golden - capture a run’s call tree as a baseline, then assert future runs still match it, which is a surprisingly pleasant way to pin down the behavior of gnarly legacy code before refactoring it.

The highlights

Now, the 0.8 goodies. The full list is in the changelog, but here’s what I’m most excited about:

  • There’s a proper entry point now: C-c s pops up a transient menu (sayid-menu) that groups the commands along Sayid’s core loop - trace something, run your code, explore the recording - and shows you how much is traced and recorded right now, or what’s missing (a REPL, the middleware) when you’re not connected. The menu uses the same key sequences as the classic prefix map, so your muscle memory keeps working, and sayid-use-menu brings the plain keymap back if popups aren’t your thing.

The Sayid menu

  • sayid-trace-fn (C-c s t t) is the new “just trace this” command. You no longer need to know what inner and outer traces are before you can trace your first function - the default does the right thing, and you can graduate to the fancier variants later.
  • Empty views teach instead of scolding. Opening the workspace before anything was recorded used to greet you with an error; now you get the buffer anyway, with a short walkthrough of how to get data into it. The first five minutes with Sayid should no longer require reading the manual.
  • The commands talk back like a human. Tracing a function now tells you what happened and what to do next (“Outer-traced acme.checkout/subtotal - run some code, then C-c s w shows what was recorded”), and trying to enable a trace that doesn’t exist tells you the actual problem instead of pretending everything went fine.
  • The workspace tree is now the one view to rule them all: on any call you can inspect a captured value in CIDER’s inspector (c i - yes, it works now), def it to a var for REPL poking (c d), pretty-print it (c p), or copy an expression that reproduces the call (c r). C-c s f - show the recorded calls of the form at point - renders there too.
  • A pile of small courtesies that add up: tracing a function no longer steals your window with a popup, g refreshes the Sayid buffers like every other Emacs buffer, and resetting the workspace asks before irreversibly dropping your traces and recording.
  • The client and the middleware now tolerate version skew - if your deps.edn pins an older Sayid jar than your Emacs package expects, things degrade politely instead of erroring in strange ways.
  • The Emacs client now requires CIDER 2.0, which let me drop a bunch of compatibility shims and lean on the new cider-tree-view throughout.
  • And the README finally shows the tool instead of just describing it - screenshots and the GIF above included. A picture of a call tree is worth a thousand words about one.

Upgrading notes

Nothing here should break a working setup, but two things are worth knowing. The Emacs package now requires CIDER 2.0 (released earlier this week), so they’ll need to be upgraded together. And C-c s now opens the menu instead of acting as a bare prefix - every old key sequence still works exactly as typed, but if you prefer the old silent prefix, set sayid-use-menu to nil. A few commands got more consistent names (sayid-trace-fn-outer and friends); the old names live on as obsolete aliases.

Onward

When I wrote the revival post I asked people to kick the tyres and tell me what feels rough. This release is what acting on that feedback looks like, and it’s exactly the kind of contribution I need more of - a two-line bug report turned into the biggest usability overhaul in the project’s history. So thanks to everyone who has been trying out the revived Sayid, and please keep the reports coming on the issue tracker.

[mx.cider/sayid "0.8.0"] is on Clojars, the Emacs package is on MELPA, and tracing your first function is now a single C-c s t t away. Give it a spin!

Keep hacking!

  1. In fairness, it also tells you something about me - the bug shipped with my own resurrection releases. Reviving a decade-old codebase means inheriting a decade of API drift, and some of it only surfaces when a real user presses a real key. 

-1:-- Sayid 0.8 (Post Meta Redux)--L0--C0--2026-07-18T07:00:00.000Z

Meta Redux: Projectile 3.2

Projectile 3.2 is out!1 That’s the third Projectile release this month, and by now you probably see the pattern - a whole lot of nothing for a couple of years, then everything at once. Where 3.0 was the big cleanup and 3.1 the pile of long-standing feature requests, 3.2 is a focused release with one clear theme: search and replace. Plus one bonus feature I’ve wanted for ages, but more on that below.

Replace, but with a preview

projectile-replace has always been one of those commands that technically worked, but that I never quite trusted. It walks the project file by file with query-replace, asking you about every match with no way to see the big picture, and once you start there’s no graceful way out. I’ve been promising myself a better story there for years (that’s #1924, if you’re keeping score at home).

3.2 finally delivers it. projectile-replace-review (s-p R) prompts for a search string and a replacement, gathers every match across the project into a *projectile-replace* buffer, and shows you a per-match preview of exactly what’s about to change. Every match can be toggled on or off, and ! applies just the enabled ones - in any order, whenever you’re ready. Here’s the whole flow, start to finish:

projectile-replace-review in action

The matches are gathered in Emacs Lisp rather than by shelling out to grep, so projectile-replace-regexp-review honours full Emacs regexp syntax (yes, including \_<foo\_> and capture groups in the replacement), and the preview reflects the exact text that will be edited - including unsaved changes in buffers you already have open. The apply step is equally paranoid: files are edited bottom-up so earlier edits can’t shift later matches, open buffers get a single undo step, and buffers modified since the scan are skipped rather than clobbered.

The results buffer is a little workbench of its own - you can toggle case sensitivity (c), flip between literal and regexp matching (x), keep or flush matches by line or by file (k/d/K/D), and export everything to a grep-mode buffer (e) if you’d rather finish the job with wgrep. And both reviewers scan the project asynchronously, so a big search no longer freezes Emacs - matches stream in as they’re found and you can bail out at any point.

Search gets the same treatment

Once the replace reviewer existed, the obvious next question was “can I have this without the replacing part?”. You can. projectile-search-review (s-p s R) collects every match for a term into a read-only *projectile-search* buffer, grouped by file, with the matched spans highlighted:

The projectile-search results buffer

It shares the replace reviewer’s navigation and filtering, and pressing r hands the current search over to the replace reviewer, carrying along the term and settings and prompting only for the replacement. So “look first, replace later” is now a single keystroke, not a re-do.

My favourite touch: when rg is installed, a literal search rides ripgrep and streams matches in near-instantly even on huge projects (customizable via projectile-search-use-ripgrep). The regexp variant and the replace reviewer always use the portable Emacs Lisp scan, so the preview you act on is always the real thing.

A tab per project

The bonus feature: projectile-session-mode, a global minor mode that gives each project its own tab-bar tab. Switch to a project and Projectile either selects its existing tab (with the window layout you left there) or opens a fresh one named after the project. Sessions can also persist across restarts - s-p w s saves a project’s layout and buffers, switching to the project can restore it automatically, and projectile-session-restore-all brings your whole working set back after a restart, each project in its own tab.

I’ve long envied the “workspace per project” workflows people build with tab-bar and a handful of hooks, and now Projectile just does it out of the box. It’s opt-in, so nothing changes unless you enable the mode.

Odds and ends

A few smaller things that shipped alongside the headliners:

  • The reviewable search/replace learned whole-word matching - toggle it with w in the results buffer, or default it with projectile-search-whole-word.
  • projectile-dispatch picked up matching switches - --word and --case-sensitive seed the reviewable search/replace, the same way --regexp already did for the other search commands.
  • All the search prompts now share one consistent format that shows the tool that will actually run and the symbol-at-point default up front. No more guessing whether you’re about to invoke grep or ripgrep.
  • Projectile’s prompts now advertise the right completion metadata category per command, so marginalia annotates candidates properly (buffers get buffer annotations, projects get file ones) and embark can act on them. With vertico and marginalia the built-in commands look downright fancy now:

projectile-find-file with vertico and marginalia

  • Function-valued lifecycle commands (say, a CMake preset picker) are re-invoked on every run instead of having their first result frozen in the command cache - an old gotcha that bit CMake users in particular.

Wrapping up

No breaking changes this time, and nothing to migrate - the full list of changes is in the changelog, as always.

More importantly, 3.2 completes - for now - the arc I embarked on with 3.0. Looking back at the whole 3.x series, the mission boils down to three things:

  • make Projectile’s internals faster, simpler and more robust
  • modernize the UX (async everything, the dispatch menu, and now reviewable search and replace) and give people more reasons to prefer Projectile
  • replace hardcoded behavior with mechanisms - backend registries, file kinds, named tasks, session serializers - that you can bend to your own workflow in a few lines of config

All three boxes feel properly ticked at this point, so the summer release burst is probably winding down.2 Projectile is in the best shape it’s been in years, and I’m having more fun working on it than I’ve had in a long time. Huge thanks to everyone who reported issues and tested the new reviewers while they were taking shape!

Keep hacking!

  1. Technically it’s been out since July 12th, and there’s already a 3.2.1 patch release, because 3.2.0 shipped with a crash in projectile-search. You polish a release for a week and then break the one command everyone uses. Classic. 

  2. Famous last words, again. I said something similar after 3.1. 

-1:-- Projectile 3.2 (Post Meta Redux)--L0--C0--2026-07-18T06:15:00.000Z

Charlie Holland: The Search for Knowledge: Emacs Carnival August 2026

1. About

august-emacs-carnival-banner.jpg

This month I'd like to host the Emacs Carnival! I've been an enthusiastic participant for a while now. I wrote entries for May, June, and July (that last one hosted by Andy over at Plain DrOps). Now it's my turn to hold the torch.

Reading other people's carnival posts has consistently been the highlight of my month, and I find I learn a lot through this structured prompting for insight from other Emacs users.

This month's topic is "The Search for Knowledge". This combines a couple of concepts that I think are near and dear to the hearts of many Emacsapiens, and have seen a recent surge in attention:

  1. Information Retrieval, or more abstractly "Search"
  2. Knowledge Management

I'm hoping to assay the community for solutions to these complex endeavors. Emacs, in my opinion, is the world's most capable text processing environment, and so it only makes sense that it would have best-in-class solutions and workflows for managing complex information and synthesizing precious knowledge from it.

There are so many solutions to these problems, and we see a veritable embarrassment of riches in Emacs along our intrepid search for knowledge. I'm hoping this carnival can crystallize some of the best solutions and workflows.

Some topic ideas, but feel free to bring your own!

  1. Knowledge graphs: How much do you use the knowledge graph? Do you use the visualization or some other utility to traverse connections between the notes in your notes vault?
  2. The shape of your knowledge graph: Have you learned anything from the shape and structure of your knowledge graph? Are there salient shapes and neighborhoods in that graph that enlighten your thought process or internalized knowledge? Maybe this isn't the case, and that's also a worthwhile observation to talk about!
  3. The knowledge graph anti-pitch: I know a lot of people who don't use knowledge graphs at all, despite trying. They dump their notes into a directory and simply grep for information when they want it. Are you one of these people? Do you still get knowledge graph utility without relying on a knowledge graph solution?
  4. Emacs packages for knowledge management: There are a huge number of these inside and outside of Emacs. Which ones do you use? Are some better than others? Why? Are there models for knowledge management that work better for you than others?
  5. Information structure: How do you store your notes and information? Are you more of a few-large-files or many-tiny-files person?
  6. Information retrieval tools: How much mileage do you get out of a simple grep? Do you ever reach for something else? What is that something else?
  7. Information retrieval sources: Where does all your text come from? Is it just notes? Do you manage information coming from other file types like images, PDFs, SVGs, etc.?
  8. What's next in the knowledge management space: Do you think there are unsolved or soon-to-be-solved problems in this space? What are they? What do you wish you had now in your "search for knowledge".

2. What is a carnival?

See the wikipage on the topic here: Emacs Carnival.

This is our first Emacs Carnival, so you may not know the format. A blog carnival is a fun way to tie together a community with shared writing prompts, and marvel at all the creative interpretations of the topic of the month. I’ve provided a couple of interpretations above, but you may think of something else entirely. That’s amazing, roll with it, that’s what makes this fun!

— Christian Tietze

I want to emphasize that a carnival is supposed to be fun. And I'd also like to say that if you've ever considered writing a post but haven't found the time or space, the Emacs carnival is the best way to give it a crack!

3. How to take part

Getting involved is easy:

  1. Write something on "The Search for Knowledge" any time during August 2026. Any format is welcome. A long essay, a short note, a screencast, or a thread on the fediverse. Pick one of the prompts above, or take the theme somewhere I haven't thought of.
  2. Send me the link by 31 August 2026. Email me at mister.chiply@gmail.com, or post it on the fediverse with #EmacsCarnival so the wider community sees it too. You can also DM me on reddit at u/misterchiply, I hang out there a lot!
  3. Watch this space. I'll add every entry to the list below as it arrives, and once the month closes I'll publish a round-up that gathers them all in one place.

Don't overthink it! Even a few honest paragraphs about how you find things in your notes make a great entry.

4. Submissions

I'll keep this list updated throughout August as entries come in. Check back through the month, and look out for the round-up once the carnival closes.

No submissions yet — yours could be the first!

-1:-- The Search for Knowledge: Emacs Carnival August 2026 (Post Charlie Holland)--L0--C0--2026-07-17T15:42:51.000Z

Emacs Redux: Tree-sitter Modes Still Need a Syntax Table

When you write a tree-sitter major mode, it’s tempting to think the parser handles everything. It doesn’t. Tree-sitter drives font-lock, indentation, and structural navigation, but a whole layer of everyday Emacs behavior still runs on the humble syntax table: forward-sexp and friends, electric-pair-mode, delete-pair, forward-word, and anything built on syntax-ppss. (I wrote about that structural layer in Essential Structured Navigation and Editing Commands.)

I got a sharp reminder of this while building neocaml, my tree-sitter mode for OCaml. The grammar parsed everything beautifully, yet delete-pair and sexp motion kept misbehaving around a couple of OCaml constructs. The fix turned out to be a classic tool that long predates tree-sitter: syntax-propertize-function. Here’s the story - it tripped me up for a good while, so maybe it’ll spare you some head-scratching.

The problem: characters a static syntax table can’t classify

A syntax table assigns each character a single class: this one opens a string, that one is a comment starter, this is a word constituent. That works until a character means different things in different places. OCaml has two such troublemakers.

Character literals. 'a' is a character, and the single quotes delimit it. But the same ' also starts a type variable ('a in 'a list), and can appear inside identifiers as a prime (x'). So you can’t just declare ' a string delimiter. And if you leave it as a symbol constituent (the usual choice), a character literal whose contents happen to be a delimiter wreaks havoc:

let x = '"'   (* the " opens a string, as far as the syntax table knows *)
let y = '('   (* the ( is an unbalanced open paren *)

Quoted string literals. OCaml’s {|...|} (and tagged {foo|...|foo}) raw strings can contain anything - including ", (*, and friends - which the syntax table reads as real string/comment delimiters:

let z = {|a "b" (* not a comment *)|}

You can see the damage with syntax-ppss. With nothing but a static table, point at the end of let x = '"' reports that you’re inside a string:

(nth 3 (syntax-ppss))  ;; => 34   (i.e. inside a string opened by ")

And every command that consults the syntax table inherits the confusion: C-M-f walks off into nonsense, delete-pair grabs the wrong delimiter, electric-pair-mode autopairs incorrectly.

Note that font-lock looks fine here - tree-sitter fontifies these constructs correctly from the parse tree. That’s exactly what makes the bug sneaky: the buffer looks right, but the syntactic layer underneath is lying.

The fix: context-sensitive syntax with syntax-propertize

The escape hatch is syntax-propertize-function: a buffer-local function that runs lazily over regions of the buffer and applies syntax-table text properties to override the static table where context demands it. Because the properties are attached to specific positions, ' can be a string delimiter in 'a' and an ordinary symbol character in 'a list - in the same buffer.

The usual way to write one is with syntax-propertize-rules, which maps regexps to the syntax classes to apply to their capture groups. Here’s the heart of neocaml’s:

(defun neocaml--syntax-propertize (start end)
  (goto-char start)
  (funcall
   (syntax-propertize-rules
    ;; Character literals: 'a', '\n', '"', '(', ...
    ;; Mark both quotes as string delimiters so the contents are inert.
    ;; A closing quote is required, so type variables ('a) are untouched.
    ("\\_<\\('\\)\\(?:[^'\\\n]\\|\\\\.[^\\'\n \")]*\\)\\('\\)"
     (1 "\"") (2 "\""))
    ;; Quoted strings {tag|...|tag}: fence both ends so the body is inert.
    ("\\({\\)\\([[:lower:]_]*\\)|"
     (1 (let* ((tag (match-string 2))
               (close (save-excursion
                        (when (search-forward (concat "|" tag "}") end t)
                          (point)))))
          (when close
            (put-text-property (1- close) close
                               'syntax-table (string-to-syntax "|")))
          (string-to-syntax "|")))))
   (point) end))

and you install it in the mode body:

(setq-local syntax-propertize-function #'neocaml--syntax-propertize)

Two kinds of rules are at work:

  • The character literal rule matches a complete '...' and gives both quotes string-quote syntax ("). The contents then sit inside a string as far as the scanner is concerned, so an inner " or ( is inert. Crucially, the regexp requires a closing quote, so a bare 'a type variable never matches and keeps its symbol syntax.
  • The quoted string rule matches the opening {tag|, searches for the matching |tag}, and marks both the opening and closing delimiters with generic string fence syntax (|). Everything between two fences is a string, so embedded quotes and comment starters are neutralized.

With that in place, syntax-ppss tells the truth again, and C-M-f, delete-pair, and electric-pair-mode all behave - including for the delete-pair corner case I wrote about earlier.

Lessons for mode writers

A few things worth keeping in mind, whether or not you touch OCaml:

  • Tree-sitter and the syntax table are different layers. The parser handles font-lock and structural queries, while the syntax table handles character-level motion and pairing. A great grammar doesn’t save you from getting the syntax table right. (See also Customizing Font-Lock in the Age of Tree-sitter.)
  • Reach for syntax-propertize whenever a character’s role depends on context - raw strings, here-docs, regex literals, character literals, JSX, anything a single syntax class can’t capture. It’s exactly what rust-ts-mode and c-ts-mode reach for, too.
  • The syntax-table text properties don’t affect tree-sitter font-lock. They live on a separate channel, so you can fix the syntactic layer without disturbing your carefully tuned highlighting.
  • Don’t call syntax-ppss from inside your syntax-propertize-function. It’s re-entrant (syntax-propertize is itself driven by syntax-ppss) and a fragile source of subtle bugs. Match constructs whole from their opening delimiter instead, as the quoted-string rule above does, rather than asking “am I currently inside a string?”.
  • Know your string classes. Class " (string quote) is delimited by the same character, while class | (generic string fence) pairs up independently of the specific character. The latter is handy for multi-character or asymmetric delimiters like {tag||tag}.

None of this is new - syntax-propertize has been the right answer since Emacs 24. But it’s easy to forget it exists when you’re deep in tree-sitter land, and the symptoms (movement and pairing going subtly wrong while the colors look perfect) are puzzling until you remember which layer owns what.

Have you run into this while writing your own tree-sitter modes? I’d love to hear how you tamed the syntax table in the comments.

That’s all I have for you today. Keep those parens balanced!

-1:-- Tree-sitter Modes Still Need a Syntax Table (Post Emacs Redux)--L0--C0--2026-07-17T09:15:00.000Z

Emacs Redux: Inline Completion with completion-preview-mode

As part of the ongoing overhaul of my Emacs setup I’ve been trying to make the most of the built-in functionality that recent Emacs releases keep quietly shipping. My in-buffer completion setup is based on corfu and cape these days, but it turns out I had overlooked a nice Emacs 30 addition in the same area - completion-preview-mode. It gives you inline completion suggestions - the “ghost text” UI that GitHub Copilot and friends made famous1 - except here it’s powered by plain old Emacs completions.

What It Does

As you type, completion-preview-mode shows a grayed-out preview of the top completion candidate right after point - inline, in the buffer itself. No popup, no minibuffer, no fuss. If the suggestion is what you want, press TAB to accept it. If not, just keep typing and the preview updates (or goes away).

Ghost text suggesting the rest of a symbol as you type

The part I like best - the candidates come from completion-at-point-functions, which means the preview is fed by the exact same backends as corfu and company. If you’ve already set up cape-dabbrev, cape-file or eglot, all of them power the ghost text automatically. Batteries very much included.

Enabling It

You can try it out in the current buffer with M-x completion-preview-mode. If you like what you see, you can enable it everywhere:

(global-completion-preview-mode 1)

Here’s the relevant bit of my config:

(use-package completion-preview
  :ensure nil ; built-in
  :config
  ;; cycle through the other candidates with M-n/M-p (those two
  ;; commands have no default bindings)
  (define-key completion-preview-active-mode-map (kbd "M-n") #'completion-preview-next-candidate)
  (define-key completion-preview-active-mode-map (kbd "M-p") #'completion-preview-prev-candidate)
  (global-completion-preview-mode +1))

Essential Keybindings

While a preview is visible you can make use of the following keybindings:

  • TAB (completion-preview-insert) - accept the suggested completion
  • M-i (completion-preview-complete) - insert only the longest common prefix of all the candidates (the preview underlines it, so you know in advance what you’ll get) and let you keep typing from there
  • M-n / M-p (completion-preview-next-candidate / completion-preview-prev-candidate) - cycle through the other candidates; those are not bound by default, but the docs themselves suggest M-n and M-p (see my config above)

Here’s M-i in action. I had typed my-pro, and pressing M-i filled in ject-find-, stopping exactly where the two candidates (my-project-find-file and my-project-find-dir) diverge:

M-i completes up to the longest common prefix of the candidates

And when you cycle with M-n, Emacs even tells you where you are in the candidate list in the echo area:

Cycling through the completion candidates with M-n

There’s basic mouse support as well - clicking the ghost text with mouse-1 inserts it, and scrolling the mouse wheel over it cycles through the candidates. (I doubt I’ll ever use that, but it’s kind of cute)

Fine-tuning

A few user options to adjust the behavior:2

;; show the preview only after typing at least 3 characters (the default)
(setopt completion-preview-minimum-symbol-length 3)

;; wait a bit before showing the preview (by default it shows up instantly)
(setopt completion-preview-idle-delay 0.2)

;; show a preview only when there's exactly one candidate
(setopt completion-preview-exact-match-only t)

There’s also completion-preview-commands - the list of commands after which the preview appears (things like self-insert-command). You’ll rarely need to touch it, but it’s good to know it’s there if some command you use doesn’t trigger the preview.

Do You Still Need Corfu?

When I first read about completion-preview-mode I assumed it was competing with corfu and company, but I’ve come to think that’s the wrong way to look at it. They draw candidates from the same source and simply present them differently - corfu gives you the full candidate list with annotations and documentation, while the preview gives you the single most likely candidate with zero visual ceremony. These days I run both: the ghost text handles the “obviously I meant that symbol” cases with a single TAB, and corfu’s popup kicks in when I actually need to browse.

Admittedly, two completion UIs at once won’t be everyone’s cup of tea. If you find it too busy, a nice middle ground is to enable completion-preview-mode only where a popup feels out of place - e.g. in eshell-mode or comint-mode buffers.

Closing Thoughts

Emacs 30 keeps chipping away at my list of third-party packages - which-key and EditorConfig support are now built-in, and completion-preview-mode covers a niche I didn’t even know I wanted covered. Not bad for a “boring” stable release!

Have you tried completion-preview-mode already? Are you a ghost text person or a popup person? (or both, like me) I’d love to hear your thoughts in the comments!

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

  1. Unlike the AI assistants, the suggestions here come straight from your buffers and your completion-at-point-functions. No subscription needed and no hallucinations included. 

  2. If you’re wondering what’s this setopt thing - check out this article

-1:-- Inline Completion with completion-preview-mode (Post Emacs Redux)--L0--C0--2026-07-17T08:45:00.000Z

Meta Redux: Stepping Through Macros in CIDER

This is another installment in the series of articles about the notable changes in CIDER 2.0. Today’s topic is one of those “ambitious ideas that lay dormant for ages” I keep mentioning: proper interactive macro stepping.

The Dream

CIDER has had macroexpansion support practically forever - C-c C-m expands the form before point into a dedicated buffer, a feature we inherited spiritually from SLIME. It works, but it has always felt a bit… detached. The expansion lives in another buffer, divorced from the code you’re reading, and for deeply nested macros you end up bouncing between buffers trying to keep your bearings.

Emacs Lisp hackers have long had something nicer: macrostep, a brilliant little package that expands macros in place - right where they sit in your code - one step at a time, and collapses them back when you’re done. I’ve wanted a Clojure version of this for years. CIDER 2.0 finally ships one.

Standing on shoulders, not on top of them

I’m hardly the first person to want this. The idea of bridging CIDER and macrostep goes back to at least 2016, where a proof of concept wired up macrostep’s extension hooks to CIDER by injecting a couple of helper functions into your REPL and shuttling forms back and forth as strings. Later, macrostep-geiser - a Scheme-oriented macrostep backend - grew CIDER support as well, and SLIME itself ships a slime-macrostep contrib built on the same extension API. So the hooks were there, the hacks existed, and I could have blessed one of them and called it a day.

I opted for a clean, from-scratch implementation in CIDER instead - mostly to provide the best possible experience for Clojure programmers, without the compromises the bridges had to make. macrostep’s extension API was designed around Emacs Lisp’s happy circumstances, and Clojure violates most of them:

  • macrostep-expand-1-function must be synchronous. That’s fine when expansion is an elisp function call; it’s less fine when it’s a network round-trip to an nREPL server. (CIDER has been busy removing exactly this kind of blocking call lately - asynchronous eldoc being the poster child.)
  • The API traffics in forms - elisp data structures. But Clojure code isn’t elisp data: keywords, reader tags, #() lambdas and namespaced maps have no faithful elisp representation, so every bridge ends up round-tripping code through strings and hoping the quoting survives. The 2016 proof of concept literally did (format "(expand-once '%s)" form), which should make anyone a little nervous.
  • macrostep-print-function expects the client to print and fontify the expansion. In CIDER the server does the printing - that’s how we get namespace tidying (when instead of clojure.core/when), print-option handling and metadata display for free. A client-side printer would have to reimplement all of that, badly.

There’s also a less technical reason: lately I’ve grown rather averse to adding third-party dependencies to CIDER. A dependency is a bet on someone else’s continued enthusiasm, and such bets sometimes go bad - tellingly, macrostep itself spent a stretch unmaintained and now lives in a GitHub org literally named “emacsorphanage” (it has since found a new maintainer, but the point stands). CIDER is 14 years old and intends to stick around; over that kind of horizon, owning ~600 lines of overlay code is cheaper than adopting someone else’s semi-abandoned package. So we kept macrostep’s brilliant UX ideas and its familiar keybindings, and left the coupling behind.

cider-macrostep

The entry point is cider-macrostep-expand (C-c M-m e). Put point after a macro form, invoke it, and the form is replaced inline with its one-step expansion, highlighted so you can tell what’s expansion and what’s your code:

(when-let [x (fetch-thing)]
  (process x))

;; C-c M-m e =>

(let [temp__5825__auto__ (fetch-thing)]
  (when temp__5825__auto__
    (let [x temp__5825__auto__]
      (process x))))

From there you’re in cider-macrostep-mode, where the further-expandable sub-forms are underlined, n/p hop between them, e (or RET) steps into one, and c/q collapse one level or everything back to the original code. Your buffer is never actually modified in a way that sticks - collapse everything and it’s exactly as it was.

Here’s the whole flow in motion - expand, step into a nested macro, collapse back to the original code:

cider-macrostep stepping through when-let and collapsing back

A couple of touches I’m particularly fond of:

  • Every distinct gensym in the expansion gets its own color, so you can finally track where that temp__5825__auto__ flows through the expanded code. Once you’ve seen a for expansion with colorized gensyms, you won’t want to go back:

cider-macrostep expanding for, with colorized gensyms

  • E (cider-macrostep-expand-all) fully expands the form in one step, for when you don’t care about the journey.
  • b (cider-macrostep-expand-in-buffer) runs the same stepping session in a dedicated popup, leaving the source buffer untouched - handy when you’re in someone else’s code and feel uneasy about inline rewrites, however temporary.

The classic buffer got some love too

The traditional macroexpansion buffer wasn’t neglected either: it grew a header line showing the active expander and display options, n and t cycle namespace display and metadata in place, g re-expands with the latest macro definition, and freshly expanded forms pulse briefly so your eye lands in the right place.

The expansion commands also got more talkative: pointing them at an unresolved symbol now tells you whether the namespace simply isn’t loaded yet (evaluate the buffer!) or you’ve got a typo, instead of silently doing nothing.1

Why bother?

Macros are the part of Clojure people are most likely to describe as “magic”, and the standard advice - “just macroexpand it” - has always carried a hidden tax: the expansion of any non-trivial macro is a wall of gensyms and nested let*s that’s genuinely hard to read cold. Stepping through the expansion one level at a time, in place, with the gensyms color-coded, turns that wall into something you can actually follow. I did not expect macro debugging to become fun, and yet here we are.

All the details are in the macroexpansion docs. Give it a try the next time a macro surprises you - and keep hacking!

  1. Amusingly, we initially overdid these diagnostics - the guard refused to expand let and fn (which are macros wearing special-form badges) and broke a beloved trick of using macroexpansion to normalize reader syntax like ::auto/keywords. See #4111 - fixed right after 2.0. Even diagnostics need diagnostics. 

-1:-- Stepping Through Macros in CIDER (Post Meta Redux)--L0--C0--2026-07-17T05:15:00.000Z

Meta Redux: clj-refactor.el 4.0

Hot on the heels of CIDER 2.0, clj-refactor.el 4.0 is out! It’s the first major release of the project in almost five years, and this time around the version bump is not just ceremonial - 4.0 is the biggest batch of user-facing improvements clj-refactor has seen in ages, plus a healthy dose of long-overdue spring cleaning.

Unfinished business

When I announced clj-refactor.el 3.0 back in 2021, I opened with a confession - we had failed to deliver the main objective of that release, namely restoring the dependency hot-loading that JDK 9’s module system broke. Well, better late than never: cljr-hotload-dependency is back in 4.0, reimplemented on top of clojure.tools.deps, and it even accepts deps.edn-style coordinate maps alongside the classic Leiningen vectors.1

I’ll also be honest about the bigger picture, as I was back then: clj-refactor spent most of the intervening years in maintenance mode, while clojure-lsp became the default refactoring engine for much of the community. That hasn’t discouraged me, though - it clarified what clj-refactor should be. The project now has a proper roadmap that spells out where things are headed (spoiler: a static analysis index and closer ties with CIDER), and 4.0 is the first big step in that direction.

The highlights

The changelog for 4.0 has over 40 entries, but here’s what I’m most excited about:

  • There’s a new clj-refactor-menu - a transient menu of every command, grouped by category (bound to hh under your prefix, e.g. C-c C-m hh). It mirrors the classic two-letter mnemonics, so it doubles as a way to finally learn them. It replaces the old hydra-powered menus, which allowed us to drop the hydra dependency.
  • Project-wide refactorings are no longer a leap of faith. cljr-rename-symbol, cljr-change-function-signature and cljr-inline-symbol now show you their edits as a diff and touch nothing until you confirm, and the new cljr-undo-last-refactoring (ur) reverts the last applied refactoring across every affected file in one step.
  • The heavyweight commands went asynchronous. Renames, signature changes and file renames used to freeze Emacs while refactor-nrepl analyzed the project - now they return immediately, show a spinner in the mode-line, and finish when the analysis does. Cold-cache runs on big projects are dramatically more pleasant.
  • cljr-change-function-signature grew up: it can add and remove parameters now (not just reorder and rename them), it understands multi-arity functions, and a long-standing parsing bug that broke it against recent CIDER releases is fixed.
  • cljr-slash (the magic requires feature) learned a neat trick: entries in cljr-magic-require-namespaces can carry an :artifact coordinate, and when the namespace isn’t on your classpath, typing json/ will offer to add the library to your project and hotload it on the spot.
  • A bunch of commands now work without a running REPL. cljr-slash and cljr-add-missing-libspec fall back to the static alias table, cljr-clean-ns falls back to sorting the ns form, and cljr-remove-let and the #(...) -> (fn ...) promotion are handled purely on the Emacs side. Refactoring your requires before the REPL is up is no longer a paper cut.
  • Everything got snappier. Artifact lists and libspec suggestions are cached client-side (the latter matters because cljr-slash fires on every / keystroke), and the startup cache warming now actually populates the cache the interactive commands read from.
  • clj-refactor now talks to refactor-nrepl using namespaced op names (refactor/clean-ns and friends), with a graceful fallback for older middleware versions. The companion refactor-nrepl 3.14.0 release also hardened the AST analysis and made find-usages noticeably faster on projects with many macros.
  • The dependency diet continued: multiple-cursors, hydra and inflections are gone, along with a pile of aliases that had been deprecated since 2.3.0. clj-refactor is a much leaner package than it used to be.
  • And a long tail of quality-of-life fixes - cljr-describe-refactoring now opens the wiki page in your browser instead of scraping HTML, the dependency commands give clear errors instead of raw search failures, and clojure-ts-mode users get a working offline ns sort and proper setup docs.

Upgrading

4.0 is a major release and it does come with breaking changes: the minimum requirements are now Emacs 28.1 and CIDER 2.0, the long-deprecated 2.3.0-era command aliases are gone (use the clojure-* equivalents that live in clojure-mode these days), and the legacy namespace-aliases code path in cljr-slash was removed along with its three configuration options. On the middleware side you don’t need to do anything if you use cider-jack-in - refactor-nrepl 3.14.0 gets injected automatically, and older versions keep working thanks to the op-name fallback. Most setups should upgrade without touching a thing, but do skim the changelog if you have a heavily customized configuration.

Five years later

Reading the 3.0 announcement again, I wrote back then that we wanted to show “we haven’t reached the limit of what one can do with a purely (mostly?) REPL-powered approach to refactoring”. I still believe that, and 4.0 is hopefully some evidence. The roadmap has the rest of the plan - including making the analysis fast and robust enough to erase the reasons people reach for other tools - and with CIDER 2.0 out the door I finally have some cycles to spend here.

Huge thanks to everyone who contributed to clj-refactor and refactor-nrepl over the years, to Clojurists Together for supporting the broader CIDER ecosystem, and to everyone who kept filing issues during the quiet years - they shaped most of what shipped in this release.

In the REPL we trust!

  1. Only five years late. In my defense, nobody said when it would be restored. 

-1:-- clj-refactor.el 4.0 (Post Meta Redux)--L0--C0--2026-07-16T15:57:00.000Z

Mike Hostetler: Why Org's agenda ignores your start day

I feel like a bad Emacs user because, while I've been using it for a long time, it's only been in the past few months that I have embraced Orgmode as my task manager. Sometime I will probably write about my journey to this point, but for now I want to talk about a small annoyance I had in org-agenda.

I use org-agenda many times a day. I used to use the Week view (C-c a a) because it gives me an overview of what I finished and what is coming up. The problem is that a week is a fixed box. On Monday I saw a full week ahead of me. By Friday that list was mostly useless because all I saw were the tasks I'd finished earlier in the week and the few I had left, with next week's work still invisible – it won't show up until next week.

But that's not how I work. By Thursday I don't generally care what I finished on Monday, but I'd love a peek at what's coming. What I wanted wasn't a calendar week at all – I wanted a window that follows me, always showing a little behind and a little ahead, with today sitting near the middle.

In typical Emacs fashion, this was easy to do – it was three lines:

(setq org-agenda-span 6
      org-agenda-start-day "-2d"
      org-agenda-start-on-weekday nil)

The first two options make a lot of sense:

  • org-agenda-span 6: show six days instead of a rigid calendar week.
  • org-agenda-start-day "-2d": start two days before today. Org accepts relative date strings like -2d or +1w here, which makes this easy to tweak.

The third line is seemingly innocent but actually it's the one that matters the most. If you set only the first two, nothing changes. You'll tweak start-day, reload the agenda, and watch it snap right back to Monday, and you'll wonder if you fat-fingered a variable name.

You see, by default org-agenda-start-on-weekday is set to a weekday, and when it's set, Org rounds your start day back to that week boundary – silently overriding start-day. Setting it to nil tells Org to stop snapping to the week and honor the start day you actually asked for. Of course, its docstring explains it if you already know to go looking, but nothing points you there when your config silently fails.

Once I had that, I went looking for how to zoom in and out from my rolling agenda and found the view shortcuts:

  • d - day view

  • w - week view (so 7 full days)

  • vt - fortnight (14 days)

  • vm - month

  • y - year

    To be more specific, v gives you a menu of options to choose from – everything that I listed here and more.

    None of this is strange – it's three variables that ship with Org. But org-agenda-start-on-weekday is a good reminder that in Emacs, the config item you want usually already exists, and so your reasoning should be less of "there's no setting for that" and more about "what setting am I missing?".

-1:-- Why Org's agenda ignores your start day (Post Mike Hostetler)--L0--C0--2026-07-16T05:00:00.000Z

Protesilaos: Emacs: ‘doric-lilac’ and ‘doric-borage’ coming to the ‘doric-themes’

I have been working on two new themes for my minimalist doric-themes package. The original plan was to publish them together as part of the version 1.2.0 from a few days ago (the version that also brought doric-tiger and doric-lion), but then I decided to refine them further.

doric-lilac is a light theme, while doric-borage is dark. I am still testing them, though in the screenshots below you can get a first look.

Borage, by the way, occurs naturally in the mountains and there even is some in my land: all sorts of bees love it. I have seen lilac around, so I will need to transplant some of it here.

doric-lilac

doric-lilac theme sample

doric-borage

doric-borage theme sample

Part of development

The new themes will be available in version 1.3.0. They already have the character I want from them, though I may still tweak some colour values.

Sources

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

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

-1:-- Emacs: ‘doric-lilac’ and ‘doric-borage’ coming to the ‘doric-themes’ (Post Protesilaos)--L0--C0--2026-07-16T00:00:00.000Z

Vadzim Shender: Making backspace in vertico behave like ido

My Emacs file-finding habits go back a long way. I lived with vanilla find-file until I discovered ido-mode, and that was a big step up in UX. Later I moved most completion to helm — but C-x C-f stayed on ido the whole time: when helm-mode takes over the standard completion commands, helm-completing-read-handlers-alist lets you add per-command exceptions, and mine said (find-file . ido). Last December I finally rewrote my config around the modern stack — vertico, orderless, consult — and the vertico wiki even has a recipe for making it behave more like ido. Most of my muscle memory survived the move.

Except backspace.

Here is the difference. In ido-find-file, M-p/M-n move you between the directories you previously opened files in: you land in a directory, and from there each backspace takes you one level up. It is navigation, not text editing. Vertico’s M-p instead recalls the full path of a previously opened file, inserted into the minibuffer as ordinary editable text — which I actually like more: the file I want is often one I already opened. And when it is not, a recalled path is still my starting point. In a project I know well, project-find-file is only half of how I move around; just as often I navigate relative to a file I touched recently — a habit formed years before project.el or projectile existed: recall it, delete its name, go a level or two up or into a sibling directory. Which means pressing backspace — and this is where it breaks. Ordinary text gets ordinary editing: backspace deletes one character, except right after a slash, where vertico-directory-delete-char removes a whole path component. So I hold backspace to erase the long file name; it eats the name character by character — and the moment it crosses the slash, the still-held key starts deleting whole directories. Release a beat too late and half the path is gone.

The fix is a small replacement for vertico-directory-delete-char: when the point is at the end of the minibuffer and the input as a whole is an existing file path, treat backspace as “delete the last component” — exactly what my fingers expect after ido. This assumes the vertico-directory extension (shipped with vertico) is loaded, as in the wiki recipe above.

(defun my/-vertico-directory-delete-char (n)
 "Delete N directories or characters before point.
Like `vertico-directory-delete-char', but when point is at the end
of the minibuffer and the input is an existing file path, delete the
entire path component instead of a single character."
 (interactive "p")
 (cond
 ;; Active region: let `delete-backward-char' handle it.
 ((and (use-region-p) delete-active-region (= n 1))
 (with-no-warnings (delete-backward-char n)))
 ;; At a directory boundary, try to go up a directory. Also at end of
 ;; input, if the whole path is an existing local file, treat DEL as
 ;; directory-up (delete the last path component). Skip remote paths to
 ;; avoid blocking on network round-trips.
 ((and (or (eq (char-before) ?/)
 (and (eobp)
 (let ((mb (minibuffer-contents-no-properties)))
 (and (not (file-remote-p mb))
 (file-exists-p mb)))))
 (vertico-directory-up n)))
 ;; Otherwise, delete a character.
 (t
 (with-no-warnings (delete-backward-char n)))))

And the binding, replacing the stock one:

(keymap-set vertico-map "DEL" #'my/-vertico-directory-delete-char)

The file-exists-p check is the heart of it, not a safety net. It is what distinguishes a path you just recalled from history (it exists — backspace removes the whole name) from a name you are typing right now (it does not exist yet — backspace fixes the typo, character by character). Orderless filter input like init compl is likewise unaffected: such input rarely names an existing file. Two more details: remote paths are skipped on purpose, because file-exists-p on a TRAMP path is a network round-trip, and backspace is the last key that should ever block on the network; and mid-path behavior is stock — after a slash, backspace deletes a whole path component; inside a name, a single character.

One honest limitation: while erasing a typed name character by character, you can land on an existing prefix (deleting Makefile2 and reaching Makefile), and the next backspace deletes a whole component. In practice, I have yet to be bitten by it.

The result: recall a path from history, press backspace once — the file name is gone; every further press climbs one directory, deliberately. No more holding the key down, no more runaway deletion, and years of finger habits work again.

History recall is where the stock behavior hurt the most, but the same rule pays off in ordinary navigation too: while the name you are typing matches no existing file, backspace edits it character by character; the moment the input is a complete file name — typed in full or completed with TAB — a single backspace removes it whole. Funnily enough, ido is worse here: whenever there is text in the minibuffer, it deletes character by character. My ido habits, it turns out, were really about directory-oriented history — and where the two behaviors differ, I like the new one more.

To be clear, none of this is a critique of the stock vertico-directory behavior — my replacement is simply what fits my habits better, and I hope it may be useful if your years of ido left you with the same directory-oriented reflexes.

Here is the commit in my config, with the use-package wiring around it.

Update (2026-07-20): Daniel Mendler, the author of vertico, responded: the stock behavior is deliberate — chosen precisely because of the Makefile-prefix case above. My replacement is just a compromise that works better for me — as noted above, that case has yet to bite me in practice. He also suggested consult-dir, and it is a nice alternative for the same need: quickly pick a recent directory and, if needed, continue navigating from there — exactly the workflow I was trying to streamline. I am keeping my replacement — it works well with my habits — but I will be using consult-dir alongside it: a great alternative way to do the same thing, and quite possibly a more efficient one.

-1:-- Making backspace in vertico behave like ido (Post Vadzim Shender)--L0--C0--2026-07-16T00:00:00.000Z

Irreal: Update To Jinx Reconsidered

Yesterday I wrote that I had reconsidered my stance on Jinx and decided to give it a serious try. The only problem I had was that picking the correct spelling wasn’t working as advertised. I speculated that this was probably the result of some interaction between Jinx and Ivy and this, indeed, turned out to be the case.

Embarrassingly, mbork had already pointed this out in his post about Jinx that I linked to but since I wasn’t planning on installing Jinx, I hadn’t paid it much attention. His solution was simple: he merely disabled Ivy for Jinx by advising jinx-correct. That works fine but I’m not sure that I like that behavior either. You have to press Tab to get the list corrections and then it isn’t any easier choose the right one.

I’m sure there’s something simple that I’m missing so I’ll continue experimenting or perhaps one of Irreal’s more knowledgeable readers will tell me I’m just being stupid. In any event, I’m going back to the original behavior until I can figure out the correct way of fixing things.

-1:-- Update To Jinx Reconsidered (Post Irreal)--L0--C0--2026-07-15T15:15:37.000Z

Meta Redux: CIDER 2.0: Sky is the Limit

Two weeks ago I wrote that CIDER 2.0 was brewing. Today the brew is ready - CIDER 2.0 (“Terceira”)1 is officially out! I promised the release would follow the preview within a week or two if nothing serious surfaced, and for once in my life I’m actually on schedule.

The preview post covered the big themes in detail - the transient menus, the call-graph browsers, cider-macrostep, the revamped tracing and enlighten, the ClojureScript improvements - so I won’t rehash all of that here. Instead I’ll focus on what changed between the preview and the release, and on the bigger picture of what CIDER 2.0 is actually about.

What CIDER 2.0 is about

Looking back at the (enormous) changelog, the release boils down to four themes:

  • Tackle some ambitious ideas that had been lying dormant for ages - inline macro stepping, rich (content-type) results, source-aware cross-referencing. Some of the issues closed by this release were filed the better part of a decade ago.
  • Polish the “understand your code” toolbox - the debugger, the macroexpansion facilities, tracing, enlighten, the stacktraces and the cross-references all got a serious amount of love.
  • Make the whole CIDER experience more consistent and discoverable - transient menus everywhere, one tree-view widget shared by all the browsers, and a naming cleanup that brought a bunch of stragglers in line.
  • Fix old annoyances - the friendly-session complexity that 1.22 started taming (remember the redisplay lag fix and default sessions?), the find-references gaps, the flaky SSH tunnels, the confused stdin handling.

Notice what’s not on that list - a pile of shiny new features. There are a few genuinely new things in 2.0, of course, but the heart of this release is that most of CIDER’s important features got overhauled (tastefully, I hope) or made more robust and faster. After 14 years you accumulate a lot of good ideas with rough edges; 2.0 is me going over them with fine-grit sandpaper.

What landed after the preview

Quite a lot, as it turns out - the last two weeks were busy. The headliners:

  • Rich results are now on by default. Evaluate something that returns an image and it renders inline; a result that points to external content (a file, a URL) gets a [show content] button that fetches it only when you press it. HTML renders as formatted text, URLs are clickable. This works for regular C-x C-e-style evaluations too, not just in the REPL (configurable via cider-eval-rich-content-destination). Fun fact: content-type support was added way back in 0.17, disabled in 0.25 after it got a bit overzealous with the fetching, and the interactive-eval part was requested in 2018. Better late than never, right?
  • The transient story got finished. The debugger and the inspector now have menus of their own (? and m respectively), and many menus grew argument flags - pick a pretty-printer per invocation, set test selectors once and reuse them across runs, toggle the refresh modes, pass aliases at jack-in. As before, your muscle memory is safe - the menus only help when you pause.
  • There’s a new cider-doctor command that checks your Emacs setup and your active session for common problems (version mismatches, stale byte-code, leftover obsolete config) and produces a copy-pasteable report. My hope is that it will make “CIDER doesn’t work” bug reports a thing of the past - or at least give us something to look at when they arrive.
  • Pending evaluations now show an animated spinner overlay right at the form you’re evaluating, instead of a spinner in the mode-line of a REPL buffer you probably can’t even see.
  • The debugger got dusted off properly: quitting a debug session finally restores point to where you started - an issue filed in 2016 - the force-step-out key works again, and cider-nrepl 0.62 fixed a batch of instrumentation bugs (records surviving instrumentation, clear errors for forms too big to instrument, and a few crashes).
  • Stdin handling got a long overdue overhaul - input prompts are routed to the session that actually asked for input, cancelling a prompt now interrupts the evaluation (instead of quietly letting it continue), and C-c C-d sends EOF for code that reads until end of input.
  • Clicking a stack frame for a top-level anonymous function now jumps to the actual source instead of clojure.core/fn - a bug from 2020 - and ClojureScript frames render their ns/fn properly.
  • A big consistency pass over the options: the REPL history browser is now cider-history, the inline-result options became a coherent cider-eval-result-* family, and the six per-buffer auto-select options collapsed into a single cider-auto-select-buffer. Every old name keeps working as an obsolete alias, so nothing breaks.
  • And a long tail of robustness work - a slow memory leak on the eldoc/completion path, cider-classpath on Windows, formatting no longer corrupting multi-line strings, theme-aware colors for the nREPL message log, and plenty more of the same ilk.

The documentation also got restructured to be more approachable - there’s a proper quickstart now, a keybindings reference page, dedicated pages on using CIDER alongside clojure-lsp and clojure-ts-mode, and a guide for full-stack Clojure + ClojureScript projects. The manual has grown organically for over a decade, and it showed; hopefully finding things is much easier now.

Upgrading

Despite the big scary version number, upgrading should be uneventful. All the renames ship with obsolete aliases, the transient menus preserve the classic keybindings, and the only removals are commands that had been no-ops for years. The one bit of muscle memory you may need to adjust: cider-macroexpand-all moved from C-c M-m to C-c M-m a, as C-c M-m is now a prefix for all the macroexpansion commands. If anything feels off after the upgrade, M-x cider-doctor is your friend.

Fourteen years later

CIDER 0.1 (well, nrepl.el 0.1) was released on July 10th, 2012 - fourteen years (and five days) ago.2 I’ve been reflecting on this a lot lately. Fourteen years is an eternity in our line of work - entire ecosystems have come and gone in that time - and yet here we are, still innovating, still improving, still moving forward. I dare say CIDER 2.0 is the strongest release in the project’s history, and it’s certainly the one I’ve enjoyed working on the most.3

None of this would have been possible without the people and organizations who have supported the project over the years - everyone who contributed code, reported issues, wrote about CIDER, answered questions, or backed the project financially. A special thanks to Clojurists Together for their long-standing support, and to everyone who took the snapshot for a spin after the preview post and shared feedback - several rough edges got filed down because of you.

So, go play with CIDER 2.0! Kick the tires, explore the menus, crack open some values in the inspector, step through a macro or two. And if CIDER makes your work a little nicer every day, consider supporting its future development

  • that’s what keeps CIDER and friends going.

Where to from here? The sky is the limit. The REPL is the inspiration. The best is always yet to come…

Keep hacking!

  1. Continuing the Azores naming streak started by 1.22 (“São Miguel”). “Terceira” literally means “the third” in Portuguese, which is a slightly confusing name for a 2.0 release, but naming things has never been my strong suit. 

  2. The full origin story is in CIDER Turns 10, if you’re curious how a prototype hacked on a flight to San Francisco ended up here. 

  3. That I can remember. My memory is not what it used to be. 

-1:-- CIDER 2.0: Sky is the Limit (Post Meta Redux)--L0--C0--2026-07-15T13:40:00.000Z

Bicycle for Your Mind: MojoPad is a Wicked Wiki

mojopadmojopad

Product: MojoPad — a thinking space for your Mac · second brain & PKM
Price: $59

Some of you might remember VoodooPad. It was a product marketed with the tag line “VoodooPad - You put your brain in it”

I used it a long time ago. After version 5, the product got sold to a new developer who promised version 6. Users of VoodooPad have been waiting for a long time for that. We are still waiting.

Paul Korn in the Miss the old VooDooPad? · Outliner Software post described MojoPad as “VooDooPad definitely was wonderful – and it’s wonderful to see a sort of tribute-band modern version.”

I don’t remember everything VoodooPad used to do or how it did it. In this article I am going to cover what MojoPad does and not worry about comparing it to VoodooPad.

I would describe MojoPad as VoodooPad remixed for the modern age. Yes, the product is inspired by VoodooPad, but it does so much more that the comparison is not fair to it.

TL;DR: If you are looking for a PKM application which is innovative, comprehensive, and one you can live in, check out MojoPad.

Caveats: MojoPad is an Electron application. If you have a visceral negative reaction to all Electron applications, you are better off avoiding reading further. If you can see past that, you might want to read on and try out a product which could become the centerpiece of your writing life.

First Things First

MojoPad comes with a great manual. Go read it first. MojoPad is a deep product and you are going to read the manual a few times. Go through it once. You are not going to remember all of it. It is large. But you are going to have an idea of what the toolset of MojoPad is. That will guide what you use it for. You can find the manual in the Help menu. It is a MojoPad document.

MojoPad is Pretty

prettypretty

The first thing that strikes you when you launch MojoPad is that it is pretty. One could never accuse VoodooPad of that. It comes with a collection of themes and is nice to look at.

OK, but What Is It?

Marketed as “A Digital Playground For The Mind” MojoPad is a personal wiki. We have all become used to wikis in the context of “knowledge-management” programs like Obsidian. MojoPad is that. You surround a selection of words with two square brackets, that becomes a link. You click on it and the program takes you to a new page. You can link both to a new page or to an existing page. Conceptually it is a website on your local machine containing links to and from parts of the document you are working on. It is a powerful metaphor for managing your notes, projects, and writing.

Settings

themesthemes

MojoPad has a nice collection of themes.

more customizationmore customization

Some more choices to make the program look pretty and customize it to your taste.

linkslinks

You get to open the links in the same window you are on or in a new tab. I love the preview bubble. Probably not useful, but it is an interesting look. You hover over a link and a bubble showing the first few lines pop up. Pretty.

editingediting

You get to choose the default font for the editor, the font for the headings. Interestingly, you can specify a font for the Markdown source view. Love Lexend — Change the way the world reads. and am using it almost everywhere I can. Zed Mono, a forked Iosevka, is the default Markdown font for me. You have some of the usual typographic controls.

shortcutsshortcuts

This is the feature which makes me love MojoPad. Every keyboard command is customizable. You can change it all. Use the ones you are used to. If you are used to outliners or text editors, you have favorites merged into your muscle memory. Use them in MojoPad. Makes using it easy.

web clipperweb clipper

MojoPad has a good web clipper. It is an extension for Chrome and a bookmarklet for Safari.

backupbackup

There is a backup function and you can specify the location of the backup. Gives me peace of mind.

location, date, and timelocation, date, and time

You get to specify the date and time formats and your location in the application.

Choice of Formats

MojoPad lets you choose your document format. You can work in rich text, plain text, or Markdown. Of course, I choose to work in Markdown.

It has a full-featured outliner mode (more on that later).

Edit Page in External Editor

external editorexternal editor

You get to assign an external editor to the program. If you prefer writing in another program, you can assign an editor as your external editor and use that to write. MojoPad is not bad as an editor, but I prefer Emacs or iA Writer, so I use this function a lot.

A keyboard command switches the present page to the external editor. I work on it there, save it and come back to MojoPad.

Writing in MojoPad

Writing in MojoPad is a good experience. You get to choose your own fonts and it has typewriter scrolling with nice themes. You have great typographic control and can control spaces and distance between lines and paragraphs.

For me, the usefulness of an editor is a function of its support for keyboard commands. I don’t like mousing around and MojoPad’s support for keyboard commands and the ability to customize them to my liking makes it a great environment to write in.

A few words about formats. MojoPad supports rich text, plain text and Markdown. I don’t care about rich text, so I have no input on that. The Markdown support is excellent. It doesn’t deal with Org-mode in the sense that it doesn’t do any syntax highlighting of Org-mode files, but it treats them as plain text files and that is enough for my use.

MojoPad has an outlining mode. A document can be treated as an outline document and it supports all of the features that make a good outliner. I have been using it for a while and haven’t found anything that I am missing. It is well implemented. The interesting thing about MojoPad is lot of the features of an outline are shared by Markdown mode. Same keyboard commands, same general structure and the same ease of dealing with documents. At this stage, MojoPad is a great outliner along with its other features.

There are small touches in MojoPad which make writing in it simple and efficient. Some of the ones I am fond of:

You can fold/unfold headings and reduce distractions while you are writing.

The command Open Folder as Longform… makes it possible to view multiple pages as a stream. You can choose the pages or it can follow the folder structure, gives you a sense of how the document will flow if presented in a particular sequence. Useful.

Another command which I use all the time is Open Side by Side… This lets me get two views into the same document or a view into a different document while working. I use this feature a lot.

Tasks and Projects

MojoPad has a an implementation of tasks and associated projects. I have not explored this much. My tasks and projects are maintained in Emacs through Org-mode. But I have rudimentary task lists in MojoPad and they work great giving me the opportunity to break up a big task into its component pieces and providing a running tally of how many of the sub-tasks have been completed out of the total number of sub-tasks. Another useful touch.

Additional Features

There is a Kanban board, and a Canvas implementation in MojoPad. I have not explored those. Nor have I explored the Mermaid diagrams implementation. I suggest that you read the manual of MojoPad. That is where all of the features are detailed and it is an invaluable resource if you are going to be using this program. You will discover many goodies hidden there. The documentation is excellent and it is free. Use it.

The Developer

Mark Busse is the developer of MojoPad. I have never interacted with a developer like Mark. He is on the ball. I find a bug, or a feature request, and send it to him. It almost seems like the fixes/additions make it to the program before the send is complete. He is on it. I suspect he is a super-spy who keeps a close watch on my brain waves to monitor what he can find out about my usage of MojoPad. Seriously, he is responsive and absolutely phenomenal when it comes to fixing bugs and adding features. It is an unqualified pleasure dealing with a developer who is that responsive.

I am impressed and I would use MojoPad only because of his attitude and talent. The product now has a blog at Blog — MojoPad and a community discourse channel at MojoPad. Join up, ask questions, opine, and contribute to the future direction of MojoPad.

Improvements

I cannot think of anything I would suggest and that is extremely rare for me. OK, maybe one thing. I know it is an Electron app and it chews up memory. I would like an attempt to control the amount of memory the application eats up. The speed of the application is not a problem, the memory usage is. A focus on reducing that would be a welcome addition.

Conclusion

You can live in MojoPad. This can be the central node of your writing life.

I recommend MojoPad heartily.

Thanks: Thanks to the developer who provided me with a review license for the product when I asked for one.

-1:-- MojoPad is a Wicked Wiki (Post Bicycle for Your Mind)--L0--C0--2026-07-15T07:00:00.000Z

Still working with Claude on my Emacs settings as a reference. Need to start a new session, so asked it to write a summary with instructions on how I like it to help me. It wrote this:

I know it’s complementing me on purpose, but that emphasized why? That’s why my blog is called this way 😉

-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-07-14T22:20:58.000Z

Sacha Chua: Voulez-vous parler d'Emacs avec Prot et moi ?

: J'ai ajouté des bénévoles. J'ai hâte de discuter avec eux !

C'est merveilleux ! Prot a répondu à ma vidéo et il adhère à mon idée folle d'essayer des conversations sur Emacs avec d'autres personnes en français, malgré mon niveau débutant. Prot est aussi en train de pratiquer son expression orale, donc c'est parfait. Comme Prot l'a dit, je dois parler sans peur. Tout le monde est patient, et si on n'est pas patient, on peut partir. ;)

Voulez-vous parler d'Emacs avec Prot et moi ? Je pense que ce sera comme mes conversations Emacs Chat en anglais. L'objectif est principalement le partage de chouettes idées sur votre flux de travail et votre configuration, au lieu de se focaliser sur moi. Je devrais donc pouvoir survivre avec mon français. Peut-être que le chat web me sauvera si je suis bloquée. Je peux probablement aller loin avec quelques expressions comme:

  • Qu'est-ce que vous faites avec Emacs ?
  • Comment êtes-vous tombé amoureux d'Emacs ?
  • En lisant votre configuration, j'ai remarqué que…
  • Oh, dites-nous en plus sur la façon dont vous l'utilisez.
  • Y a-t-il un raccourci clavier ou une petite astuce préférée ?
  • Comment avez-vous fait ça ?
  • C'est incroyable, je n'avais aucune idée qu'Emacs pouvait faire ça !
  • Quel conseil donneriez-vous à quelqu'un qui commence tout juste à apprendre ?

Je pense que ma fille se lève plus tard pendant l'été. Si je planifie des sessions tôt (pour moi), nous pourrons probablement avoir une conversation avant que je doive m'occuper de mes activités de maman. Par conséquent, j'espère être disponible (America/Toronto; UTC-4 à ce moment; si vous avez Javascript et si vous lisez ceci sur mon site, vous pouvez cliquer sur l'heure pour convertir à votre fuseau horaire):

Si vous souhaitez me rejoindre pour une des séances, s'il vous plaît, envoyez un courriel à sacha@sachachua.com avec vos dates préférées, puis je vais m'organiser avec Prot. Si aucun de ces créneaux horaires ne vous convient, veuillez nous en suggérer d'autres et nous verrons si c'est possible.

Si vous incluez des liens vers votre configuration ou votre site, je pourrai préparer des notes et chercher des mots dans le dictionnaire. =)

( Et si mes tentatives inspirent d'autres personnes à partager leurs apprentissages malgré leur envie de toujours peaufiner, ce sera incroyable. )

D'autres sujets dans ma vidéo pour Prot :

Faire du shadowing consiste à écouter un exemple court et à le répéter immédiatement (peut-être plus lentement ou à la même vitesse) pour imiter la prononciation et le rythme. Si je suis sur mon ordinateur, c'est facile d'utiliser les sous-titres pour répéter une phrase, mais jusqu'à présent, je n'ai pas de flux de travail similaire sur mon téléphone sauf pour faire du shadowing sur des phrases avec la synthèse vocale dans Chrome. Je suis sûre qu'il y a beaucoup d'applications qui demandent un abonnement pour offrir cette fonctionnalité, mais comme vous le savez, je veux exploiter Emacs (ou mes propres logiciels) au maximum. Un jour, peut-être !

View Org source for this post

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

-1:-- Voulez-vous parler d'Emacs avec Prot et moi ? (Post Sacha Chua)--L0--C0--2026-07-14T18:22:03.000Z

Irreal: Jinx Reconsidered

In a stunning case of the Baader–Meinhof phenomenon, I’ve been running across the Jinx spelling corrector everywhere. I had never heard of it until last week when I watched and wrote about Prot’s video on writing in foreign languages with Emacs. Then Marcin Borkowski wrote about it and I posted about that. In all those instances, I concluded that Jinx was interesting but mainly of use for those who regularly wrote in more than one language. Since I seldom do that, I decided I was going to stick with flyspell.

Then Omar commented on my Borkowski post saying that Jinx was a better solution than flyspell even if you used it with only one language. I thought that perhaps I should reconsider my decision about sticking with flyspell. Finally, I saw this post by Bozhidar Batsov on why he has recently switched from flyspell to Jinx and became convinced that I should give it a try.

I installed it without difficulty and am writing this post with it as a sort of maiden voyage. So far, everything seems fine. By default, jinx-correct is bound to Meta+$, which I find a little clumsy so I changed that to Ctrl+;, which is where my muscle memory thinks it should be.

The only problem I’ve had is quickly choosing the correction. The documentation says that you can do that put typing the number of the correction but that simply exits the correction leaving the old spelling in place. The problem may be some interaction with Ivy, which I’ll investigate as soon as I get some time.

-1:-- Jinx Reconsidered (Post Irreal)--L0--C0--2026-07-14T16:02:46.000Z

Emacs Redux: All Aboard Embark!

Yesterday I published a post about Jinx, where I mentioned in passing that dropping Flyspell freed up C-. and C-; for embark, and that this would be a topic for another article. Well, here it is!

Embark has been around for quite a few years, and pretty much everyone in the vertico/consult crowd swears by it. Somehow I never got around to adopting it until my recent config overhaul, and now I keep asking myself why I waited so long.

So, What’s Embark?

Embark, written by Omar Antolín Camarena, is often described as a keyboard-driven right-click menu for Emacs, and I think that’s a pretty apt description. You point it at something - a minibuffer candidate, a file name, a URL, a symbol - press C-. (embark-act) and you get a menu of actions that make sense for that particular thing. A few examples:

  • On a buffer in C-x b? You can kill it, rename it, or diff it against its file without leaving the minibuffer.
  • On a file in C-x C-f? You can delete it, rename it, copy its path, or open it in another window.
  • On a URL in a buffer? You can browse it or copy it.
  • On an Elisp symbol? You can jump to its definition or look up its documentation.

The beauty of it is that you don’t have to abort the minibuffer session, fiddle with dired or remember some obscure command name - the action comes to you, right where you are.

Here’s the action menu you get when you press C-. with point on a URL:

embark-act showing the available actions for the URL at point

There’s also embark-dwim (C-; here), which skips the menu and runs the default action directly - it opens the URL at point, visits the file at point and so on. I find myself using it all the time for the common cases and reaching for the full embark-act menu when I need something more exotic.

The Killer Feature: embark-export

Acting on a single thing is nice, but embark-export is where Embark really shines. It takes the entire set of minibuffer candidates and dumps it into a buffer of the appropriate major mode:

  • consult-ripgrep/consult-grep results become a grep-mode buffer
  • consult-line results become an occur buffer
  • file candidates become a dired buffer
  • buffer candidates become an ibuffer buffer

You trigger the export from within the action menu - e.g. C-. E while in the middle of a consult-ripgrep session.

In other words - you start a quick search in the minibuffer, and if it turns out to be something bigger, you promote it to a proper buffer you can navigate, save for later, or (my favorite) edit in place with wgrep (C-c C-p in the exported grep buffer) and apply the changes across all the matched files. Once this workflow clicks, there’s no going back.

Tips and Tricks

A few more things I picked up in my first days with Embark that you’ll probably want to know about:

  • Press C-h right after C-. to get a searchable completing-read menu of every applicable action, and run the one you pick. This is both the best way to discover what Embark can do and a lifesaver when you’ve forgotten a key.
  • C-. i inserts the current candidate in the buffer you came from and C-. w copies it to the kill-ring. Sounds mundane, but grabbing a file path or a variable name from the minibuffer this way quickly becomes second nature.
  • C-. A (embark-act-all) acts on all the candidates at once - think killing every buffer matching your current input in one go.
  • C-. B (embark-become) replaces the current minibuffer command while keeping the input you’ve typed. Started C-x C-f, but realized you actually need consult-buffer? No need to abort and start over - just “become” the other command.
  • By default acting on a candidate exits the minibuffer. Call embark-act with a prefix argument (C-u C-.) to keep the session alive - handy when you want to, say, delete a few files in a row. (see embark-quit-after-action if you prefer to flip the default)

Embark Meets Projectile

Being the Projectile maintainer, I obviously had to check how well Embark plays with it. The answer is - pretty well, actually! Projectile advertises the completion category of its candidates, and Embark knows how to resolve a project-relative path like lisp/init-git.el to a full path before acting on it.1 This means the whole file action arsenal works in projectile-find-file:

  • C-. w - copy the full path of a project file without visiting it
  • C-. r - rename a file (no dired detour needed)
  • C-. d - delete a file you just realized shouldn’t exist
  • C-. j - jump to the file’s location in dired

projectile-switch-project is even more fun, as there the candidates are the project directories themselves:

  • C-. j - browse a project in dired without actually switching to it
  • C-. $ - open eshell directly in some project’s root (Embark is smart enough to run shell actions in the target’s directory)
  • C-. w - copy a project’s path
  • C-. E - export the entire list of your known projects to a dired buffer

That last one makes for a pretty nice project dashboard, if you ask me.

You can also teach embark-become about Projectile. Out of the box its file/buffer map offers p for the built-in project-find-file, but we can easily add a Projectile equivalent:

(with-eval-after-load 'embark
  (keymap-set embark-become-file+buffer-map "P" #'projectile-find-file))

Now when you start a plain C-x C-f and realize the file is somewhere deep in the current project, C-. B P will re-run your input through projectile-find-file instead. No retyping needed.

Note: One small asymmetry - C-. E in projectile-find-file currently produces an Embark collect buffer rather than a dired buffer, as the exporters operate on the raw completion category. Still useful (you can keep acting on the candidates from there), just don’t expect wdired magic.

Note: The exporters for the various consult commands live in the embark-consult package, so you’ll want that one too if you’re a consult user.

The Setup

Here’s the relevant bit of my config:

(use-package embark
  :ensure t
  :bind (("C-." . embark-act)
         ("C-;" . embark-dwim)
         ("C-h B" . embark-bindings))
  :init
  (setq prefix-help-command #'embark-prefix-help-command))

(use-package embark-consult
  :ensure t
  :hook (embark-collect-mode . consult-preview-at-point-mode))

C-. and C-; are the bindings suggested by Embark’s README and they are quite comfortable, provided nothing else is squatting on them.2 The prefix-help-command bit is a small gem on its own - press C-h after any prefix (say C-x) and instead of the regular help buffer you get a completing-read over all the bindings in it, which you can narrow and execute directly.

Embark doesn’t really care which minibuffer completion UI you’re using - it works fine with vertico, icomplete and even the default completion setup.

Closing Thoughts

Embark is one of those packages that are a bit hard to explain, but make total sense the moment you try them - the “aha” moment for me came the first time I exported a consult-ripgrep session. It has certainly earned its place in the small set of packages I’d install on day one.

Are you using Embark already? What are your favorite actions? I’d love to hear about them in the comments!

That’s all I have for you today. Keep acting (on all the things)!

  1. Under the hood the path resolution goes through project.el, so this works in any VC-backed project - which covers pretty much every Projectile project out there. 

  2. In my case Flyspell was hogging both of them, which is partially what prompted its replacement

-1:-- All Aboard Embark! (Post Emacs Redux)--L0--C0--2026-07-14T08:45:00.000Z

Andros Fenollosa: Human fear in Emacs

Just a few days ago, Human Emacs was published: a declaration signed by more than 50 people from the community, led by technomancy, announcing that if GNU ends up accepting LLM-generated contributions in Emacs, they are prepared to maintain a fork free of AI-generated code. They have also opened a mailing list to discuss the topic and a chat channel on Libera Chat. Naturally, debates have appeared elsewhere too, such as Hacker News, Lobsters and Reddit. The debate is on.

I notice a lot of tension in the community. Every time I leave a comment in a thread supporting some point contrary to Human Emacs, I receive downvotes and flags for off-topic, spam, troll and unkind. That is why I thought it appropriate to develop my opinion on my blog, where there is room for nuance without the pressure of votes and labels.

That said, it is worth separating two debates that are being mixed together. The first one belongs to GNU and the FSF, the legal matter: if LLM-generated code cannot be copyrighted, it could weaken copyleft.

The licensing arguments seem legitimate to me, but easy to get around. Today you can work with a free LLM, with open weights, running on your own machine. If the purity of the tool really mattered, we should not accept patches typed on Windows with a proprietary IDE. If code is the heart of free software, we should only care about it being useful, maintainable and of good quality, regardless of its origin. And if what matters is the person behind it, we are entering territory that has already been settled: contributions developed by private companies are accepted.

The second one belongs to the Human Emacs signatories, and it is purely ethical. They make it clear themselves in their "Not Under Discussion" section:

We are not here to discuss whether LLMs are effective at what they are claimed to be able to do; their effectiveness is not at all relevant to the question of whether their use can be part of a principled software movement dedicated to user empowerment.

This stance has the virtue of being honest: they are not discussing whether LLMs work, they are discussing whether they fit their principles. And there is no possible debate against someone's principles. I will only say that I do not share the premise that LLMs are incompatible with user empowerment: for someone who cannot program, being able to add a complex feature to their editor is precisely empowerment.

I fully understand the right to fork, and I defend it: I maintain emacs-gpu myself. Forking is the essence of free software, and having an Emacs without AI code for those who want it seems healthy to me. What I find harder is the practical side: there is no reliable way to detect LLM-generated code, so the policy can only rest on trust and each contributor's voluntary disclosure. And we already know where that leads: a few weeks ago a 92-line performance patch was rejected on emacs-devel precisely because its author disclosed that it had been drafted by an open-weights model, even though they reviewed it, modified it, tested it and took legal responsibility for it themselves. If they had kept quiet, nobody would have known. A policy that punishes honesty only produces less honest contributors.

Personally, I believe LLMs will improve Emacs in areas where it was not possible before. It is already happening with very complex bugs and with abstraction layers that required an enormous amount of human work that nobody was willing to do. And although I understand that LLM-written code in the core is not the same as human-written packages that use LLMs, such as gptel or agent-shell.el, I struggle to see the coherence between rejecting the former outright and celebrating that the latter is bringing fresh air into the ecosystem. I believe that, deep down, there is more human fear than real problem: perhaps the root lies neither in the LLMs nor in the FSF, but in the uncertainty that such a big change stirs in us. And that fear runs both ways: fear that LLMs will degrade what we love, and fear of falling behind by not using them. The Emacs community has always been very open to new ideas, and I would like it to stay that way.


Will you buy me a coffee? This is how I keep writing without ads or paywalls. Sure, it's on me!

Send an email to comment+article-b70b058a@andros.dev to leave a comment. The subject will be ignored.

-1:-- Human fear in Emacs (Post Andros Fenollosa)--L0--C0--2026-07-14T06:06:15.000Z

Jakub Nowak: Response to Human Fear in Emacs

This is a short response to the blog post titled Human fear in Emacs by Andros Fenollosa, as I believe there is a small correction to be made around the legal matter surrounding LLM contributions to Emacs under the current policy.

Note that I am not a contributor to Emacs, nor GNU; this is purely my understanding of their position and it may be wrong.

The licensing arguments seem legitimate to me, but easy to get around. Today you can work with a free LLM, with open weights, running on your own machine. If the purity of the tool really mattered, we should not accept patches typed on Windows with a proprietary IDE.

This is a faulty comparison, in my opinion, and a misunderstanding of the licensing issue. The FSF and GNU require assignment of copyright from contributor to foundation in order to effectively enforce the GPL.

The fundamental problem with LLM output is that it is currently a legal grey area in terms of who the owner of the generated output is. I am not a lawyer, but you could certainly argue that the output is owned by one of either:

  1. The LLM developer.
  2. The LLM user.
  3. The developers whose code was used to train the LLM.
  4. No-one.

Because the FSF requires assignment of copyright, the only situation where the copyright could feasibly be transferred is in option 2. Unless, of course, you manage to convince Anthropic or every GitHub developer to sign off copyright every time someone submits a patch to GNU.

In all likelihood, the answer to who the owner of the output is, is "No-one" (this opinion is based off these two lawsuits, and is strictly my opinion of a very complex legal topic: 1, 2). However, until someone takes Microsoft to court for scraping GitHub repositories to train Copilot, we cannot definitively say which of these it is. That uncertainty is why the FSF cannot allow LLM contributions.

This problem does not go away with a free LLM and open weights, because those weights were still trained on a set of data not owned by the LLM developer (99% of the time, at least). Therefore, this is not such an easy thing to work around, unless you want to make an LLM trained purely on your own code from scratch (I don't mean fine-tune an existing model, I mean to make a complete model entirely from scratch). And the size of that dataset certainly will not be big enough to produce a useful result.


The other thing I wanted to mention is the performance patch compiled by Przemysław Alexander Kamiński which was rejected from emacs-devel. I've seen this discussed a lot recently in various places, here are my two cents (again, quoting from Andros' blog).

And we already know where that leads: a few weeks ago a 92-line performance patch was rejected on emacs-devel precisely because its author disclosed that it had been drafted by an open-weights model, even though they reviewed it, modified it, tested it and took legal responsibility for it themselves.

As I said above, though you can "claim" legal responsibility for the output of the LLM, you cannot "take" it as the ownership is still disputable. This may seem like a semantic difference, but it is critical in understanding why this patch was rejected.

I think a substantial amount of the discussion around this patch was mired with people misunderstanding the legal status of LLM output. Once you realise that the reasoning here is one of legal caution for the single organisation actually enforcing the GPL and software freedom, it becomes a bit more reasonable of a decision in my eyes.

If they had kept quiet, nobody would have known. A policy that punishes honesty only produces less honest contributors.

I don't believe that this policy punishes honesty more than any other policy that you could implement in moderation of a codebase.

The fact of the matter is that all of FOSS relies almost entirely on an open trust model to operate, and everyone expects everyone else to act like decent human beings. And some people don't, which is how you get supply chain attacks with random contributors compromising xz, and this will happen whether or not you have a rule that says "don't include trojans in your PRs pls."

So, what, we should do away with contributor guidelines entirely now because people can lie? No, you enforce the rules against the people who are honest because it holds everyone accountable to the same standard. This is the same way every law works in almost every single country: you could, right now, go and lie to the police about anything you want! The difference is that in one of these, your patch gets rejected, and in the other, you get arrested.

I want to thank Andros for putting his thoughts on a stage; it's not an easy thing to do. I hope that you don't take my direct responses as rude or otherwise improper.

-1:-- Response to Human Fear in Emacs (Post Jakub Nowak)--L0--C0--2026-07-14T00:00:00.000Z

Say you go jump to a header in org-mode somewhere with C-c C-o. Did you know that you can go back with C-c &? And that it has history, so you can keep going back??

Years of isearch-backwards… wasted. Mind blown. 🤯

-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-07-13T22:21:20.000Z

Sacha Chua: 2026-07-13 Emacs news

There's some more buzz about spell-checking with Jinx (Bozhidar Batsov, Marcin Borkowski). Also, quite a few people have signed up for the Human Emacs initiative.

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- 2026-07-13 Emacs news (Post Sacha Chua)--L0--C0--2026-07-13T17:45:19.000Z

TAONAW - Emacs and Org Mode: A Quick org-mode Internal Links Reference

As I mentioned yesterday, I started organizing (and cleaning) my Emacs config. What may have seemed like an annoying and boring task has proven to be fun and addicting (reminds me of this, which is part of this video 😂).

I’ve already discovered a couple of new improvements to my current config as I was researching individual settings and why I have them, and as I said I do plan to share it. For now though, one important section I added to my config is a quick org-mode links reference. This is actually the first time I figured out the difference between radio links and “regular” plain links and how to use them.

Here it is, slightly modified:

Org-ID

We know about links to other files using org-id: You give a header an :ID: property (this is generated automatically1 when you link to a header) and you get a slug, usually a UUID (for me it’s something slightly else, more on that soon below). This stores the links in org-id-locations, which in turn points to a local file with all of these unique IDs. This is good for linking between headers in different files.

Custom-ID

We can set a :CUSTOM_ID: property for a header, and it’s only active in that file. Kind of like a table of contents in a book, with chapters (= headers) listed. If you don’t have the book, you don’t have its table of contents, so you know nothing about it. To point to a header that has a custom ID: [[#custom-id-value][description]].

Plain Target and Radio Target link

These are buffer-specific only (no cross-reference between files). In this case, the links are tied to words (anchors), not headers. There are two kinds.

The regular plain target link works when we set the anchor first with <<an anchor>>, then call it again with [[an anchor]].

Its more “magical” brother is a radio target link, where we set an anchor like so <<<very special anchor>>> (three angle brackets) and then all we need to do is type very special anchor (no brackets this time), and it works. Note though that when you create a radio target for the first time, you have to revert the buffer or M-x org-update-radio-target-regexp for it to take effect.

Footnotes

1 : this can be done with a function, org-id-get-create. For a deeper dive into how and why I set my org-id with a date format (not UUID), look here.

-1:-- A Quick org-mode Internal Links Reference (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-07-13T12:55:12.000Z

Emacs Redux: Cape: Corfu’s Best Friend

I’ve been using corfu for in-buffer completion for a while now and I’m quite happy with it. There was one thing that kept bugging me, though - the completion popup would only ever show whatever the current major mode’s completion function had to offer. No plain word completion, no file name completion - if the major mode (or your LSP server) didn’t know about something, neither did the popup.

Turns out I was simply missing one piece of the puzzle, namely cape.

The Problem

A bit of background first. Modern Emacs completion is built around completion-at-point-functions (capfs for short) - each major mode registers a function that knows how to complete things in its buffers, and UIs like corfu (or the built-in completion-at-point) simply display the results.

The catch is that most major modes register only their own capf. That’s why the trusty old dabbrev-style completion (complete any word that appears in your buffers) is nowhere to be found in the popup, even though most of us have been relying on it for decades via M-/.

Enter Cape

Cape (Completion At Point Extensions) is another package by the prolific Daniel Mendler and it does exactly what its name suggests - it provides a bunch of extra capfs that you can mix into any buffer:

  • cape-dabbrev - complete words from the current and other buffers (the star of the show, in my opinion)
  • cape-file - complete file paths
  • cape-keyword - complete programming language keywords
  • cape-elisp-symbol - complete Elisp symbols anywhere (e.g. in comments or docstrings)
  • cape-dict - complete words from a dictionary file
  • cape-line - complete entire lines from the buffer
  • cape-emoji - complete emoji, if that’s your thing (Emacs 29+)

My setup is deliberately minimal - buffer words and file names everywhere, on top of whatever the major mode provides:

(use-package cape
  :ensure t
  :init
  (add-hook 'completion-at-point-functions #'cape-dabbrev)
  (add-hook 'completion-at-point-functions #'cape-file))

That’s it. Now when I type a few characters, corfu shows the major mode’s candidates and falls back to buffer words when there’s nothing smarter to offer. Typing a path like ~/proj offers to complete it as a file name. It’s one of those small quality of life improvements you stop noticing after a day, because it feels like it has always been there.

Note: Every cape capf is also an interactive command, so you can invoke them on demand - e.g. M-x cape-file to complete a file name regardless of your setup. Cape’s README suggests putting them on the C-c p prefix, but for me (and my fellow Projectile users) that’s a no-go, as C-c p is Projectile territory. Pick your own prefix if you want quick access to them.

Tips and Tricks

A few practical things worth knowing:

  • Type file: anywhere and cape-file kicks in for the text right after it, even in places where a path wouldn’t normally be recognized (say, in the middle of a comment). The prefix is customizable via cape-file-prefix.
  • cape-dabbrev gathers its candidates from buffers with the same major mode by default, so your Elisp buffers won’t pollute the completions of your Clojure buffers. If you want different behavior, that’s cape-dabbrev-buffer-function.
  • If you set tab-always-indent to complete (as I do), TAB becomes the perfect trigger for the whole completion stack - it indents the line if needed and otherwise summons corfu with all your capfs, cape ones included:
;; smart tab behavior - indent or complete
(setq tab-always-indent 'complete)
  • cape-dict reads from /usr/share/dict/words by default, which is fine on macOS and most Linux distros, but you can point cape-dict-file to any word list - e.g. one for your native language.

A Couple of Power Tricks

Cape also ships a few combinators that are worth knowing about:

  • cape-capf-super merges several capfs into one, so their candidates show up in a single unified popup. (e.g. you can blend eglot’s completion with cape-dabbrev)
  • cape-company-to-capf converts company-mode backends into capfs, which is super handy if there’s a company backend for your favorite tool but no capf in sight.

I haven’t needed either of them yet, but it’s good to know they are there.

Closing Thoughts

If you’re already on the vertico/consult/corfu bandwagon, cape is pretty much a mandatory addition - corfu deliberately stays small and focused, and cape is the intended way to extend what it can complete.1 Funny enough, I went years without it simply because I never stopped to ask why file names wouldn’t complete in the popup. Sometimes you don’t know something is missing until you try it.

What’s in your completion-at-point-functions? Any cape goodies I’ve overlooked? Let me know in the comments!

That’s all I have for you today. Keep completing (at point)!

  1. Also, let’s be honest - corfu wearing a cape is a pretty great mental image. 

-1:-- Cape: Corfu’s Best Friend (Post Emacs Redux)--L0--C0--2026-07-13T06:00:00.000Z

Emacs Redux: Replacing Flyspell with Jinx

I’ve been using flyspell-mode for the better part of two decades, and I’ve written about it a couple of times before. It gets the job done, but it has always felt a bit creaky to me - it checks words one at a time as you type, flyspell-buffer is painfully slow in big buffers, and you have to remember to enable flyspell-prog-mode in your programming modes, so it would check only comments and strings there.

Recently, as part of the ongoing overhaul of my personal Emacs config, I finally replaced it with jinx and I can already tell you that I’m not going back.

Why Jinx?

Jinx is a modern spell-checker by Daniel Mendler (of vertico, consult and corfu fame), built on top of libenchant.1 Quite a few things make it a great alternative to Flyspell:

  • It’s fast. Jinx checks only the visible part of the buffer (it hooks into Emacs’s JIT font-locking machinery), so it doesn’t matter if your buffer is 100 lines or 100,000 lines long. There’s no need for anything like flyspell-buffer - misspellings simply get highlighted as they scroll into view.
  • One mode everywhere. global-jinx-mode replaces both flyspell-mode and flyspell-prog-mode. Jinx decides what to check based on faces, so in programming modes it automatically limits itself to comments and strings.
  • Enchant is a facade over many spell-checking backends - Hunspell, Nuspell, Aspell, and (notably) AppleSpell on macOS. That last one means Jinx can use macOS’s built-in dictionaries and you don’t have to install any yourself.
  • Support for multiple languages at once. Set jinx-languages to something like "en_US bg" and Jinx will check both English and Bulgarian in the same buffer. As someone who writes in two languages every day, this alone would have sold me on it.
  • A much nicer correction UI. M-$ (jinx-correct) pops up a completing-read menu (lovely if you’re using vertico) with the suggestions, plus entries for saving the word. More on that in a bit.

Here’s Jinx flagging a few typos for me:

Jinx underlining the misspelled words in a buffer

The Setup

Here’s the relevant bit of my config:

(use-package jinx
  :ensure t
  :hook (emacs-startup . global-jinx-mode)
  :bind (("M-$" . jinx-correct)
         ("C-M-$" . jinx-languages)))

M-$ is bound to ispell-word by default, so rebinding it to jinx-correct feels quite natural. jinx-languages allows you to switch the active languages for the current buffer on the fly.

Note that those bindings are not optional - Jinx doesn’t bind any keys globally and jinx-mode-map is empty, so out of the box there’s no way to even trigger a correction! The package does come with a few handy bindings of its own, but they live on the misspelled words themselves (via overlay keymaps) and are active only while point is on one of them. More on that in the next section.

One thing to keep in mind - Jinx uses a small native module to talk to libenchant, and this module gets compiled automatically the first time you enable the mode. In other words - you’ll need libenchant (plus pkgconf) and a C compiler on your system:

# macOS
brew install enchant

# Debian/Ubuntu
sudo apt install libenchant-2-dev pkgconf

Admittedly, that’s a bit more setup than Flyspell, which is built-in and only needs an external aspell/hunspell binary. Flyspell will probably remain the path of least resistance, but in my opinion the small extra effort pays for itself many times over.

Essential Keybindings

In the spirit of Essential Flyspell, let’s go over the keybindings you actually need. The nice thing about Jinx is that almost everything hangs off M-$:

  • M-$ (jinx-correct) - correct the nearest misspelled word
  • C-u M-$ - correct all the misspellings in the buffer, one after another
  • C-u C-u M-$ (jinx-correct-word) - correct the word before point, even if Jinx doesn’t consider it misspelled
  • C-M-$ (jinx-languages) - switch the languages for the current buffer

When point is on a misspelled word a few extra bindings light up (those are the overlay-map bindings I mentioned earlier):

  • M-n (jinx-next) and M-p (jinx-previous) - jump to the next/previous misspelling. They also set up a repeat map (provided you’ve enabled the built-in repeat-mode), so after the first jump you can keep going with just n and p, and press $ to correct the word you’ve landed on.
  • mouse-3 (a.k.a. right click) - pops up a menu with the corrections. (Flyspell used mouse-2 for this, which always clashed with the default mouse yank)

The correction minibuffer has a few tricks of its own:

  • 1 to 9 select the corresponding suggestion right away
  • M-n and M-p move to the next/previous misspelled word without leaving the minibuffer - super handy in combination with C-u M-$
  • below the suggestions you’ll find entries for saving the word, prefixed with @ (personal dictionary), * (file-local), / (directory-local) and + (just for the current session)

This is how correcting a word looks with vertico (note the numbered quick select keys and the squiggly underlines on the remaining typos):

jinx-correct's suggestion menu powered by vertico

If your Flyspell muscle memory is strong, here’s how the old bindings map to the new ones:

  • M-$ (ispell-word) is still M-$ - that was the whole point of the binding in my setup
  • C-, (flyspell-goto-next-error) becomes M-n
  • C-c $ (flyspell-correct-word-before-point) is also covered by M-$, as correcting a word and saving it to your dictionary live in the same menu
  • C-. and C-; (the auto-correct commands) have no direct equivalent - Jinx always takes you through the correction minibuffer, although the numeric quick-select keys make that almost as fast

Note: Dropping Flyspell also frees up C-. and C-;, which happen to be prime keybinding real estate. In my setup they are now bound to embark’s embark-act and embark-dwim, but that’s a topic for another article.

Closing Thoughts

Funny enough, I knew about Jinx for quite a while, but I kept using Flyspell mostly because of inertia. I guess old habits die hard! Now that I’ve finally made the switch, I can’t help but wonder what took me so long.

Have you tried Jinx already? Are you still happily using Flyspell (or something else entirely)? I’d love to hear your thoughts in the comments!

That’s all I have for you today. Keep fixing those typos!

  1. I guess the name makes sense - what do you get from an enchantment gone wrong? A jinx! 

-1:-- Replacing Flyspell with Jinx (Post Emacs Redux)--L0--C0--2026-07-13T05:26:00.000Z

Thought my Emacs init file needs a bit of cleaning - it contains configs from maybe 5 and 6 years ago. It’s a nostalgic learning experience. More fun than I thought. More to come soon 🤓

-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-07-13T00:14:31.000Z

Irreal: Jinx For Spell Correction

Marcin Borkowski (mbork) has a nice post on the Jinx spell corrector. Mbork has tried fly-spell but found it too laggy for his purposes. That was back in the days when he had a Pentium class machine running at 100 Mhz so he thought, in view of his more modern hardware, that it was worth trying out fly-spell again.

He still found it too slow and decided to try Jinx instead. I’ve always found fly-spell snappy and I hadn’t heard of Jinx until I read Prot’s post on writing (and spell correcting) foreign languages in Emacs but mbork wasn’t satisfied until he tried Jinx. He found that it was fast enough for his use cases and decided to embrace it as his spell corrector.

He discovered that Jinx interfered with some of his work flows but it turned out to be pretty easy to fix those. You can check out mbork’s post for the details.

If you are writing in multiple languages with Emacs, then Jinx may be an excellent solution as a spell checker. If, like me, you mostly write in your native tongue, you’ll probably find fly-spell is all you need. As I said above, it’s more than fast enough for me and provides a wonderfully interactive spell correction system.

-1:-- Jinx For Spell Correction (Post Irreal)--L0--C0--2026-07-12T15:18:36.000Z

Chris Maiorana: Org mode as a gantt chart generator

The word Gantt tends to evoke project-management software with bars you can drag and a hefty license fee. The underlying idea, tasks arranged on a horizontal timeline, with owners and effort estimates, is much older and much simpler than the software around it. It is basically a chart. You can draw one with a pencil and a string for a plumb line.

Org mode already stores everything you need for a Gantt chart: a hierarchy of tasks, SCHEDULED and DEADLINE timestamps, properties for Effort and assignments to various agents. What’s missing is the picture. But org can draw the picture too. Let’s take a look and, one hopes, save you a subscription fee.

The minimum data model

For each task you want to track on the chart, add three things:

* Draft chapter 5
SCHEDULED: <2026-02-12 Thu> DEADLINE: <2026-02-26 Thu>
:PROPERTIES:
:AGENT:    Chris
:Effort:   10
:END:

* Edit chapter 4
SCHEDULED: <2026-02-10 Tue> DEADLINE: <2026-02-20 Fri>
:PROPERTIES:
:AGENT:    Chris
:Effort:   6
:END:

* Design cover art
SCHEDULED: <2026-02-15 Sun> DEADLINE: <2026-03-01 Sun>
:PROPERTIES:
:AGENT:    Maya
:Effort:   8
:END:

That is enough for a starting point. SCHEDULED is the start of the bar, DEADLINE is the end, :AGENT: is the swimlane, :Effort: is the height of the bar (or just a hover tooltip). Everything else is presentation.

Rendering the chart

I’ve included an example implementation below on how I render a nice view from this. Here’s the whole renderer, about sixty lines of elisp. Saved as gantt-render.el somewhere on my load path, I can open the project file, and run M-x org-fas-gantt-render-to-html. It walks every heading that has both a SCHEDULED start and a DEADLINE end, lays each one out as a bar scaled to the project’s overall span, and groups the bars into swimlanes by :AGENT:.

(require 'org)
(require 'ox-html)   ; for org-html-encode-plain-text

(defun org-fas-gantt--collect ()
  "Return a list of task plists from the current Org buffer.
Only headings with both a SCHEDULED and a DEADLINE timestamp
are included.  :start and :end are absolute day numbers so
they are cheap to subtract."
  (delq nil
        (org-map-entries
         (lambda ()
           (let ((start (org-get-scheduled-time (point)))
                 (end   (org-get-deadline-time (point))))
             (when (and start end)
               (list :title  (org-get-heading t t t t)
                     :agent  (or (org-entry-get (point) "AGENT") "Unassigned")
                     :effort (org-entry-get (point) "Effort")
                     :start  (time-to-days start)
                     :end    (time-to-days end))))))))

(defun org-fas-gantt--bar (task day0 span)
  "Return one HTML row for TASK, scaled against DAY0 across SPAN days."
  (let* ((left  (* 100.0 (/ (float (- (plist-get task :start) day0)) span)))
         (width (max 1.0 (* 100.0 (/ (float (- (plist-get task :end)
                                               (plist-get task :start)))
                                     span))))
         (effort (plist-get task :effort)))
    (format (concat "<div class=\"row\"><span class=\"label\">%s</span>"
                    "<span class=\"track\"><span class=\"bar\" "
                    "style=\"left:%.1f%%;width:%.1f%%\" title=\"%s\"></span>"
                    "</span></div>")
            (org-html-encode-plain-text (plist-get task :title))
            left width
            (if effort (format "Effort: %s" effort) ""))))

(defun org-fas-gantt-render-to-html (&optional file)
  "Render the current buffer's Gantt tasks to FILE as HTML.
Interactively prompts for the output file.  Returns the file name."
  (interactive)
  (let* ((tasks (org-fas-gantt--collect))
         (file  (or file (read-file-name "Write Gantt HTML to: " nil "gantt.html"))))
    (unless tasks
      (user-error "No tasks with both SCHEDULED and DEADLINE found"))
    (let* ((day0 (apply #'min (mapcar (lambda (tk) (plist-get tk :start)) tasks)))
           (dayN (apply #'max (mapcar (lambda (tk) (plist-get tk :end)) tasks)))
           (span (max 1 (- dayN day0)))
           (agents (cl-remove-duplicates
                    (mapcar (lambda (tk) (plist-get tk :agent)) tasks)
                    :test #'equal :from-end t)))
      (with-temp-file file
        (insert "<!DOCTYPE html><html><head><meta charset=\"utf-8\">\n"
                "<style>\n"
                "body{font-family:sans-serif;margin:2rem;color:#222;}\n"
                "h2{border-bottom:1px solid #ccc;margin-top:1.5rem;}\n"
                ".row{display:flex;align-items:center;margin:.25rem 0;}\n"
                ".label{width:16rem;font-size:.9rem;padding-right:.5rem;}\n"
                ".track{position:relative;flex:1;height:1.2rem;background:#f0f0f0;border-radius:3px;}\n"
                ".bar{position:absolute;top:0;height:100%;background:#4a7;border-radius:3px;}\n"
                "</style></head><body>\n"
                "<h1>Project timeline</h1>\n")
        (dolist (agent agents)
          (insert (format "<h2>%s</h2>\n" (org-html-encode-plain-text agent)))
          (dolist (task tasks)
            (when (equal (plist-get task :agent) agent)
              (insert (org-fas-gantt--bar task day0 span) "\n"))))
        (insert "</body></html>\n"))
      (message "Wrote %s (%d task%s)" file (length tasks)
               (if (= (length tasks) 1) "" "s"))
      file)))

It reads your timestamps, does a little arithmetic to turn dates into percentages, and writes plain HTML and CSS. You could extend it to color bars by status or add a “today” line in an afternoon.

You might be wondering why HTML. I love HTML and CSS. It’s simple, and anyone can read it. You can easily throw it up to a server and share it with anyone capable of opening a web browser.

You may prefer a different delivery format. Or, importantly, your stakeholders and team might prefer a different format. That’s fine. The point here is that from org mode you can generate any format you want.

The renderer doesn’t matter as much; the point is that the source of truth is still the .org file. You can change the renderer next year and your project data is pristine.

Here’s a picture of my Gantt chart for a fake video game release. This was totally built from org mode and then exported to HTML. What you see is the browser rendering:

Now that’s a nice visual indicator of progress that costs you and your team nothing, but looks really fancy.

Keeping the team on schedule

If you are coordinating with people who don’t use Emacs, which is almost everyone, the workflow is:

  1. You maintain the org file as the planning document.
  2. You export to HTML (or PDF, or a quick PNG) and share it to a central location.
  3. Updates come back as comments or from a meeting; you transcribe them into the org file.

It may sound inefficient, but in practice it’s the same number of steps as maintaining a separate Asana board, minus the SaaS subscription, plus the fact that your project plan now lives next to your notes and your agenda, and you’re not locked into a proprietary format.

If this post saved you a SaaS subscription or two, consider tossing a few dollars in the tip jar. It goes straight back into writing more of these.

The post Org mode as a gantt chart generator appeared first on Chris Maiorana.

-1:-- Org mode as a gantt chart generator (Post Chris Maiorana)--L0--C0--2026-07-12T15:01:11.000Z

Srijan Choudhary: 2026-07-12-001

#TIL that #Emacs also has a Global Mark Ring:

the global mark ring records a sequence of buffers that you have been in, and, for each buffer, a place where you set the mark

For programming buffers / projects, I've been using the xref stack to go back/forward when jumping around. But the global mark ring is super useful as a general purpose tracker of my context jumps.

By default, C-x C-<SPC> jumps back. There is no forward like xref-go-forward, but it' a ring so it's possible to go around. Or there's always consult-global-mark to show the marks in a list and select from it.

Other helpful links:

-1:-- 2026-07-12-001 (Post Srijan Choudhary)--L0--C0--2026-07-12T07:50:00.000Z

Protesilaos: Emacs: doric-themes version 1.2.0

These are my minimalist themes. They use few colours and will appear mostly monochromatic in many contexts. Styles involve the careful use of typography, such as italics and bold italics.

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

Below are the release notes.


Version 1.2.0 on 2026-07-12

Two new themes

doric-tiger is a light theme, while doric-lion is dark. Both have warm colours.

Org agenda events are easier to spot

Events are entries which have an active timestamp but not a SCHEDULED or DEADLINE keyword associated with it. Those are now rendered in italics in addition to the faint foreground they already had (the faint foreground is there because an event is not as important as a

Support for vc-dir-key-binding-hint-label (Emacs 32)

This concerns a new option for VC Dir buffers to display their available key bindings. The face applies to the additional headings. They should now look like all the other headings in those buffers in the interest of stylistic consistency.

Some package.el faces stand out more

Those are present in the buffer that M-x list-packages produces. They concern certain status indicators and fit in better with the rest of the design.

The nobreak-space face is now underlined

This is one way to make that character visible. It is useful to know that a space is not the regular space. For example, in French orthography we are expected to include non-breaking spaces between the quotes and the words like « Protesilaos ». Whether you actually follow that guideline is another discussion—I happily ignore it.

-1:-- Emacs: doric-themes version 1.2.0 (Post Protesilaos)--L0--C0--2026-07-12T00:00:00.000Z

Irreal: Highest Org Priority

If you’re an Org user, you probably know that you can assign a priority to TODO items. By default, the priorities are A, B, and C but with a bit of trickery you can extend the ranges to 0–9 and A–Z.

I never use priorities but Raymond Zeitler does and after figuring out the above trickery he thought it would be useful to have an ultimate priority a sort of “drop everything and do this immediately” priority. He decided to make this priority 100 times the highest priority and to designate it with !.

The solution wasn’t straightforward but it wasn’t too difficult either. The thing about Emacs is that it’s easy to check the source code to see how things are done. It turns out that Org has a org-priority-get-priority-function variable that contains a function that Org calls to get the priority from the priority cookie. Zeitler wrote his own version to check for a ! and return the high priority if it’s found. If not, then it calls a slightly modified version of the normal function.

As I say, I don’t make use of the priority mechanism and have no idea how many people do but if you’re one of them and are looking for a way to specify an extraordinary priority, take a look at Zeitler’s post. You can cut and paste his code if you’re interested.

-1:-- Highest Org Priority (Post Irreal)--L0--C0--2026-07-11T15:10:09.000Z

Marcin Borkowski: Fast spell-checking in Emacs with Jinx

Emacs has had a spell-checker for a very long time. In fact, I’ve been using ispell-word for at least several years now, as I have mentioned for example here. However, I’ve only used Flyspell mode once, over two decades ago, when I tried it out on a machine with maybe 8MB RAM and a Pentium-class processor clocked at about 100MHz and decided that it’s too slow to be really useful. Well, things have changed a bit since then, haven’t they?
-1:-- Fast spell-checking in Emacs with Jinx (Post Marcin Borkowski)--L0--C0--2026-07-11T06:52:02.000Z

Aimé Bertrand: Preserving email dates when moving messages in Mu4e

The problem

My email setup is the usual text based machinery I keep writing about: Mu4e with mu, isync (mbsync) and msmtp. It works beautifully – until one day it did not.

When I moved emails around in Emacs – with mu4e-headers-mark-for-move, mu4e-headers-mark-for-refile or other command – the date of the moved email changed. A mail from last March would suddenly float to the top of the mailbox as if it had just arrived.

The diagnosis

The Date: header inside the message is never touched – that is why Mu4e always looks correct, it shows the date parsed from that header.

What changes is the arrival date: the IMAP “internal date” the server stores separately from the body. That is what Mail.app shows and sorts by (“Date Received”).

The culprit is this setting, which I need to avoid duplicate-UID errors with mbsync:

(setq mu4e-change-filenames-when-moving t)

With it on, Mu4e does not just move the file on refile – it rewrites the message as a brand-new Maildir file, as if freshly delivered, resetting its arrival time to now. mbsync then pushes that up and the server stamps the new internal date. Mail.app is exempt because it moves server-side via IMAP MOVE, which preserves the internal date.

First fix: CopyArrivalDate (necessary, not sufficient)

The fix lives in mbsync, not Emacs. In ~/.maildir/mbsyncrc, once at the top so it applies to every channel:

CopyArrivalDate yes

This propagates the arrival time with the message instead of letting the server stamp a fresh one. But on its own it was still wrong – because mbsync reads that arrival time from the file’s modification time (mtime), and Mu4e already reset the mtime to now when it rewrote the file. So CopyArrivalDate faithfully copies the wrong value.

The real fix: restore the mtimes before syncing

I keep mu4e-change-filenames-when-moving at t and, right before each sync, walk the Maildir, read each message’s Date: header, and set the file’s mtime to match. Then CopyArrivalDate yes carries the correct value up.

Thank god for Claude Code for the help with the Python script.

~/.maildir/fix-maildir-mtimes.py:

#!/usr/bin/env python3

"""Restore Maildir mtimes from each message's Date: header, so that
mbsync's CopyArrivalDate propagates the correct received date."""

import os
import sys
import time

from pathlib import Path
from email.parser import BytesParser
from email.utils import parsedate_to_datetime

maildir = Path(os.path.expanduser(sys.argv[1] if len(sys.argv) > 1 else "~/.maildir"))
window  = float(sys.argv[2]) if len(sys.argv) > 2 else 2 * 86400
cutoff  = time.time() - window if window else 0

for f in maildir.rglob("cur/*"):
    try:
        st = f.stat()
        if window and st.st_mtime < cutoff:
            continue
        with open(f, "rb") as fh:
            msg = BytesParser().parse(fh, headersonly=True)
        dt = parsedate_to_datetime(msg.get("Date"))
        if dt is None:
            continue
        ts = dt.timestamp()
        if abs(st.st_mtime - ts) > 1:
            os.utime(f, (ts, ts))
    except Exception:
        continue

Wrapping it in front of mbsync

Rather than hooking “into” mbsync, I put a wrapper in front of it. This is ~/.maildir/sync-mail.py (yes, the .py on a bash script is a small lie – it started life in Python):

#!/usr/bin/env bash

~/.maildir/fix-maildir-mtimes.py "$HOME/.maildir"

exec mbsync "$@"

The fixer runs against the whole ~/.maildir root, covering every account in one pass. $@ is forwarded straight to mbsync, so I can pass a single channel name or --all. Make both files executable:

chmod +x ~/.maildir/fix-maildir-mtimes.py ~/.maildir/sync-mail.py

Hooking it into Mu4e

Mu4e simply runs whatever is in mu4e-get-mail-command, so I point that at the wrapper. By default it syncs everything:

(customize-set-variable 'mu4e-get-mail-command
                        (concat (executable-find "~/.maildir/sync-mail.py") " --all"))

And for the times I want to sync a single account, timu-mu4e-get-mail lets me pick it first:

(defun timu-mu4e-get-mail ()
  "Select the Account before syncing.
This makes the syncing of mails more flexible."
  (interactive)
  (let ((mu4e-get-mail-command
         (concat
          "~/.maildir/sync-mail.py "
          (completing-read
           "Which Account: "
           '("icloud" "aimebertrand" "moclub" "--all")))))
    (mu4e-update-mail-and-index t)))

(keymap-set mu4e-headers-mode-map "M-r" #'timu-mu4e-get-mail)
(keymap-set mu4e-main-mode-map    "M-r" #'timu-mu4e-get-mail)
(keymap-set mu4e-view-mode-map    "M-r" #'timu-mu4e-get-mail)

Because sync-mail.py runs the fixer before exec mbsync, every sync I trigger with M-r repairs the dates first – asynchronously, so Emacs never blocks. If you also sync outside Emacs (cron, launchd, a terminal), point those at sync-mail.py too, or a background sync could re-clobber a freshly moved message.

Conclusion

Three moving parts: CopyArrivalDate yes in mbsyncrc, fix-maildir-mtimes.py to restore the mtimes, and sync-mail.py wrapping both as the single entry point behind mu4e-get-mail-command.

Two honest caveats: it only fixes messages going forward – already-moved mails keep their overwritten server date – and some servers may stamp their own date on upload regardless. For my mailboxes it works wonderfully though. Yeah!!!

-1:-- Preserving email dates when moving messages in Mu4e (Post Aimé Bertrand)--L0--C0--2026-07-10T22:00:00.000Z

Raymond Zeitler: One Hundred Times the Highest Priority

In the previous post,1 I was disappointed to learn that valid priority characters are limited to 0–9 and A–Z. I thought it would be neat if the exclamation mark ("!") could represent the ultimate priority, a sort of "drop everything and do this immediately" kind of priority.

It turns out that you can use "!" for this purpose, regardless of which priority characters you've configured. The trick is to write a function and assign it to org-priority-get-priority-function.

The function (which I named org-get-cust-priority) simply looks for "!" in the priority cookie and returns one hundred times the value that would be returned by the highest valid priority. If it doesn't find "!" it simply calls a modified version of the usual org-get-property function,2 which I named org-get-std-priority. The two functions are shown below.3

(defun org-get-cust-priority (s)
  "Return 100 times the highest priority when S contains
a priority cookie with `!'.  Otherwise call the usual
`org-get-property' function. Intended to be assigned to
`org-priority-get-priority-function'."
  (interactive)
  (if (not (functionp org-priority-get-priority-function))
      (org-get-std-priority s)
    (string-match ".*?\\(\\[#\\(!\\)\\] ?\\)" s)
    (if (string-equal (match-string 2 s) "!")
        (* 100000 (abs (- org-priority-lowest org-priority-highest)))
      (org-get-std-priority s))))

(defun org-get-std-priority (s)
  "Find priority cookie and return priority.
S is a string against which you can match `org-priority-regexp'.
Same function as `org-get-priority' sans test for a custom
function in `org-priority-get-priority-function'."
  (save-match-data
    (if (not (string-match org-priority-regexp s))
        (* 1000 (- org-priority-lowest org-priority-default))
      (* 1000 (- org-priority-lowest
                 (org-priority-to-value (match-string 2 s)))))))

The number that Org uses to sort on priority depends on the values of org-priority-lowest and org-priority-highest,4 which are "C" and "A" by default. In the default configuration, the highest priority has a numeric value of 2000, while the lowest has a value of 0. If the highest and lowest priority characters are 1 and 9, respectively, the highest priority has a value of 8000.

You know you need this if you have about a dozen items on your agenda and half of them are prioritized A.


1 https://ray-on-emacs.blogspot.com/2026/07/numeric-priorities-in-org-mode.html
2 Modified just to prevent recursion.
3 I know this code isn't particuarly elegant. I could have made custom variables for "!" and the priority multiplier, for example. I'd like to use it for a while before deciding whether it's worth investing any more effort. I do welcome suggestions on how to improve.
4 Or rather, the difference between the highest and lowest priorities.

-1:-- One Hundred Times the Highest Priority (Post Raymond Zeitler)--L0--C0--2026-07-10T18:45:12.923Z

Charlie Holland: A Tree and a Server Walk Into a Core&#x2026;

1. TLDR   tldr

july-emacs-carnival-banner.jpeg

Two technologies have endowed every editor with IDE superpowers this past decade: tree-sitter, a generic incremental parser that gives your editor a live syntax tree of your code, and the Language Server Protocol (LSP), a generic client-server protocol that gives your editor code intelligence features like linting, type-checking, auto-completion, and code navigation. Each one replaces an M×N fan-out of per-language, per-editor hacks with a single, language- and editor-agnostic protocol. Emacs has merged support for both into its core, and shipped them in Emacs 29.1 (three years ago this month!). This post explains what each one does, visualizes a real-life tree-sitter parse tree, and recounts how both technologies made their way upstream into the glorious Emacs core.

An interactive visualization of the syntax tree tree-sitter builds from a small Python module — hover any node for details, click to zoom into a subtree. Two more of these live further down the post.

2. About   emacs carnival programming

This is my entry for July's Emacs Carnival, hosted by Andy over at Plain DrOps. The theme is Programming, i.e. Emacs as a programming environment. I've done these before (May was about deep Emacs patterns, June about Emacs teaching you Emacs), and this month I want to push back on my least favorite meme: that Emacs is arcane; rusty and dusty; old news; the "Editor for Middle Aged Computer Scientists".

Emacs has been under active development by a growing number of contributors, and is surprisingly modern. As two language-agnostic protocols for IDE features arose (tree-sitter for syntax and LSP for semantics), Emacs developers quickly merged support for both into core. More interestingly, they made them usable in the editor by integrating them into the Emacs machinery that predates them by decades.

The first suggested topic in the carnival is "Language Specific Setups" and, given the utility of tree-sitter and LSP, I couldn't help but misinterpret this suggestion intentionally and create a post on my "Language Generic Setup".

3. The Combinatorial Woes of M×N   leverage architecture

Traditionally, in most IDEs "support for language X" meant one implementation per editor, per language.

Syntax highlighting was a mixed bag of regular expressions that approximated the language's grammar.

Navigation ("jump to the function I'm in") was basically regex guesswork. Completion and go-to-definition, if they were even available, came from language-specific plugins of wildly varying quality.

With M editors and N languages, the world had to write (and maintain, and debug) M×N of these plugins. The modern fix is the classic programmer's reflex of establishing a generic layer, or protocol, in the middle, and paying a much more affordable M+N tax instead (the framing the LSP community itself uses).

This reflex is older than any editor in the diagram below, by the way. It's the UNCOL argument from 1958, which suggested putting one universal intermediate language between M source languages and N machines, and M×N compilers become M+N. UNCOL itself never shipped, but its argument became the standard justification for compiler intermediate representations, and the argument is just as valid for editor tooling.

graph TB
    subgraph before["Before: every editor reimplements every language (M × N)"]
        direction LR
        A1[Emacs] --- B1[Python]
        A1 --- B2[Rust]
        A1 --- B3[TypeScript]
        A2[Vim] --- B1
        A2 --- B2
        A2 --- B3
        A3[VS Code] --- B1
        A3 --- B2
        A3 --- B3
    end
    subgraph after["After: one generic layer in the middle (M + N)"]
        direction LR
        C1[Emacs] --> MID(("tree-sitter<br/>+ LSP"))
        C2[Vim] --> MID
        C3[VS Code] --> MID
        MID --> D1[Python grammar & server]
        MID --> D2[Rust grammar & server]
        MID --> D3[TypeScript grammar & server]
    end
    before ~~~ after

Tree-sitter provides the answer to "what is this text, structurally?" — it can tell the editor that characters 4 through 7 are a function name. LSP provides the answer to "what does this text mean, in this project?" — it knows that the function is defined in another file, is called from twelve places, and is missing an argument, etc…. These two layers solve "syntax and semantics" in a graceful, reusable way.

4. Tree-sitter: a Fresh Parse Tree on Every Keystroke   treesitter parsing

Tree-sitter, started by Max Brunsfeld while working on Atom at GitHub, is a parser generator plus an incremental parsing library. Rather than implementing a boat-load of regular expressions, you provide tree-sitter with a language's grammar, tree-sitter compiles it into a small C library, and any tool can then parse that language. Each grammar is its own small repository. The canonical ones live under the tree-sitter GitHub org (e.g. tree-sitter-python, the grammar behind every tree in this post), and the community maintains a list of hundreds more.

Compilers typically wouldn't work well for providing live feedback in the text-editing use case, but there are two properties that make tree-sitter performant and robust enough to be editor-grade:

  1. It's incremental. When you type a character, tree-sitter doesn't re-parse the file, but rather patches the existing tree in microseconds. The result is that you have an up-to-date parse tree, immediately, on every keystroke.
  2. It's error-tolerant. Code being edited is malformed almost all of the time, because you are mid-keystroke more often than not. Tree-sitter handles this gracefully and keeps the rest of the tree intact instead of failing at the first syntax error.

These aren't new ideas so much as ideas finally made practical. The figure below comes from the 1998 Berkeley dissertation that inspired tree-sitter's development, and it shows the incremental trick. As an edit arrives, the parser restores a consistent tree by creating just two new nodes (black) and adjusting a handful of others (gray). Every other node in the tree is reused as-is. This is a phenomenal paper, by the way, I highly recommend you read it.

wagner-incremental-parsing.png

Figure 1: From Tim Wagner's 1998 Berkeley dissertation, Practical Algorithms for Incremental Software Development Environments (his Figure 4.3), the work tree-sitter's incremental parsing builds on. Dashed lines are the paths walked to reach the edit site.

4.1. Looking at Concrete Trees

You'll most often hear these trees called Abstract Syntax Trees (ASTs). Tree-sitter's own docs use the term, but strictly speaking, tree-sitter produces a concrete syntax tree (CST). While an AST discards everything that stops mattering once the structure is known (keywords, parentheses, punctuation), a CST keeps every last token of the source. Editors need the concrete version, because you can't highlight a def you've thrown away. You'll see this in the charts below, where the keyword and punctuation tokens appear in the tree, in gray.

Here I show a few examples, in order of increasing complexity, of the syntax trees that tree-sitter actually produces from Python code.

There are many ways to visualize these trees. Mine are drawn with plotly as an icicle chart. Each tile is a node in the tree. A tile's width is proportional to the amount of source code the node covers, and its children sit beneath it. Color encodes the node's role in the code. These charts are interactive and fun to play with (which is why I created three examples!). You can hover over any tile to see the node type and the exact source bytes it covers, and you can click a tile to zoom into its subtree (the breadcrumb bar that appears on top zooms you back out).

4.1.1. Fibona-tree

Here's everyone's favorite, flogged-to-death fibanacci function:

def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

Here is the tree that tree-sitter produces.

Notice how the tree covers the text completely. Even def and each ( get a node — remember, that's the concrete in "concrete syntax tree". Any syntactical question or request the editor has ("am I inside a function?", "select the whole if statement", "highlight this as a parameter") can be answered by querying this tree.

Something I love about Emacs's support for this is that you don't need my chart to see these trees concretely. In Emacs 29+, you can open any *-ts-mode buffer and run M-x treesit-explore-mode. Then Emacs shows you this same live tree and highlights the node at point as you move. In this way, and very much in the spirit of last month's post on discoverability and introspection, Emacs can show you its own parse trees.

4.1.2. Scaling Up: quicksort

Here's a more complicated tree for a quicksort algorithm, implemented using list comprehensions, which are not as intuitively tree-like:

def quicksort(xs):
    if len(xs) <= 1:
        return xs
    pivot, rest = xs[0], xs[1:]
    lo = [x for x in rest if x < pivot]
    hi = [x for x in rest if x >= pivot]
    return quicksort(lo) + [pivot] + quicksort(hi)

There are two things worth noticing as you play with this tree. First, each list_comprehension is a single expression node that contains an entire clause structure (a for_in_clause and an if_clause). In this way, the editor's request to "select the whole comprehension" is one node-hop, rather than a text scan for matching brackets. Second, notice the tree got wider but not much deeper than fib's. Expression-heavy Python fans out horizontally.

4.1.3. Zooming Out: a Whole Module

Here's the typical form of an entire (albeit short) module in Python.

import math
from dataclasses import dataclass


@dataclass
class Point:
    """A point in the plane."""

    x: float
    y: float

    def distance_to(self, other):
        return math.hypot(self.x - other.x, self.y - other.y)


def centroid(points):
    n = len(points)
    return Point(
        sum(p.x for p in points) / n,
        sum(p.y for p in points) / n,
    )


def nearest(points, target):
    return min(points, key=target.distance_to)

Look at the second row and you will see import_statement, import_from_statement, decorated_definition, function_definition, function_definition. That row is your file outline, and it's what Emacs tools like imenu, which-function-mode, and code folding query from the tree. Every structural feature your editor offers over this file starts from some node in this picture. When your editor "expands the selection to the enclosing expression", it is literally walking one row up this diagram.

This brings us to the tools that do exactly that.

4.2. What Emacs Does with the Tree   fontlock navigation

With a live tree in hand, the classic per-language hacks become generic one-liners over nodes:

  • Highlighting (font-lock) becomes precise. Regexes were always fine when the answer sits inside the match (def foo is a definition, the def is right there), but they fail when it doesn't. Highlighting the uses of a parameter inside a function body, or distinguishing a C declaration from a call, requires structure and scope, and matching nested structure is provably beyond regular expressions. Classic highlighters approximated this detection with stacks of context rules, but with a tree, those distinctions are just node types.
  • Navigation and selection by type: you can easily jump to the enclosing function, next class, or previous statement, because the tree knows the exact bounds of everything, whether you're reading finished code or still typing it out.
  • Indentation, folding, imenu all subsist on the same structure, the syntax tree.

The showcase for navigation is Mickey Petersen's Combobulate, built on Emacs's built-in treesit library. It gives you structural editing across Python, TypeScript, Go, YAML, and more. It ships with commands for expanding the selection node-by-node, dragging whole siblings up and down, hopping between parameters or list elements, placing cursors on every matching node, and many more. It also has wonderfully guiding transient menus, making Combobulate a phenomenal way to learn a language's structure too.

Combobulate: expand the selection node-by-node

Expand the selection node-by-node — one row up the icicle charts above

Combobulate: navigate between sibling nodes in JSX

Hop between sibling nodes (here in JSX)

Combobulate: drag whole sibling nodes up and down

Drag whole siblings up and down

Combobulate: clone the node at point

Clone the node at point

Combobulate: place cursors on every matching node

Place cursors on every matching node

Combobulate: splice a node up into its parent

Splice a node up into its parent

Figure 2: Combobulate in action (gifs from the Combobulate repo). Click any gif for a full-screen view.

5. LSP: Tapping a Compiler's Brain   lsp architecture

Tree-sitter knows your file's structure, but it doesn't know your project. "Where is this function defined?", "where is it called?", "what's this variable's type?". Answering those questions requires language analysis, the front half of a compiler (front half meaning the parsing, name resolution, and type checking, without the code generation).

The Language Server Protocol, published by Microsoft in 2016 for VS Code, solves this with the same M+N trick, outside of the editor. The language and project intelligence lives in a standalone server process (pyright, rust-analyzer, clangd, gopls, …), and the editor talks to it over JSON-RPC. Your editor is a client of the language server.

sequenceDiagram
    participant E as Emacs (eglot)
    participant S as Language server (e.g. pyright)
    E->>S: initialize (what can you do?)
    S-->>E: capabilities (completion, definitions, rename, ...)
    E->>S: textDocument/didChange (user typed something)
    S-->>E: textDocument/publishDiagnostics (line 12: type error)
    E->>S: textDocument/definition (M-. on a symbol)
    S-->>E: Location (utils.py, line 48)

Any editor that can speak this protocol (the Language Server Protocol) is provided with completion, go-to-definition, find-references, rename, hover documentation, and on-the-fly diagnostics for every language that has a server. Because the language servers follow a protocol, integrating these intelligence features across languages is easy. The server is usually written by the people who know the language best, often the compiler team itself.

5.1. Eglot: LSP, the Emacs way   eglot builtins

Emacs's built-in client is João Távora's Eglot ("Emacs polyGLOT", started in 2018). What makes Eglot special is how it uses Emacs machinery that has already existed for decades to integrate with these language servers.

LSP capability Served via
Go to definition xref
Hover docs eldoc
Diagnostics flymake
Completion completion-at-point
Symbol outline imenu
Project boundaries project.el

M-x eglot in a programming buffer is genuinely all it takes to start using a language server, and these Emacs tools you've probably already used will work, but with richer information provided by the language servers.

The coolest part of this integration story is that Emacs honored a modern protocol by adopting it, and Eglot honored Emacs by expressing that protocol through Emacs's native idioms. Adopting the power of LSP didn't mutate Emacs in any significant way. It just added one package integrating LSPs with the existing Emacs machinery.

6. The Road Into Core   history upstream

Both tree-sitter and LSP support followed a similar path into Emacs, where external experimentation proved the concept and value, and then the core maintainers did the hard work of making it native.

timeline
    1998 : Wagner's Berkeley dissertation works out incremental parsing for editors
    2013-2017 : Max Brunsfeld develops tree-sitter at GitHub for Atom
              : Microsoft publishes LSP (2016) — lsp-mode brings it to Emacs (2017)
    2018-2020 : Eglot appears as an external package (João Távora)
              : elisp-tree-sitter binds tree-sitter to Emacs via dynamic modules (Tuấn-Anh Nguyễn)
    2022 : October — Eglot merged into Emacs core
         : November — Yuan Fu's native tree-sitter integration (treesit.el) merged
    2023 : July — Emacs 29.1 ships both, plus python-ts-mode, c-ts-mode, and friends

Tree-sitter merged in November 2022, weeks after Eglot's October merge, and Emacs 29.1 (July 2023) shipped them together with a family of *-ts-modes and M-x treesit-install-language-grammar. Emacs 29.1 was a landmark release because it delivered modern, language-agnostic syntax highlighting and code semantics in one fell swoop.

7. Try It   handson

On Emacs 29 or later, no packages required:

;; fetch + compile the grammar
M-x treesit-install-language-grammar RET python RET
;; tree-sitter highlighting
M-x python-ts-mode
;; watch the live parse tree
M-x treesit-explore-mode
;; connect to a language server
M-x eglot

(For eglot you'll need a server on your PATH, e.g. pip install pyright. To make the ts modes the default, see major-mode-remap-alist.)

treesit-install-language-grammar clones a grammar repository and compiles it for you. If the language isn't already listed in treesit-language-source-alist, Emacs prompts for the repository and pre-fills the canonical guess, https://github.com/tree-sitter/tree-sitter-<language>. For anything more exotic, pick a repository from the community's list of parsers.

-1:-- A Tree and a Server Walk Into a Core&#x2026; (Post Charlie Holland)--L0--C0--2026-07-10T18:36:07.000Z

Charles Choi: In Emacs, Everything Looks Like a Service

A common refrain is that Emacs is an operating system (OS). This isn’t true, but what invites comparison to an OS is its ability to orchestrate applications and utilities above the OS kernel level. The diagram below suggests a truer picture of how Emacs’ relates to an OS and its capabilities.

img

Emacs’ built-in access to OS system services (file system, network, etc.) coupled with the ability to run other programs makes it routine to improvise client behavior within it. Because of this, Emacs users are able to accomplish many of their computing needs from the different client modes that have been made for it. This gives credence to the notion of “living only in Emacs.”

In this post, we’ll examine some of the ways Emacs lets you build a client. By the end of this post, you’ll hopefully be convinced that from within Emacs, everything looks like a service.

Client-Server Model

Let’s first provide some definitions.

The Client–Server model is a common computer interaction pattern where a task is partitioned between the provider of a resource (the service) and the requester of that resource (the client). The client issues a request to the server, and the server in turn returns a response as shown in the diagram below.

img

Depending on the implementation, the transaction (request + response) can occur over a network or be local to a system. Client-server models using a network has been most elaborated upon with REST-style software architectures. Shown in the sequence diagram below is a common implementation pattern for REST-style client server architecture.

img

Emacs as a Client

From the diagram above, there are three concerns the client is typically responsible for:

  • UI: User interface (if any).
  • Client Edge: Sub-system concerned with communication with the service. For networked clients, this is the network sub-system.
  • Local Database: Representation of data that is exchanged or synchronized with the server. How this data is managed is up to the implementation requirements.

For the above concerns, Emacs provides numerous libraries both built-in and third-party which can implement a client. Listed below are some built-in libraries with their respective links for further reading:

Requirements dictate the amount of complexity required to implement the Emacs client. If there is an existing command line utility that can do the “heavy lifting”, said utility can be reframed as a “service” that can be accessed via a shell call.

img

Elisp

All the libraries mentioned above are accessed through the Emacs Lisp (Elisp) programming language. Elisp is a dynamic programming language which allows for a high degree of improvisation during run-time. This capability allows for complex orchestration of any behavior that is available to Emacs, from Elisp functions to shell commands.

Example wttr.in client

wttr.in is a console-oriented weather forecast web-service. It supports JSON output so we can build an Emacs wttr command which will prompt for a location, make the HTTP request, process the JSON response and display the result in the mini-buffer.

The top-level command wttr is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(defun wttr (location)
  "Show weather conditions for LOCATION from `https://wttr.in' in mini-buffer.

Result is also stored in `kill-ring'."
  (interactive "sWhere (default: local): ")

  (condition-case err
      (let* ((url (wttr--request-url location))
             (jsondb (fetch-json-as-hash-table url))
             (msg (wttr--report-message jsondb)))
        (kill-new msg)
        (message "%s" msg))

    (error (message "ERROR: %s" (cdr err)))))

The wttr.in URL is constructed by the function wttr--request-url shown below.

1
2
3
4
5
6
7
(defun wttr--request-url (location)
  "Construct wttr.in URL with LOCATION."
  (let* ((base-url (url-generic-parse-url "https://wttr.in"))
         (encoded-location (string-replace " " "+" location))
         (query (format "/%s?0&format=j1" encoded-location))
         (_dummy (setf (url-filename base-url) query)))
    (url-recreate-url base-url)))

We can subsequently pass that URL into fetch-json-as-hash-table which does the heavy lifting of retrieving the URL and parsing the JSON response into an Elisp hash-table.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defun fetch-json-as-hash-table (url)
  "Fetch URL with expected JSON response and return a `hash-table'."
  (let ((data-buffer (url-retrieve-synchronously url)))
    (if (not data-buffer)
        (error "Failed to fetch data from %s" url)
      (unwind-protect
          (with-current-buffer data-buffer
            ;; Move point past the HTTP metadata headers
            (goto-char url-http-end-of-headers)
            ;; Parse the remaining JSON buffer into a hash-table
            (json-parse-buffer :object-type 'hash-table))
        ;; Always kill the downloaded network buffer to prevent memory leaks
        (kill-buffer data-buffer)))))

Finally we can extract the desired values from the JSON response (jsondb) to populate the message that will sent to the mini-buffer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(defun wttr--report-message (jsondb)
  "Generate weather report message from JSONDB."
  (let* ((area-buflist ())
         (nearest-area
          (wttr--get-first jsondb "nearest_area"))
         (area-name
          (map-elt (wttr--get-first nearest-area "areaName") "value"))
         (region
          (map-elt (wttr--get-first nearest-area "region") "value"))
         (country
          (map-elt (wttr--get-first nearest-area "country") "value"))

         (current-condition (wttr--get-first jsondb "current_condition"))
         (temp_c (map-elt current-condition "temp_C"))
         (temp_f (map-elt current-condition "temp_F"))

         (weather-description
          (map-elt
           (wttr--get-first current-condition "weatherDesc") "value")))

    (mapc (lambda (x)
            (if (and x (not (string-equal x "")))
                (push x area-buflist)))
          (list area-name region country))

    (format "%s: %s°C, %s°F %s"
            (string-join (reverse area-buflist) ", ")
            temp_c
            temp_f
            weather-description)))

wttr.el source

Closing Thoughts

At this point, hopefully you are convinced of the title assertion that from Emacs, everything looks like a service. Furthermore, many of the APIs offered by Emacs work at a high-level of abstraction. Consider that the lines of code for wttr.el weighs in at 67. (Result using the cloc utility.)

If that’s too much, then imagine an alternate implementation where the actual network request and JSON processing is done in a Python script called weather. Then the Elisp command to invoke it is just the code shown below.

1
2
3
4
5
6
7
8
9
(defun weather (location)
  "Call weather script with LOCATION and show result in minibuffer."
  (interactive "sWhere (default: local): ")

  (let* ((weather-cmd "weather")
         (cmd (if location (format "%s %s" weather-cmd location) weather-cmd))
         (result (shell-command-to-string cmd)))
    (kill-new result)
    (message result)))

With the above implementation, the shell command becomes effectively the “service” to make a request to.

As Elisp is a dynamic programming language, it can allow for integration of Elisp libraries with command line utilities in an improvised fashion.

This capability is compelling to users who recognize the opportunities it can offer.

-1:-- In Emacs, Everything Looks Like a Service (Post Charles Choi)--L0--C0--2026-07-10T06:40:00.000Z

Mike Hostetler: How Emacs Uses Nix

It's been a while since I wrote for the Emacs Blog Carnival but I have missed it. I decided to dip my toes back in when I saw July 2026's theme - Programming.

I started thinking about how my Emacs configuration for development isn't that special and then I realized that the centerpiece of my environment, direnv and Nix, is something that is worth talking about. I have a whole post on setting them up so this will continue with what that post glossed over: How does this work with Emacs?

The idea started off with the fact that I don't want separate configurations for my CLI and Emacs. I want to just configure it once and things will just work in both. Since getting my shell to see my Emacs configuration seems like a waste of time, I chose to let Emacs read what the shell was doing. What I ended up with is my system-wide Emacs reading the same configuration that my shell does, and then by default, Emacs uses the same paths and interpreters. I don't need a .dir-locals in any project folders to set special paths. It just works.

The center of this is the envrc package. It runs direnv when each buffer is opened so you have the right configuration when you need it. I don't think the envrc maintainers sought to support Nix in their package, but because it supports everything direnv does seamlessly, it supports Nix by default.

The envrc configuration in Emacs is easy:

(use-package envrc
  :hook (after-init . envrc-global-mode))

Then I make sure I set up my .envrc and shell.nix files like I did in my old post and then make sure I run direnv allow and… that is it. Whenever I open up a file in that project after that inside Emacs, envrc will setup the environment automatically. It finds the right interpreter, LSP engine, etc. I don't have to do anything.

This is especially powerful for LSP. This is an actual shell.nix file for an old Python project I maintain:

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = [
    pkgs.python311
    pkgs.poetry
    pkgs.basedpyright
  ];


}

I install the LSP server – Basedpyright in this case – within the project instead of globally. That way I know that it has the right version of Python and can get its dependencies and then Eglot starts up just fine. When I need to work on this project, I just cd ~/Projects/project-a and then my shortcut for helm-projectile-switch-project and choose the project folder in Emacs.

For completion's sake, here is the .envrc for the same project:

use nix

The magic is that this is all local to the buffers. I can have two projects open at once, each pinned to a different Python, and Emacs just does the right thing in each.

So in project-a it looks like:

;; a buffer under ~/Projects/project-a
M-! which python
/nix/store/j0r98nqnq891c08q2dns661qnz42fz49-python3-3.11.15-env/bin/python

Then I switch to project-b

;; a buffer under ~/Projects/project-b
M-! which python
/nix/store/jn7s7larin2h9yz8fn9g0zkz5asg8i4d-python3-3.13.12-env/bin/python

Same Emacs, same command, two different interpreters because I'm in buffers in two different projects. I never touched exec-path or dropped a .dir-locals.el in either project. Each buffer is running under its own project's .envrc. Of course if I add environment variables to my .envrc file or change that in any way, I have to do another direnv allow in that folder, or in Emacs run envrc-allow in a buffer in that project.

On the other hand, I can change the shell.nix file as much as I want and it will be automatic when I reload the buffer.

Both my shell and Emacs use the same configuration, and I don't even think about it anymore. It just is so seamless and automatic.

-1:-- How Emacs Uses Nix (Post Mike Hostetler)--L0--C0--2026-07-10T05:00:00.000Z

Raymond Zeitler: Numeric Priorities in Org Mode

Org mode is configured "out-of-the-box" with these three levels of priority: A, B, C, where A is the highest priority. However, this can be changed. You can use numbers to indicate priority, such as 1 (highest) to 10 (lowest) as shown in the documentation.1

If you specify priorities from 65 to 90, Org will interpret the numbers as ordinal values of ASCII characters and give you "A" to "Z." If you try to be clever and specify priorities as 65 to 91 (91 is the decimal ASCII code for left square bracket character) you'll be disappointed that when you lower the priority from "Z," you don't get "[." (I was disappointed, anyway.)

You can define priorities from 10 to 99, for example. But if you try to get to a priority higher than 10 using org-priority-up, Org will clear the priority cookie. However, you can manually enter a priority of 0 to ensure the item appears above all other items. (Please see Figure 1, below.) Thus, a priority value of 0 can be your "stat" item. It would be super neat if Org could understand that ASCII characters below 0 (such as the exclamation mark) are an even higher priority than 0. Unfortunately, it doesn't even recognize [#!] as a priority cookie.

A large priority span seems like it would be useful. For example, you might have all utility bills set from 50 to 59, and shopping for birthday or anniversary gifts from 10 to 19. However, in order to go from a priority of 50 to 10, you'd have to press the Up Arrow key forty times. If you think you can invoke M-4 0 M-x org-priority-up to go quickly from 50 to 10, you'll be disappointed (again). The function org-priority-up doesn't accept a prefix argument.2

How do you use priority in Org?


1 Org Manual -- Priorities

2 But you can record a macro that consists of a single S-<up> key press and then play it back with a prefix argument of 39.

-1:-- Numeric Priorities in Org Mode (Post Raymond Zeitler)--L0--C0--2026-07-09T23:48:51.151Z

Amit Patel: Emacs: marking text

Although I primarily use Emacs, I love the idea of vim text objects, and wanted to incorporate them into my editing. The Kakoune introduction explains that vim uses verb first, noun second for its commands, whereas Kakoune uses noun first, verb second. I wrote a blog post about why I prefer noun-verb over verb-noun, not only in text editors but also in games and other applications.

I started cobbling together some commands in a hydra, trying to match vim keys when I could:

(defhydra hydra-mark (:body-pre (set-mark-command nil) :color red)
  "
_w_, _,w_: word, symbol    _i'_, _a'_: string  _i)_, _a)_: pair
_t_, _f_: to char (exclude/include)   _0_, _$_: begin/end of line
_;_: comment   _u_: url    _e_: email      _>_: web-mode block or tag
_S_: sexp      _d_: defun  _p_: paragraph  _s_: sentence
_h_, _j_, _k_, _l_: move   _H-._: off
  "
  ("t" mark-to-char-exclusive)
  ("f" mark-to-char-inclusive)
  ("0" move-beginning-of-line)
  ("$" move-end-of-line)
  ("w" er/mark-word)
  (",w" er/mark-symbol)
  ("i'" er/mark-inside-quotes)
  ("a'" er/mark-outside-quotes)
  ("i)" er/mark-inside-pairs)
  ("a)" er/mark-outside-pairs)
  ("i]" er/mark-inside-pairs)
  ("a]" er/mark-outside-pairs)
  ("j" next-line)
  ("k" previous-line)
  ("h" left-char)
  ("l" right-char)
  (";" er/mark-comment)
  ("u" er/mark-url)
  ("e" er/mark-email)
  ("d" er/mark-defun)
  ("S" mark-sexp)
  ("s" mark-end-of-sentence)
  ("p" mark-paragraph)
  (">" web-mode-mark-and-expand)
  ("H-." deactivate-mark :exit t)
   )
(defun my/hydra-mark ()
  (interactive)
  (set-mark-command nil)
  (hydra-mark/body))

(bind-key "H-." #'my/hydra-mark)
And some helper functions:
(defun move-to-char (arg char)
  (interactive (list (prefix-numeric-value current-prefix-arg)
                     (read-char "Move to char: " t)))
  (search-forward (char-to-string char) nil nil arg))

(defun mark-to-char-exclusive (arg char)
  "Mark up to but not including ARGth occurrence of CHAR."
  (interactive (list (prefix-numeric-value current-prefix-arg)
                     (read-char "Mark to char: " t)))
  (set-mark
   (save-excursion
     (move-to-char arg char)
     (backward-char)
     (point))))

(defun mark-to-char-inclusive (arg char)
  "Mark up to and including ARGth occurrence of CHAR."
  (interactive (list (prefix-numeric-value current-prefix-arg)
                     (read-char "Mark to char: " t)))
  (set-mark
   (save-excursion
     (move-to-char arg char)
     (point))))

(use-package expand-region) ;; for the er/* commands

I've been using this since 2017. It's now 2022. How did it go?

Old habits are hard to break. I used this for urls, words, strings, almost none of the others. It's easier for me to move the cursor manually than to learn the specific commands, unless the command is something I use often.

So I'm going to call this experiment complete. I learned that it it's not going to work for me. That's ok. I try lots of things and most don't work. Some do, which is why I keep trying.

I still think the idea is good but I have 30 years of emacs muscle memory to fight.

I considered switching to one of these:

  • mark-thing-at lets you define keys for marking each type of thin
  • objed lets you work on text objects, inspired by vim and kakoune
  • expand-region will guess the object instead of making me choose

I decided I'll remove my experiment code and try expand-region next. [update 2025-05: gave up on expand-region in general, as I only use mark word or url, so I wrote something specific to that.]

-1:-- Emacs: marking text (Post Amit Patel)--L0--C0--2026-07-09T23:09:59.982Z

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!