Charles Choi: Announcing shazam.el

img

Continuing my explorations on malleable computing with Emacs is examining access to OS/Platform-specific resources, in this case with macOS. macOS provides a number of mechanisms for high-level inter-process communication, among them AppleScript & Automator Actions (now considered legacy but still maintained) and App Intents, Entities & Shortcuts (introduced in macOS 13 and considered by Apple a replacement for the former).

This post both announces and covers the implementation of an Emacs integration with the Shazam music recognition service that is packaged with macOS. This Emacs integration is called shazam.el (https://github.com/kickingvegas/shazam) and is now available for installation via MELPA.

To understand the shazam.el implementation, the following background information is provided.

Background

Shazam is a music recognition service that is implemented across Apple's different platforms (macOS, iOS/iPadOS, watchOS, tvOS). This service provides a shortcut action (Recognize Music) which is implemented using the following APIs:

With the Recognize Music shortcut action we can realize the Elisp package shazam.el.

Implementation

A Shortcut (aka workflow) defined in the Shortcuts app can be invoked via command line as follows:

1
shortcuts run "<shortcut name>"

where “shortcut name” is the name given to a Shortcut enclosed by double-quotes.

If the Shortcut is intended to write to stdout, then the shortcuts command line utility must be run into a pipe.

1
shortcuts run "<shortcut name>" | cat

The above incantation lets Emacs run a Shortcut.

The Shortcut used by shazam.el invokes the Recognize Music action and does some work to transform the Shazam search result into a JSON dictionary. The JSON dictionary is written to stdout (iCloud Shortcut: “Identify Music JSON”) which can be read by Emacs and processed.

The sequence diagram below shows the overall integration workflow between Emacs and Shortcuts for shazam.el.

img

Features

Details on all the features provided by shazam.el can be found in its User Guide. Of note is that once the Shazam search result is deserialized, its content can be processed into a variety of formats. shazam.el takes advantage of this by storing recognized search results into an Org file, using Org markup and properties to store metadata provided by each result. Users can subsequently peruse this history file at their convenience.

Closing Thoughts

This work builds off the explorations in malleable computing with Emacs described in my prior posts:

There is not a lot of Elisp to shazam.el as shown in the cloc result for it:

1
2
3
4
5
6
github.com/AlDanial/cloc v 2.10  T=0.01 s (143.3 files/s, 35402.8 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Lisp                             1             50             38            159
-------------------------------------------------------------------------------

Most of the challenge in building shazam.el was understanding how start-process worked to asynchronously run the shortcuts command line utility.

A downside to using a Shortcut (workflow) is the requirement to use a binary code-signed representation that is accessed via the Shortcuts app. This makes it less flexible than AppleScript where declaring behavior can be done in plain text. For example, the now-playing.el package can remote-control the Music app via AppleScript that is directly invoked from Emacs built with support for ns-do-applescript. In contrast, accessing contemporary macOS app behavior that is exposed via Intents and Entities requires installing (or creating) a binary code-signed representation (the Shortcut). This adds an extra step (or steps for installing multiple shortcuts) to build an integration with Emacs or any other orchestration tool.

References

-1:-- Announcing shazam.el (Post Charles Choi)--L0--C0--2026-08-24T21:00:00.000Z

Marcin Borkowski: Transforming yanked text

Recently I found myself killing and yanking more often than ever. One problem with that is that the capitalization and punctuation of what I have yanked is often wrong. As a (pretty stupid) example, I might see the sentence “This is bad.”, and I might want to write another one: “He thinks that this is bad, and he’s right.” If I use kill-sentence (M-k) or backward-kill-sentence (C-x DEL), I have the whole This is bad. in the kill ring, but what I need to yank is this is bad (with lower-case “t” and without the period), so I need to make both changes after yanking. After several dozen such cases I thought, this is Emacs, I shouldn’t be doing this manually!
-1:-- Transforming yanked text (Post Marcin Borkowski)--L0--C0--2026-08-24T19:01:51.000Z

Irreal: Emacs 31.1

Happy news this morning. Sean Whitton has announced the release of Emacs 31.1. As usual, it’s available at the mirror nearest you. Note, however, that the URLs in Whitton’s announcement have a typo. See this message from Eli for the correct URLs.

I’m writing this with the just built new Emacs and everything seems fine. I had a bit of trouble downloading the source because, apparently, some of the mirrors haven’t updated yet. If that happens to you, just repeat the request a time or two and you should get everything. After that, enjoy your new Emacs and all the goodies it brings. As always, Mickey has his usual exegesis of the changes.

As I invariably say at times like this, Eli and the rest of the development team deserve our huge thanks for their selfless efforts on our behalf.

-1:-- Emacs 31.1 (Post Irreal)--L0--C0--2026-08-24T16:41:31.000Z

Andros Fenollosa: A year of Org Social, my social network

A year ago I created Org Social. Just a plain-text specification and an .org file served over HTTP. Today it is time to take stock, to see how the project has moved, what has worked and what has not. And above all, to tell the story of a year of development and community.

A bird's-eye view of the whole year would look like this:

2025

  • August: the specification is born (Aug 12). Front page of Hacker News and 150 stars in 24 hours. First client, org-social.el (Aug 16). Spec 1.1: the first external contribution removes :REPLY_URL:.
  • September: the Relay is born. Groups arrive in the specification.
  • October: org-social.el v2.0 with a redesigned interface (Oct 1). It lands on MELPA (Oct 26). Proposal to the Org Mode list.
  • November: Relay v1.0 and host is born. A record of 581 posts in a month, probably because the Relay indexed every account. Spec 1.4: boosting posts (:INCLUDE:) and domain migration (:MIGRATION:).
  • December: Org Social overtakes twtxt in activity. Spec 1.5: posts visible only to mentioned people (:VISIBILITY:).

2026

  • January: the ID moves into the headline (spec 1.6). A big change that required syncing every piece of software in the ecosystem.
  • May: Org Social reaches the App Store (May 11). Spec 1.7 with the :BOT: property.
  • July: Planet Emacslife adopts Org Social. The Relay ships bridges to Mastodon and RSS. Webmentions are integrated into the specification.

From the explosion to maturity

The autumn of 2025 was the big explosion.

The specification started moving fast, and a good part of the credit goes to proposals from outside. In version 1.1 (that same August 16) I removed the separation between * Posts and the first **, and dropped the :REPLY_URL: property. That change was pushed by @confusedalex: a stranger who showed up, pointed out it was unnecessary, and was right. Nothing validates an idea more than a stranger opening an issue to improve it.

From there, the specification kept growing with judgment:

  • 1.2: the avatar now had to be at least 128x128 in JPG or PNG.
  • 1.3: :MOOD: (reactions) and :GROUP: (groups) arrived. Groups were added on September 20.
  • 1.4: :INCLUDE: to share other people's posts (the classic boost) and :MIGRATION: to announce a domain change.
  • 1.5: :VISIBILITY: with the value mention, for posts only mentioned people can see.

And while the specification grew, so did the ecosystem. Org Social stopped being a file and became a constellation of programs.

Even before the Relay existed, discovering people was a manual affair: a registers.txt file in the repository where people signed up by hand. It reached 17 active users.

The Relay was born on September 5 and hit its v1.0 on November 1. It is the piece that indexes users, mentions, replies, groups and threads across the whole network, so a client can discover new people and find out that a stranger has mentioned you. It retired registers.txt and surfaced a bunch of new accounts that were there but nobody could see.

On October 26, with version 2.3, org-social.el landed on MELPA. That is the moment the Emacs client became truly stable.

The Emacs client I had to rewrite from scratch

Getting to that stable client was not free. It was my big headache.

The first version of org-social.el I wrote in August, quickly, to prove the idea worked. A single file that grew commit by commit: reading feeds, painting the timeline, replying, polls. It worked. But inside it was a snowball. The logic and the interface were tangled together, everything lived in the same place, and every new feature was a fight. When I tried to add threads, groups, notifications and real time, I hit a wall: the architecture could not take any more.

With everything I had learned, I rewrote it. On October 1, 2025 I announced the second version, org-social.el v2.0, with a completely redesigned interface and "the biggest update yet". It changed more than 4,600 lines at once. I split the monolith into modules: the parser on one side, the feeds on another, the interface in its own ui/ folder divided into buffers (timeline, thread, profile, groups, search, discover, notifications), the Relay, the real time, the validator, and more. And there I had to apply several optimization strategies to avoid freezes while Emacs processed feeds or subfeeds. The rewrite was a long and painful birth, but it was worth it.

During the process I found a bug in Org Mode itself.

Every Org Social post needs a unique identifier. The original decision, made in the early days, was to use an ISO 8601 timestamp inside the properties drawer. A post looked like this.

** 
:PROPERTIES:
:ID: 2025-05-01T12:00:00+0100
:END:
This is my post.

See that empty headline, the ** followed by nothing? Well, it is not empty: it must carry a space after it, even though Org Mode's documentation does not say so and is ambiguous. An empty level-two headline needs that trailing space to be valid syntax. Without the space, ** stops being a headline. The problem is that a lot of people (myself included) have delete-trailing-whitespace enabled on save, an Emacs command that removes trailing spaces at the end of each line. It is a universally good practice. And it silently wrecked all your posts.

I reported it and contributed a patch, but it was not accepted for backwards-compatibility reasons. Still, the discussion was very interesting.

I was forced to ship a fix in version 1.6, on January 4, 2026. I moved the ID into the headline.

** 2025-05-01T12:00:00+0100
This is my post.

Now the identifier lives where nothing can erase it.

In May 2026 I marked the old format as legacy. It is still valid for compatibility, but I recommend the new one and I document the trailing-space trap for anyone who runs into it. I took the chance in that version to also bring in :LOCATION:, :BIRTHDAY:, :LANGUAGE: and :PINNED:.

During these months I wrote several articles around the project: Why Org Social is the ethical Fediverse alternative, where I defend the project without hiding its limits, and Quick tutorial to get a blog online from Org mode thanks to Org Social, where I use the ecosystem to publish a blog with no server.

With the specification settled and the client mature, the same old pending task remained: making it easy for anyone to start, Emacs users or not.

iOS and host: finally an easy way in

On November 18, 2025 host was born, the hosting service for your social.org with an automatic nick and public URL, very much in tune with the Tilde philosophy. Not everyone has a server to upload a file to, and this solved that at the root. It also opened the door to native clients, since it let you sync your social.org from any device.

To communicate, most of us use the smartphone far more than the desktop. A native iOS client broke that barrier.

On May 11, 2026 the app reached the App Store. For the first time you could carry your decentralized social network in your pocket, with the interface anyone who has touched a phone expects: posts, replies, threads, polls, groups, scheduled posts and feed export. And all of it with no analytics, no tracking, no telemetry, no third-party SDKs.

The combination of host and the iOS app made Org Social accessible.

The flow became as simple as this:

  1. You sign up to host from your phone. Behind the scenes, host gives you a nick and a public URL.
  2. You write in the iOS client and save. The client uploads your social.org to host.

Transparent and frictionless.

Of course, this is also compatible with the Emacs client, and it is bidirectional: you can write in Emacs and carry on in iOS, or the other way around.

On April 21, 2026 I published a tiny post, "writing from iOS", from a test build of the app. Seeing it show up in the timeline, coming from an iPhone and not from an Emacs buffer, was one of those moments when a project crosses a line.

In May the app got custom themes and push notifications, and with that I called it done. It went into maintenance mode. Not all software has to grow forever.

That same month version 1.7 of the specification arrived with the :BOT: property, to mark posts generated by bots without cluttering anyone's timeline. With it, for example, you can play a game of chess against a bot from Org Social.

And the year brought signs of maturity. The Relay shipped bridges to follow Mastodon (ActivityPub) accounts and RSS/Atom feeds from any Org Social client, as if they were just more users. A bit later Webmentions were integrated, so that when you link to someone's article they get notified.

Org Social is starting to talk to the rest of the web, not just to itself.

The present

A year later, where do we stand?

The picture of the community is that of a niche project, healthy and stable.

The numbers:

Metric Total
Registered accounts 267
Indexed feeds 236
Posts 2,392
Follows 1,274
Active groups 5

It stays steady at around 19 active accounts a month. Not millions, and it never will be. It is a small community that is still there, month after month.

The specification is at 1.7, though I do not think a new version will come out, at least not in the short term. It feels finished to me.

The official ecosystem is already a dozen pieces:

  • org-social: the specification, the founding document.
  • org-social.el: the Emacs client, today at version 2.14 and beyond.
  • relay: the P2P server that indexes the network.
  • host: hosting for your social.org with an automatic nick.
  • OrgSocialKit / ios: native library and client for iOS and macOS in Swift, available on the App Store.
  • live-preview and static-preview: social-media-style preview cards for post URLs.
  • web-reading: a web timeline viewer, for those who don't use Emacs.
  • push: push notifications for the iOS app.
  • rss-bridge: turns any RSS/Atom feed into Org Social format, so you can follow whatever you want.
  • awesome: the curated list of clients, relays, libraries and tools.

The important stuff already exists.

The future

I would love a client for Android and one for the terminal. Neither is trivial nor impossible.

It would also make sense to extend host so it could hold multimedia content, like images and videos. I am still thinking about it.

What I do want is to improve the funding. Each Relay is a node in the network: the more there are, the more robust it becomes. And Apple's developer account does not pay for itself. If you like the project, consider making a donation.

And if you have made it this far, you could leave a comment on your social.org by following this tutorial.

See you in the timeline!


Help me keep writing Every coffee gives me a push toward the next article. Sure, it's on me!

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

-1:-- A year of Org Social, my social network (Post Andros Fenollosa)--L0--C0--2026-08-24T15:05:32.000Z

James Cherti: Emacs: Copy-paste without org-mode or markdown-mode formatting bleeding into other buffers

When text is copied from an org-mode or markdown-mode buffer and pasted into another Emacs buffer, its visual formatting can sometimes follow it. For instance, text displayed with a particular color, background, or font weight in an Org or Markdown buffer can retain that appearance after being inserted into a Python buffer.

This can be undesirable because the inserted text may then display with the source buffer's face text property instead of relying on the destination buffer's normal font-lock or syntax highlighting.

Understanding the face text property

In Emacs, visual formatting can be represented by the face text property. Some major modes, including org-mode and markdown-mode, make extensive use of text properties, including face, to control how text is displayed. When text containing these properties is yanked (pasted), Emacs can preserve them unless they are explicitly excluded.

Solution 1: Strip faces universally on paste

Emacs provides the yank-excluded-properties variable for specifying text properties that should not be retained when text is yanked (pasted) into a buffer. To prevent face properties from being carried into the destination buffer, add the following to your configuration:

(add-to-list 'yank-excluded-properties 'face)

With this configuration, the face property is removed from the inserted yanked text. The inserted text can therefore be displayed according to the destination buffer's own font-lock and other display rules.

Tradeoff: Once face is included in yank-excluded-properties, yanked text does not retain its face text property. This is generally desirable when the intention is for inserted text to adopt the appearance of its destination buffer, but it may be undesirable for workflows that intentionally use rich text. Examples of workflows and modes where you might want to preserve rich text include enriched-mode, mu4e, or gnus.

For the majority of users, adding 'face to yank-excluded-properties is a worthwhile configuration change. This makes copy and paste more predictable and prevents formatting from leaking into unrelated buffers.

Solution 2: Strip faces per mode on copy

While appending face to the global yank-excluded-properties variable sanitizes pasted text, it applies universally across Emacs. For workflows that require rich text in specific applications like mu4e or gnus, a global configuration is too aggressive.

An alternative way is to strip the visual formatting at the source during the copy operation, rather than at the destination during the paste operation. This can be achieved by applying buffer-local advice to filter-buffer-substring-function:

;; Copy-paste without org-mode or markdown-mode formatting bleeding into other
;; buffers. Alternative to: (push 'face yank-excluded-properties)
;; URL: https://www.jamescherti.com/emacs-fix-org-mode-copy-paste-yank-bleed/
(defun my-strip-face-properties-from-string (string)
  "Remove visual face properties from STRING."
  (remove-text-properties 0 (length string)
                          '(face nil font-lock-face nil)
                          string)
  string)

(defun my-enable-plain-text-copy ()
  "Strip visual face properties from copied text in the current buffer."
  (add-function :filter-return
                (local 'filter-buffer-substring-function)
                #'my-strip-face-properties-from-string))

(add-hook 'markdown-mode-hook #'my-enable-plain-text-copy)
(add-hook 'markdown-ts-mode-hook #'my-enable-plain-text-copy)
(add-hook 'org-mode-hook #'my-enable-plain-text-copy)

;; Other modes
;; (add-hook 'prog-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'text-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'conf-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'diff-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'help-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'info-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'compilation-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'shell-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'eshell-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'magit-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'dired-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'term-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'vterm-mode-hook #'my-enable-plain-text-copy)

Instead of filtering the text when it is pasted, this solution cleans the text as it is copied, making sure only plain text enters the Emacs clipboard history (the kill ring).

-1:-- Emacs: Copy-paste without org-mode or markdown-mode formatting bleeding into other buffers (Post James Cherti)--L0--C0--2026-08-24T12:39:53.000Z

Sacha Chua: 2026-08-24 Emacs news

Emacs 31.1 has been released, whee! Also, Divya's post An Introduction to Canvas in GNU Emacs is really cool and I'm looking forward to that becoming generally available in Emacs 32. =)

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-08-24 Emacs news (Post Sacha Chua)--L0--C0--2026-08-24T12:37:18.000Z

Mickey Petersen: What's New in Emacs 31.1?

Emacs 31.1 is finally out! Unlike earlier Emacs versions, there is not a singular big-bang feature in this release. From what I could gather, the new garbage collector was possibly planned for inclusion in Emacs 31.1, but it has been postponed to Emacs 32. But more on that in a future post; it’s an interesting subject.

Some of the more notable features in Emacs 31.1 are small, quality of life fixes, and one deprecation that marks the end of an era.

As always, my book, Mastering Emacs is 31% off for the next week to celebrate the release also.

The unexec/pdumper controversy and subsequent deprecation

Emacs is… not a normal application. When you compile and link it, you get temacs which is the heart of Emacs but without most of the libraries that ship with it. It’s a bare-bones Emacs with little more than the C core and the interpreter; it’s not really that useful.

To get the Emacs binary you know and love, you have to run temacs and tell it to load the standard library into memory. That is slow. There is a lot of elisp and housekeeping that has to happen. It can take several minutes and a fair bit of CPU and ram to start Emacs this way; it’s untenable.

This has been a problem that has dogged Emacs for decades. It’s not a huge deal today, but back in the day it could break the back on home computers or even shared multi-user environments if a brace of enthusiastic Emacs users all decide to launch Emacs at the same time in the morning.

The solution to this problem? Load it all in once and then literally dump the text/data/bss/etc. segments of Emacs’s memory to a new binary. Do that, and you don’t have to bootstrap all that Emacs lisp state again and again. It feels like a wrestling move almost. You corral a top-heavy Emacs into position and apply The Attitude Adjustment, body slamming Emacs into a new binary, and everything’s all set up and ready to go.

It’s a pretty boss move.

But to make this, uh, wrestling move work, Emacs depended on a number of snowflake functions in glibc. After a couple of decades of enabling this sort of bad behavior the glibc team called it quits, and Emacs had to find another way of doing it.

Daniel Colascione built a much better solution, though not everyone was happy about it, that – put simply – standardizes the serialization of Emacs’s internal structures into something that is not a 1:1 dump of its internal memory structures.

The portable dumper’s been the default for a number of years now. It was first introduced around ten years ago, and in keeping with Emacs’s long history of backwards compatibility, the old unexec dumper was kept around ostensibly for the one or two users who found the idea of a portable dumper risible or unworkable.

But now it is finally gone for good. The end of an era.

User Lisp Directory

Classic problem: you git clone or download an Emacs package somewhere and now you want it to work. But how? It’s not that trivial; there are quite a few competing ways of doing it. The simplest one is to tell users to drop their package into the user-lisp/ in your .emacs.d directory and Emacs will sort out loading and setting up autoload (so the right stuff appears in M-x.)

Minibuffer and Completions

Emacs 30.1 gained completion-preview-mode, a native “pop-up window” system not unlike Company and Corfu, but more attuned to Emacs’s own way of doing things: using the *Completions* window instead of a floating child frame like Company and friends.

Emacs 31.1 builds on that with a wide range of customizable options you’re sure to want to customize if you want to go native.

Rotating Window Layouts

M-x window-layout-rotate-clockwise (see C-x w C-h for the manifold new options) and suchlike rotate your window layouts. Another little UI winner.

Exchanging the point and mark without activating the region

I’ve talked (mostly in the my book) about how transient-mark-mode is a rather awkward one-size-fits-all that was draped over Emacs’s multitude of “region-affecting” commands, like kill-region (C-w).

So C-x C-x, that exchanges point and mark, also activates the region whether you want it to or not. Fixing the mark commands in transient mark mode is an old article of mine where I demonstrate how to do exactly that. But now there’s a builtin option to not have it do that — sweet.

Tree-sitter now offers to install its grammars for you

Two blockers work in tandem to hold back the wider adoption of tree-sitter in Emacs:

  1. The fact that TS demands a special major mode to work; and that said mode is often a thread-bare re-implementation of the original.
  2. That installing grammars, especially on Windows, is a giant pain in the neck, as you have to not only thread the needle with the exacting ABI version of the tree-sitter library itself, but also ensure you just the exacting version of each language grammar, or everything goes up in smoke.

The former is still a problem, but the latter is now mostly resolved. Emacs can now finally offer to install the right language grammar for TS modes it knows about.

Now there’s no excuse not to try out Combobulate: Structured Movement and Editing with Tree-Sitter.

and so much more

Lots of little tweaks and changes. Have a read.

Installation Changes in Emacs 31.1

unexec dumper removed.
The traditional unexec dumper, deprecated since Emacs 27, has been
removed.
The portable dumper now works on m68k a.out targets.

As I wrote in the introduction at the top, this is indeed the end of an era.

Emacs's old 'ctags' program is no longer built or installed.
You are encouraged to use Universal Ctags <https://ctags.io/> instead.
For now, to get the old 'ctags' behavior you can can run 'etags --ctags'
or use a shell script named 'ctags' that runs 'etags --ctags "$@"'.

If you’re a TAGS user you should check with where and make sure you’ve got a newer one installed. (If you don’t know if you use TAGS, you do not.)

Changed GCC default options on 32-bit x86 systems.
When using GCC 4 or later to build Emacs on 32-bit x86 systems,
'configure' now defaults to using the GCC options '-mfpmath=sse' (if the
host system supports SSE2) or '-fno-tree-sra' (if not).  These GCC
options work around GCC bug 58416, which can cause Emacs to behave
incorrectly in rare cases.
New configure option '--with-systemduserunitdir'.
This allows specifying the directory where the user unit file for
systemd is installed; the default is '${prefix}/usr/lib/systemd/user'.

You can tell Emacs to install a systemd service to run Emacs’s server that way. I recommend doing this.

Startup Changes in Emacs 31.1

In compatible terminals, 'xterm-mouse-mode' is turned on by default.
For these terminals the mouse will work by default.  A compatible
terminal is one that supports Emacs setting and getting the OS selection
data (a.k.a. the clipboard) and mouse button and motion events.  With
'xterm-mouse-mode' enabled, you must use Emacs keybindings to copy to the
OS selection instead of terminal-specific keybindings.

You can keep the old behavior by customizing 'xterm-mouse-mode' to nil.

Most people do not know this but Emacs added mouse support to terminal Emacs years ago but left it off. Terminal capabilities vary widely so that was a nice and safe decision. But now it just works as you’d expect it to: menus are clickable and so forth. Good stuff.

site-start.el is now loaded before the user's early init file.
Previously, the order was early-init.el, site-start.el and then the
user's regular init file, but now site-start.el comes first.  This
allows site administrators to customize things that can normally only be
done from early-init.el, such as adding to 'package-directory-list'.

If you’re on a single user system like your laptop or home computer, this is unlikely to matter much to you.

New User Lisp directory feature.
If you have a subdirectory "user-lisp/" in your Emacs configuration
directory, then Lisp files in it and any subdirectories are now
recursively byte-compiled, scraped for autoload cookies and added to
'load-path'.

You can disable the feature by setting 'user-lisp-auto-scrape' to nil,
and you can customize the option 'user-lisp-directory' to process some
other directory instead.  There is also a new command
'prepare-user-lisp' that you can invoke at any time.  See the Info node
"(emacs) User Lisp Directory" for more details.

Oh this is so useful. I have been cargo culting the same snippets of code around for 23 years to load directories with my stuff in it; yes use-package helps but it’s still a lot of manual hassle. About time!

The first client frame now shows warnings from daemon startup.
When there are warnings emitted during Emacs startup, usually due to
problems in your initialization file, these are shown in a "*Warnings*"
buffer.  Until now such warnings were not made visible in the case that
Emacs was started as a daemon.  Now the first frame after daemon startup
will show the "*Warnings*" buffer.  So for example, starting Emacs with
a command like 'emacsclient -a "" -c' will now show "*Warnings*" just
like a plain invocation of 'emacs' would.

Bad news. Emacs’s insistence on telling you about every minor stubbed toe in some random package will now plague you even if you’re running Emacs as a daemon. Such a cursed feature. Nobody cares. If it was important it’d be an error.

Changes in Emacs 31.1

'line-spacing' now supports specifying spacing above the line.
Previously, only spacing below the line could be specified.  The user
option can now be set to a cons cell to specify spacing both above and
below the line, which allows you to vertically center text.

This is a global value to all of Emacs, it’s not a face setting, so you cannot use M-x customize-face to change it. Set it with setopt or customize ui.

New face 'margin' for the window margin display.
A new basic face 'margin' is used by default for text displayed in the
left and right margin areas, which are used by various packages for
per-line annotations.  Its background defaults to the frame default
background, so existing behavior is unchanged for users who do not
customize this new face.

Display strings shown in the margins now inherit unspecified face
attributes from the 'margin' face, if the string itself does not fully
specify its face.  If your code relied on the face of the underlying
buffer text to serve as a default for any unspecified face attributes of
strings displayed in the margin, you must now apply those face
attributes to the margin string itself using 'propertize'.
'prettify-symbols-mode' attempts to ignore undisplayable characters.
Previously, such characters would be rendered as, e.g., white boxes.
'standard-display-table' now has more extra slots.
'standard-display-table' has been extended to allow specifying glyphs
that are used for borders around child frames and menu separators on TTY
frames.

Call the command 'standard-display-unicode-special-glyphs' to set up the
'standard-display-table's extra slots with Unicode characters.  See the
documentation of that command to see which slots of the display table it
changes.
Child frames are now supported on TTY frames.
This supports use-cases like Posframe, Corfu, and child frames acting
like tooltips.  To enable tooltips on TTY frames, call 'tty-tip-mode'.

The presence of child frame support on TTY frames can be checked with
'(featurep 'tty-child-frames)'.

Recent versions of Posframe and Corfu are known to use child frames on
TTYs if they are supported.

This is a welcome change for terminal users. Frames in the terminal do not work as they do in GUI — they behave more like tmux/screen “windows”. Here child frames are just inset popups like the ones you find in GUI Emacs.

Several font-lock face variables are now obsolete.
The following variables are now obsolete: 'font-lock-builtin-face',
'font-lock-comment-delimiter-face', 'font-lock-comment-face',
'font-lock-constant-face', 'font-lock-doc-face',
'font-lock-doc-markup-face', 'font-lock-function-name-face',
'font-lock-keyword-face', 'font-lock-negation-char-face',
'font-lock-preprocessor-face', 'font-lock-string-face',
'font-lock-type-face', 'font-lock-variable-name-face', and
'font-lock-warning-face'.

These variables contributed both to confusion about the relation between
faces and variables, and to inconsistency when major mode authors used
one or the other (sometimes interchangeably).  We always recommended
using faces directly, and not creating variables going by the same name.

If you have customized these variables, you should now customize the
corresponding faces instead, using something like:

    M-x customize-face RET font-lock-string-face RET

If you have been using these variables in Lisp code (for example, in
font-lock rules), simply quote the symbol, to use the face directly
instead of its now-obsolete variable.

Note this is not about the faces but about variables named the same as the faces. Yeah that is confusing. Emacs has faces like font-lock-string-face that you probably have customized already. But it also has variables named the same as the faces. The variables are deprecated.

If you have configured your faces with M-x customize-face (you should!) you have nothing to worry about.

New char-table 'special-mirror-table' for mirroring special glyphs.
This char-table is used to mirror special glyphs (truncation and
continuation) when the user has defined an alternative representation
for those characters via display tables.
find-func.el commands now have history enabled.
The 'find-function', 'find-library', 'find-face-definition', and
'find-variable' commands now allow retrieving previous input using the
usual minibuffer history commands.  Each command has a separate history.

Huh. I never noticed they did not have their own history; now they do. That is good to know I guess but unlikely to affect me much.

New minor mode 'find-function-mode' replaces 'find-function-setup-keys'.
The new minor mode defines the keys at a higher precedence level than
the old function, one more usual for a minor mode.  To restore the old
behavior, customize 'find-function-mode-lower-precedence' to non-nil.

You’re unlikely to have much of a need to customize this.

'find-function' can now find 'cl-defmethod' invocations inside macros.
New minor mode 'prettify-special-glyphs-mode'.
The new minor mode prettifies the special character glyphs (truncation
and continuation) on TTY frames (and GUI frames without fringes).  You
can customize the associated new face 'special-glyphs'.

Minibuffer and Completions

Support for immediate display of the "*Completions*" buffer.
Whenever a minibuffer with completion is opened, then if the completion
table sets the 'eager-display' completion property to non-nil, the
"*Completions*" buffer will now be displayed immediately.  This property
can be overridden for different completion categories by customizing
'completion-category-overrides'.  Alternatively, the new user option
'completion-eager-display' can be set to t to force eager display of
"*Completions*" for all minibuffers, or nil to suppress this for all
minibuffers.
Support for updating "*Completions*" as you type.
If the "*Completions*" buffer is displayed and the completion table sets
the completion property 'eager-update' to non-nil, then the
"*Completions*" buffer will be updated as you type.  This property can
be overridden for different completion categories by customizing
'completion-category-overrides'.  Alternatively, the new user option
'completion-eager-update' can be set to t to make "*Completions*" always
be updated as you type, or nil to suppress this always.  Note that for
large or inefficient completion tables, this can slow down typing.
'RET' chooses the completion selected with 'M-<UP>/M-<DOWN>'.
If a completion candidate is selected with 'M-<UP>' or 'M-<DOWN>',
typing 'RET' will exit completion with that candidate as the result.
This works both in minibuffer completion and for in-buffer completion.
This feature supersedes 'minibuffer-completion-auto-choose', which
previously provided similar behavior; that variable is now nil by
default.

This goes hand in hand with the changes in Emacs 30.1 to make Emacs’s minibuffer completion system behave a little bit more like traditional company/corfu-style completers.

I really rate these new inclusions but I do warn they require a fair bit of customization to really get them to behave like something that does not get in your way.

Support for completion category inheritance.
You can now define completion categories that inherit properties from
existing categories, using the new function 'define-completion-category'.
New optional value of 'minibuffer-visible-completions'.
If the value of this option is 'up-down', only the '<UP>' and '<DOWN>'
arrow keys move point between candidates shown in the "*Completions*"
buffer display, while '<RIGHT>' and '<LEFT>' arrows move point in the
minibuffer.
New user option 'completion-pcm-leading-wildcard'.
This option configures how the partial-completion style does completion.
It defaults to nil, which preserves the existing behavior.  When it is
set to t, the partial-completion style behaves more like the substring
style, in that the input can match a candidate anywhere in the candidate
string.

Another minor tweak to a completion style to make it behave more like something it once did. Emacs has a diverse set of completion styles. The default have changed a lot over the years, sometimes to the chagrin of people who were used to the quirks of a now-relegated default style. For example there’s both an emacs21 and an emacs22 completion style in completion-styles-alist. But see Understanding Minibuffer Completion for more information.

'completion-styles' now can contain lists of bindings.
In addition to a symbol naming a completion style, an element of
'completion-styles' can now be a list of the form '(STYLE ((VARIABLE
VALUE) ...))' where STYLE is a symbol naming a completion style.
VARIABLE will be bound to VALUE (without evaluating it) while the style
is executing.  This allows multiple references to the same style with
different values for completion-affecting variables like
'completion-pcm-leading-wildcard' or 'completion-ignore-case'.  This
also applies to the styles configuration in
'completion-category-overrides' and 'completion-category-defaults'.

Oh man. That is niche. completion-styles is a shopping list of how Emacs must match things in stuff like the minibuffer’s completer. Now you can make it so initials ignores case but substring does not.

Navigating "*Completions*" now accommodates 'completions-format'.
When 'completions-format' is set to 'vertical', typing 'n', 'TAB' or
'M-<DOWN>' in the "*Completions*" buffer (the latter also in the
minibuffer) now moves point to the completion candidate in the next line
in the current column, and wraps to the next column after the last
completion candidate of the current column.  Likewise, typing 'p',
'S-TAB' or 'M-<UP>' moves point to the completion candidate in the
previous line or wraps to the previous column.  Previously, these keys
ignored the vertical format, i.e., they moved point only to the item in
the same line of the next or previous column, in accordance with the
default horizontal format.  In the vertical format, typing '<LEFT>' and
'<RIGHT>' in the "*Completions*" buffer (and when
'minibuffer-visible-completions' is non-nil, also in the minibuffer)
moves point only within the current line, analogously to how, in the
horizontal format, '<DOWN>' and '<UP>' move point only within the
current column.

You’ll want to configure this for sure if you are intent on using the Completions buffer and window for in-buffer completion. I always found navigating between the tabular structure in completions to be a bit weird and offputting; it’s a good use of space, for sure, but a flat list of matches is much easier to reason about.

Selected completion candidate is preserved across "*Completions*" updates.
When the window point is on a completion candidate in the
"*Completions*" buffer (because of 'minibuffer-next-completion' or for
any other reason), it will remain on that candidate after the
"*Completions*" is updated with a new list of completions.  The
candidate is deselected when the "*Completions*" buffer is hidden.
"*Completions*" is now displayed faster when there are many candidates.
As before, if there are more completion candidates than can be displayed
in the current frame, only a subset of the candidates is displayed.
This process is now faster: only that subset of the candidates is
actually inserted into "*Completions*" until you run a command which
interacts with the text of the "*Completions*" buffer.  This
optimization only applies when 'completions-format' is 'horizontal' or
'one-column'.
New user option 'crm-prompt' for 'completing-read-multiple'.
This option configures the prompt format of 'completing-read-multiple'.
By default, the prompt indicates to the user that the completion command
accepts a comma-separated list.  The prompt format can include the
separator description and the separator string, which are both stored as
text properties of the 'crm-separator' regular expression.

It’s a pretty rare feature, that. You can “toggle-select” multiple matches from the minibuffer; few things use it, to be honest. I find the user experience rather poor if I am perfectly honest, no matter the completer. Helm is one of the few tools I think that does it well.

For a practical example of multi-select see Fuzzy Finding with Emacs Instead of fzf.

New user option 'completion-preview-sort-function'.
This option controls how Completion Preview mode sorts completion
candidates.  If you use this mode together with an in-buffer completion
popup interface, such as the interfaces that the GNU ELPA packages Corfu
and Company provide, you can set this option to the same sort function
that your popup interface uses for a more integrated experience.

('completion-preview-sort-function' was already present in Emacs 30.1,
but as a plain Lisp variable, not a user option.)
New user option 'completion-preview-inhibit-functions'.
This option provides fine-grained control over Completion Preview mode
activation.  You can use it to specify arbitrary conditions in which to
inhibit the mode's operation.

Another thing you’ll want to customize. You may want certain movement commands like those used in paredit or combobulate commands to not trigger the completion window.

New mode 'minibuffer-nonselected-mode'.
This mode, enabled by default, directs attention to the active
minibuffer window in the case the minibuffer window is no longer
selected, but still waiting for input.  This uses the new
'minibuffer-nonselected' face.

I like this. I am glad it is enabled by default; it can be a little bit confusing not having a selected/non-selected state.

'read-multiple-choice' now uses the minibuffer to read a character.
It still can use 'read-key' when the variable
'read-char-choice-use-read-key' is non-nil.
'map-y-or-n-p' now uses the minibuffer to read a character.
It still can use 'read-key' when the variable
'y-or-n-p-use-read-key' is non-nil.

Ugh. They mucked around with the default method used for answering “yes or no” prompts in Emacs to make it more like a regular minibuffer thing instead and it caught me out by surprise some years ago when they did that, and it was a pain to track down. Keep you eye on this one if you’re of a similar mind to me on this.

'flex' completion style rewritten to be faster and more accurate.
Completion and highlighting use a new, superior algorithm.  For example,
pattern "scope" now ranks 'elisp-scope-*' functions well above
'dos-codepage' and 'test-completion'.  Pattern "botwin" finds
'menu-bar-bottom-window-divider' before 'ibuffer-other-window'.

Flex matching is an ido-mode feature, and I believe this is strictly speaking a reimplementation of it for the fido-mode completer built on the “new” minibuffer completion system. See Introduction to Ido Mode for IDO mode; and Understanding Minibuffer Completion for the latter.

Mouse

New mode 'mouse-shift-adjust-mode' extends selection with 'S-<mouse-1>'.
When enabled, you can use the left mouse button with the '<Shift>' modifier
to extend the boundaries of the active region by dragging the mouse pointer.

Cool. I rarely drag-select stuff in Emacs, but for finicky stuff it does actually work faster than a keyboard if it’s a one-off.

'context-menu-mode' now includes a "Send to..." menu item.
The menu item enables sending current file(s) or region text to external
(non-Emacs) applications or services.  See send-to.el for customizations.

Oh my this is great. M-x context-menu-mode itself is a reasonably new feature itself (Emacs 28) and is not on by default. It adds contextual right-click menus to stuff. I have not kept abreast with all the places it has had custom commands added to it, and as the default is a bit… barebones, I can imagine most people bounced right off.

You can manually trigger the context menu mode (minor mode active or not) with M-x context-menu-open.

The mouse now drags lines in character increments again.
Dragging a horizontal or vertical line like the mode line or the lines
dividing side-by-side windows now happens in increments of the
corresponding frame's character size again.  This is the behavior
described in the manual and was the default behavior before
'window-resize-pixelwise' was added for Emacs 24.1.  To drag in pixel
increments, as with Emacs 24 through Emacs 30, customize
'window-resize-pixelwise' to t.

Windows

New commands to modify window layouts of frames.
'window-layout-rotate-clockwise' ('C-x w r <RIGHT>') and its counterpart
'window-layout-rotate-anticlockwise' ('C-x w r <LEFT>') rotate an entire
window layout.
'window-layout-flip-topdown' ('C-x w f <DOWN>', 'C-x w f <UP>') and
'window-layout-flip-leftright' ('C-x w f <LEFT>', 'C-x w f <RIGHT>')
flip the window layout vertically and horizontally.
'window-layout-transpose' ('C-x w t') reorganizes windows such that
every horizontal split becomes a vertical split and vice versa.
'rotate-windows' ('C-x w o <RIGHT>') and its counterpart
'rotate-windows-back' ('C-x w o <LEFT>') rotate windows in cyclic
ordering.

Oh I love this. But I’ll probably just pick one direction, clockwise or whatever, and bind that to an easy-to-reach key and suffer the indignity of tapping a few times. It is not often I wish to do this sort of thing. I’m a bit curious though because I am guessing the implementation will cycle through all nodes (Emacs’s window tiling window splits are represented as a tree structure) in the tree — try M-: (window-tree) to see the internal representation.

New user option 'rotate-windows-change-selected'.
This controls whether 'rotate-windows' and 'rotate-windows-back' change
the selected window.  If nil, the selected window does not change.
The default is t, which means the new selected window will be the one
that winds up at the location of the previously-selected window.

Rotate windows but not move with it? Not for me, thanks.

New user option 'transpose-dedicated-windows'.
This controls how functions transposing or rotating windows handle
dedicated windows.  The default is nil, which causes these function to
signal an error if they encounter a dedicated window.

Yeah you’ll want this at nil; dedicated windows are sticky windows. Rotating them around is probably not what you intend — or maybe it is, if you have a peculiar workflow.

Windmove commands now move to skipped windows if invoked twice in a row.
The new user option 'windmove-allow-repeated-command-override' controls
this behavior: if it is non-nil, invoking the same windmove command twice
overrides the 'no-other-window' property, allowing navigation to windows
that would normally be skipped.  The default is t; customize it to nil
if you want the old behavior.

C-x o taps through windows as you probably know. But you can flag a window (see Demystifying Emacs’s Window Manager) as no-other-window so it is exempt from that command. Windmove of course lets you move between windows using your arrow keys.

New hook 'window-deletable-functions'.
This abnormal hook gives its client a way to save a window from being
deleted implicitly by functions like 'kill-buffer', 'bury-buffer' and
'quit-restore-window'.

Emacs always had a weird, disconnected relationship between buffers and windows, as anybody who has ever tried to tame the window manager (see previously mentioned article) so this feels like another patch on top of what is a pretty irreconcilable problem: how do you keep two things that can be interchanged easily from manipulating something the user/package does not want it to? With more hooks, it seems…

Buffer-local window change functions now run in their buffers.
Running the buffer-local version of each of the abnormal hooks
'window-buffer-change-functions', 'window-size-change-functions',
'window-selection-change-functions' and 'window-state-change-functions'
will make the respective buffer temporarily current while running the
hook.
'window-buffer-change-functions' is run for removed buffers too.
The buffer-local version of 'window-buffer-change-functions' may now be
run twice: once for the buffer removed from the window and once for the
buffer now shown in that window.
New user option 'quit-window-kill-buffer'.
This option specifies whether 'quit-window' should preferably kill or
bury the buffer shown by the window to quit.  The default is nil.
Customize it to t to always kill the buffer; customize to a list of
major modes to kill if the buffer's major mode is one of those.
New user option 'kill-buffer-quit-windows'.
This option has 'kill-buffer' call 'quit-restore-window' to handle the
further destiny of any window showing the buffer to be killed.
'split-window' can optionally resurrect deleted windows.
A new optional argument REFER of 'split-window' makes it possible to,
instead of making a new window object, reuse an existing, deleted one.
This can be used to preserve the identity of windows when swapping or
transposing them.
New window parameter 'quit-restore-prev'.
This parameter is set up by 'display-buffer' when it detects that the
window used already has a 'quit-restore' parameter.  Its presence gives
'quit-restore-window' a way to undo a sequence of buffer display
operations more intuitively.
'quit-restore-window' handles new values for BURY-OR-KILL argument.
The values 'killing' and 'burying' are like 'kill' and 'bury' but assume
that the actual killing or burying of the buffer is done by the caller.
New user option 'quit-restore-window-no-switch'.
With this option set, 'quit-restore-window' will delete its window more
aggressively rather than switching to some other buffer in it.

Ahem yeah - as above. All these things are just ointment balmed on Emacs to try and solve an intractable problem. If you let a window host anything, and a buffer jump around and open anywhere (either mechanically or by the whim of the user) then… how do you lock it down properly if you want something IDE-like?

I like the idea of these things. And I predict few will ever really make use of them, even in packages.

The user option 'display-comint-buffer-action' has been removed.
It has been obsolete since Emacs 30.1.  Use '(category . comint)'
instead.  Another user option 'display-tex-shell-buffer-action' has been
removed too, for which you can use '(category . tex-shell)'.

Nothing much to worry about.

New user option 'split-window-preferred-direction'.
Functions called by 'display-buffer' split the selected window when they
need to create a new window.  A window can be split either vertically
(one below the other) or horizontally (side by side).  This new option
determines which direction will be tried first in the case that both
directions are possible according to the values of
'split-width-threshold' and 'split-height-threshold'.  The default value
is 'longest', which means to prefer to split horizontally if the
window's frame is a "landscape" frame, and vertically if it is a
"portrait" frame.  (A frame is considered to be portrait if its vertical
dimension in pixels is greater or equal to its horizontal dimension,
otherwise it is considered to be landscape.)  Previous versions of Emacs
always tried to split vertically first, so to get the previous behavior,
you can customize this option to 'vertical'.  The value 'horizontal'
always prefers the horizontal split.

Good news if you hated the random nature of window splits. Now you’ll have a little bit of control over which direction at least. I recommend you make a note of this and if you find it aggravating that it splits things the wrong way, do set it.

The default value of 'split-width-threshold' is reduced from 160 to 150.
We believe that, after splitting, text filled to 75 columns remains
comfortable to read.

No arguments here.

New optional argument INDIRECT for 'get-buffer-window-list'.
With this argument non-nil, 'get-buffer-window-list' will include in the
return value windows whose buffers share their text with BUFFER-OR-NAME.
New 'display-buffer' action alist entry 'reuse-indirect'.
With such an entry, 'display-buffer-reuse-window' may also choose a
window whose buffer shares text with the buffer to display.

Indirect buffers are a power user feature. If you want to do separate things in the same buffer, you can split and “point” your new window to an already-visited buffer in another window, but then you’ll run into awkward things like shared major modes, the point not always remembering the right place you were because of how points and windows work. An indirect buffer points to a base buffer that you clone from; it is the same underlying text, but everything else is separate (like major mode)

New variable 'window-state-normalize-buffer-name'.
When bound to non-nil, 'window-state-get' will normalize 'uniquify'
managed buffer names by removing 'uniquify' prefixes and suffixes.  This
helps to restore window buffers across Emacs sessions.
New action alist entry 'this-command' for 'display-buffer'.
You can use this in 'display-buffer-alist' to match buffers displayed
during the execution of particular commands.

That’s really cool, but will it work well if you trigger stuff through stuff like magit’s gnarly dispatchers or orgs’?

New command 'other-window-backward' ('C-x O').
This moves in the opposite direction of 'other-window' and is for its
default keybinding consistent with 'repeat-mode'.

No more need for using the negative prefix argument to go backwards.

New functions 'combine-windows' and 'uncombine-window'.
'combine-windows' is useful to make a new parent window for several
adjacent windows and subsequently operate on that parent.
'uncombine-window' can then be used to restore the window configuration
to the state it had before running 'combine-windows'.

I wonder how this fits into atomic windows which are another way of ‘combining’ windows. I am sure there is a substantial difference, perhaps because this one is not tied at all to display-buffer-alist.

New function 'window-cursor-info'.
This function returns a vector of pixel-level information about the
physical cursor in a given window, including its type, coordinates,
dimensions, and ascent.

Frames

New function 'frame-deletable-p'.
If this function returns nil, the following call to 'delete-frame' might
fail to delete its argument FRAME or might signal an error.  It is
therefore advisable to use this function as part of a condition that
determines whether to call 'delete-frame'.
New function 'frame-use-time'.
This function is the frame equivalent of the function 'window-use-time'
for a window.  The result is the 'window-use-time' of the frame's most
recently used window.
New functions 'get-mru-frames' and 'get-mru-frame'.
'get-mru-frames' returns a list of frames sorted by their most recent
use time, among all frames, or among those visible or iconified on the
same terminal as the selected frame.  Child frames can be excluded.  A
single frame can be excluded (e.g. the selected frame).  'get-mru-frame'
returns the single most recently used frame.
After deleting, 'delete-frame' now selects the most recently used frame.
Previously, after deleting a specified frame, 'delete-frame' would
select the oldest visible frame on the same terminal.  To revert to the
old behavior, set the new user option 'delete-frame-choose-selected'
to nil.

I am not a massive frame user as I never found the customization to make it work the way I liked it worth the effort, even though I do also use a tiling WM. But returning to the last seen frame does seem like an odd thing to only add now; this will no doubt restore the same of people who prefer frame-only approaches to windows.

New value 'force' for user option 'frame-inhibit-implied-resize'.
This will inhibit implied resizing while a new frame is made.  It can be
useful on tiling window managers where the initial frame size should be
specified by external means.
New user option 'alter-fullscreen-frames'.
This option is useful to maintain a consistent state when attempting to
resize fullscreen frames.  It defaults to 'inhibit' on NS builds which
means that a fullscreen frame will not change size.  It defaults to nil
everywhere else, which means that the window manager is supposed to
either resize the frame and change the fullscreen status accordingly, or
keep the frame size unchanged.  The value t means to first reset the
fullscreen status and then resize the frame.
New functions to set frame size and position in one compound step.
'set-frame-size-and-position' sets the new size and position of a frame
in one compound step.  Both size and position can be specified as with
the corresponding frame parameters 'width', 'height', 'left' and 'top'.
'set-frame-size-and-position-pixelwise' is similar but has a more
restricted set of values for specifying size and position.
New commands 'split-frame' and 'merge-frames'.
'split-frame' moves a specified number of windows from an existing frame
to a newly-created frame.  'merge-frames' merges all windows from two
frames into one of these frames and deletes the other one.
Frames can now be renamed to "F<number>" on text terminals.
Unlike with other frame names, an attempt to rename to "F<number>"
signals an error when a frame of that name already exists.

As I mentioned earlier frames in terminal Emacs are really just another way of doing a window configuration / screen-style “window pane”.

New frame parameters 'cloned-from' and 'undeleted'.
The frame parameter 'cloned-from' is set to the frame from which the new
frame is cloned using the command 'clone-frame'.

The frame parameter 'undeleted' is set to t when a frame is undeleted
using the command 'undelete-frame'.

These are useful if you need to detect a cloned or undeleted frame in
hooks like 'after-make-frame-functions' and
'server-after-make-frame-hook'.
Frames now have unique ids and the new function 'frame-id'.
Each non-tooltip frame is assigned a unique integer id.  This allows you
to unambiguously identify frames even if they share the same name or
title.  When 'undelete-frame-mode' is enabled, each deleted frame's id
is stored for resurrection.  The function 'frame-id' returns a frame's
id (in C, use the frame struct member 'id').
New commands 'select-frame-by-id', 'undelete-frame-by-id'.
The command 'select-frame-by-id' selects a frame by ID and undeletes it
if deleted.  The command 'undelete-frame-by-id' undeletes a frame by its
ID.  When called interactively, both functions prompt for an ID.

Mode Line

New definitions for mode line faces on dark backgrounds.
The faces 'mode-line' and 'mode-line-highlight' now have separate
definitions for dark backgrounds.  Previously, these two faces looked
the same with both light and dark background modes.  To get the previous
visuals for these two faces, customize them to have the colors "grey75"
and "grey40", respectively, regardless of the background mode.
New user option 'mode-line-collapse-minor-modes'.
This is a new, built-in facility to hide minor mode lighters.  If
non-nil, minor mode lighters on the mode line are collapsed into a
single button.  The value can also be a list to specify minor mode
lighters to hide or show.  The default value is nil, which retains the
previous behavior of showing all minor mode lighters.

One of my earliest articles was Hiding and replacing modeline strings with clean-mode-line. It’s been an issue as long as mode authors have had a say in how loud their mode line lighters should be.

Glad it is finally built in. No word on whether it plugs into :delight / :diminish in use-package.

New user option 'mode-line-modes-delimiters'.
This option allows changing or removing the delimiters shown around
the major mode and list of minor modes in the mode line.  The default
retains the existing behavior of using parentheses.
New minor mode 'mode-line-invisible-mode'.
This minor mode makes the mode line of the current buffer invisible.
The command 'mode-line-invisible-mode' toggles the visibility of the
current-buffer's mode line.  The default is to show the mode line of
every buffer.

People do ask for this all the time, so it’s good to see a built in feature to do this instead of all the hacky tricks people got up to before.

The standard mode line no longer specifies minimum widths.
The default values for the 'mode-line-position' variable and
'mode-line-format' user option no longer specify any minimum widths.  If
you use a proportional font for your mode line, you may need to
customize the values of these variables to include minimum widths again.

Tab Bars and Tab Lines

Tab bars are window configurations you switch between; tab lines are like browser tabs that point to buffers in the window.

New commands 'split-tab' and 'merge-tabs'.
'split-tab' moves a specified number of windows from an existing tab to
a newly created tab.  'merge-tabs' merges all windows from two tabs into
one of these tabs, and closes the other.
New abnormal hook 'tab-bar-auto-width-functions'.
This hook allows you to control which tab-bar tabs are auto-resized.
'mouse-face' properties are now supported on the 'tab-bar'.
'tab-bar' tab buttons are now highlighted when the mouse pointer
hovers over them.  You can customize the new face
'tab-bar-tab-highlight'.
New abnormal hook 'tab-bar-post-undo-close-tab-functions'.
This hook allows you to operate on a reopened tab.

This is useful when you define custom tab parameters that may need
adjustment when a tab is restored, without resorting to advice.

I do actually end up closing tab bar tabs by mistake quite often. And it has had an undo feature C-x t u to fix screwups like that for a long time now.

Tabs are now closed upon releasing the middle mouse button.
Previously, closing the tab-bar's tabs occurred upon pressing the
button.
New user option 'tab-bar-define-keys'.
This controls which key bindings tab-bar creates.  Values are t, the
default, which defines all keys and is backwards compatible, 'numeric'
for tab number selection only, 'tab' for the 'TAB' and 'S-TAB' keys
only, and nil for none.

This is useful to avoid key binding conflicts, such as when folding in
outline mode using 'TAB' keys, or when a user wants to define her own
tab-bar keys without first having to remove the defaults.
New variable 'tab-bar-format-tab-help-text-function'.
This variable may be overridden with a user-provided function to
customize the help text for tabs displayed on the tab-bar.  Help text is
normally shown in the echo area or via tooltips.  See the variable's
docstring for the arguments passed to a help-text function.
New variable 'tab-bar-truncate'.
When non-nil, it truncates the tab bar, and therefore prevents
wrapping and resizing the tab bar to more than one line.
New user option 'tab-line-define-keys'.
When t, the default, it redefines window buffer switching keys
such as 'C-x <LEFT>' and 'C-x <RIGHT>' to tab-line specific variants
for switching tabs.
New command 'tab-line-move-tab-forward' ('C-x M-<RIGHT>').
Together with the new command 'tab-line-move-tab-backward'
('C-x M-<LEFT>'), it can be used to move the current tab
on the tab line to a different position.
New command 'tab-line-close-other-tabs'.
It is bound to the tab's context menu item "Close other tabs".
New user option 'tab-line-exclude-buffers'.
This user option controls where 'tab-line-mode' should not be enabled in
a buffer.  The value must be a condition which is passed to
'buffer-match-p'.
New user option 'tab-line-close-modified-button-show'.
With this user option, if non-nil (the default), the tab close button
will change its appearance if the tab's selected buffer has been
modified.
New user option 'tab-line-tabs-window-buffers-filter-function'.
This user option controls which buffers should appear in the tab line.
By default, this is set so as to not filter out any buffers.

Aha this is useful. One problem with tab line is that it’s quite indiscriminate; it won’t show hidden buffers by default (they start with a whitespace) obviously but it’s still a bit heavyhanded. Now you can at least limit what you see.

New faces 'tab-line-active' and 'tab-line-inactive'.
These inherit from the 'tab-line' face, but the faces actually used on
the tab lines are now these two: the selected window uses
'tab-line-active', and non-selected windows use 'tab-line-inactive'.

Help

New binding 'C-h u' for 'apropos-user-option'.
IDLWAVE has moved to GNU ELPA.
The version included with Emacs is out-of-date, and is now marked as
obsolete.  Use 'list-packages' to install the 'idlwave' package from GNU
ELPA instead.
New faces 'header-line-active' and 'header-line-inactive'.
These inherit from the 'header-line' face, but the faces actually used
on the header lines are now these two: the selected window uses
'header-line-active', and non-selected windows use 'header-line-inactive'.

Useful; header line is an immovable header that appears at the top of a window. It is commonly used for things like column headers in tables, as seen in M-x list-packages.

In 'customize-face', the "Font family" attribute now supports completion.

Heavenly manna indeed. I have long argued that all this complex futzing around with .Xresources, frame-setting faces and all manner of complicated ways of setting your default font is a bad habit and that M-x customize-face RET default RET is the simplest and most effective compared to the alternatives. Well, you don’t have to guess at the names of fonts any more! Emacs is finally capable of auto completing them. Excellent change.

'process-adaptive-read-buffering' is now nil by default.
Setting this variable to a non-nil value reduces performance and leads
to wrong results in some cases.  We believe that it is no longer useful;
please contact us if you still need it for some reason.

Another toggle switch to maybe possibly potentially speed Emacs up a tad; it’s part of a growing list of these magic feature toggles that may or may not have adverse consequences down the road. I checked my Emacs and mine is set to nil. I do not recall why I set it to nil, nor can I remember when.

'byte-compile-cond-use-jump-table' is now obsolete.
Modified settings for an enabled theme now apply immediately.
Evaluating a 'custom-theme-set-faces' or 'custom-theme-set-variables'
call for an enabled theme causes the settings to apply immediately,
without a need to re-load the theme.
'describe-variable' now automatically says if 'setopt' is needed.
If a user option has a defcustom ':set' function, users will normally
need to set it with 'setopt' for it to take an effect.  If the docstring
doesn't already mention 'setopt', the 'describe-variable' command will
now add a note about this automatically.

One of the greatest challenges in Emacs is convincing people - including yours truly - to stop using setq to bind values to global/customizable variables. Emacs’s customize system – defined as anything you can edit with M-x customize – supports edge triggers: code that runs when one of its variables change. It was once uncommon enough that nobody really had to worry; more and more things in Emacs lean into this system though.

The primary reason people use setq is that it just kinda-sorta works (notwithstanding the edge-trigger) but also because the proper way to set variables via customize’s machinery is custom-set-variables which is an obnoxious utility function that not only has a bad prefix namespace custom vs customize but also it’s just so damn long to type.

So nobody bothered to use it. Emacs 29.1 added setopt which automatically does all the heavy lifting and it’s a drop-in replacement for setq.

New user option 'eldoc-help-at-pt' to show help at point via ElDoc.
When enabled, display the 'help-at-pt-kbd-string' via ElDoc.  This
setting is an alternative to 'help-at-pt-display-when-idle'.

Eldoc is Emacs’s help/document/code argument lookup system that actives when you move point around. It relies on a complex timing machinery to trigger the help. Forcing it to appear at point (even if that is nearly always your current point) is a great utility function. Now you can have eldoc without the timer: bind it to a key when you need it and off it goes.

New user option 'native-comp-async-on-battery-power'.
Customize this to nil to disable starting new asynchronous native
compilations while AC power is not connected.

Somewhere someone with a laptop more dinged-up than Zildjian cymbal lost their last 5% of battery to native comp and furiously decided to solve this problem once and for all.

New user option 'show-paren-not-in-comments-or-strings'.
If this option is non-nil, it tells 'show-paren-mode' not to highlight
parens inside comments and strings.  If set to 'all', 'show-paren-mode'
will never highlight parens that are inside comments or strings.  If set
to 'on-mismatch', mismatched parens inside comments and strings will not
be highlighted.  If set to nil (the default), highlight parens wherever
they are.

Show paren of course is Emacs paren highlighter, though its name today is doing it a disservice as it is designed to highlight like terms like braces, string quotes or things like begin and end terms.

New user option 'view-lossage-auto-refresh'.
If this option is non-nil, the lossage buffer of 'view-lossage' will be
refreshed automatically for each new input keystroke and command
invoked.

Lossage is C-h l and it reflects the last N number of typed keys in your Emacs. With auto-refresh enabled you can simulate basic version of those “keypress overlays” people use in streaming videos. Useful for gifs too!

Change in SVG foreground color handling.
SVG images no longer have the 'fill' attribute set to the value of
':foreground' or the current text foreground color.  The 'currentcolor'
CSS attribute is still set, as before.

This change should result in more consistent display of SVG images.

To use the ':foreground' or current text color ensure the 'fill' attribute
in the SVG is set to 'currentcolor', or set the image spec's ':css'
value to 'svg {fill: currentcolor;}'.
Errors signaled by 'emacsclient' connections can now enter the debugger.
If 'debug-on-error' is non-nil, errors signaled by Lisp programs
executed by 'emacsclient' connections will now enter the Lisp debugger
and show a backtrace.  If 'debug-on-error' is nil, these errors will be
sent to 'emacsclient', as before, and will be displayed on the terminal
from which 'emacsclient' was invoked.
Empty string arguments to emacsclient are no longer ignored.
Emacs previously discarded arguments to emacsclient of zero length, such
as in 'emacsclient --eval "(length (pop server-eval-args-left))" ""'.
These are no longer discarded.

Huh. That may have explained some weird issues I’ve run into calling evals into emacsclient over the years. I always just assumed I did something wrong!

Emacs now uses the 'setrgbf' and 'setrgbb' terminfo capabilities.
Emacs now uses 24-bit colors on terminals that support the 'setrgbf' and
'setrgbb' user-defined terminfo capabilities.  These are supported by
more terminals and applications than the old capabilities, 'setf24' and
'setb24', which are now obsolete.

I’m not an expert on termcaps so I cannot say what these caps offer people, but Emacs already supports 24-bit if you did not know. In fact, you can just set the environment variable COLORTERM=truecolor to force Emacs to treat your terminal as 24-bit capable.

New user option 'xterm-update-cursor' to update cursor display on TTYs.
When enabled, Emacs sends Xterm escape sequences on Xterm-compatible
terminals to update the cursor's appearance.  Emacs can update the
cursor's shape and color.  For example, if you use a purple bar cursor
on graphical displays then when this option is enabled Emacs will use a
purple bar cursor on compatible terminals as well.  See the Info node
"(emacs) Cursor Display" for more information.

Neat. The highlight of course being that Emacs has multiple cursor styles. See M-x customize-option cursor-type.

New command 'copy-theme-options'.
You can use this command to copy options from a theme into your user
configuration.
New user option 'multiple-terminals-merge-keyboards'.
Customizing this option to non-nil disables entering single-keyboard
mode in most cases in which Emacs would by default enter that mode.
This can make things work better for some cases of X forwarding; see the
Info node "(emacs) Multiple Displays".
Emacs now comes with Org v9.8.
See the file "etc/ORG-NEWS" for user-visible changes in Org.
New user option 'compilation-search-extra-path'.
compile.el will now use paths specified in both
'compilation-search-extra-path' and 'compilation-search-path' when
searching.  'compilation-search-extra-path' is consulted first.  One
possible use case for this option is to add new search paths on a
per-project basis with directory-local variables.

Editing Changes in Emacs 31.1

Commands for keyboard translation.
'key-translate' is now interactive.  It prompts for a key to translate
from, and another to translate to, and sets 'keyboard-translate-table'.
The new command 'key-translate-remove' prompts for a key/translation
pair, with 'completing-read', and removes the translation from the
translation table.

My article on Mastering Key Bindings in Emacs is a good place to start on key bindings.

But it is not a good article to understand keyboard translation. Even I am not crazy enough to try to write an article that explains that. I spent nearly a full day trying to trace a weird keyboard translation issue in Combobulate that only manifests in some terminals with some key bindings and only in Combobulate’s complicated “carousel interface”.

The translation system – and how it plugs into your OS, tty, etc. – and trying to fully understand it will make you go crazy.

Internationalization

Emacs now supports Unicode Standard version 17.0.
New input method 'greek-polytonic'.
This input method has support for polytonic and archaic Greek
characters.
New language environment and input method for Tifinagh.
The Tifinagh script is used to write the Berber languages.
New input methods for Northern Iroquoian languages.
Input methods are now implemented for Haudenosaunee languages in the
Northern Iroquoian language family: 'mohawk-postfix' (Mohawk
[Kanien’kéha / Kanyen’kéha / Onkwehonwehnéha]), 'oneida-postfix' (Oneida
[Onʌyote’a·ká· / Onyota’a:ká: / Ukwehuwehnéha]), 'cayuga-postfix'
(Cayuga [Gayogo̱ho:nǫhnéha:ˀ]), 'onondaga-postfix' (Onondaga
[Onųdaʔgegáʔ]), 'seneca-postfix' (Seneca [Onödowá’ga:’]), and
'tuscarora-postfix' (Tuscarora [Skarù·ręʔ]).  Additionally, there is a
general-purpose 'haudenosaunee-postfix' input method to facilitate
writing in the orthographies of the six languages simultaneously.
New input methods for languages based on Burmese.
These include: Burmese, Burmese (visual order), Shan, and Mon.
New language environment and input methods for Syriac languages.
A new language environment for languages that use the Syriac script:
Classical Syriac, Aramaic, and others.  There are two new input methods
for these languages: Syriac and Syriac (phonetic).
'visual-wrap-prefix-mode' now supports variable-pitch fonts.
When using 'visual-wrap-prefix-mode' in buffers with variable-pitch
fonts, the wrapped text will now be lined up correctly so that it is
exactly below the text after the prefix on the first line.

Visual wrap prefix mode, not to be confused with truncating long lines (M-x toggle-truncate-lines) or visual line mode (M-x visual-line-mode) deals with text that overflow one line and how it is wrapped. I’m not going to get into which one does what; try them out in turn and see which one works best.

New commands 'unix-word-rubout' and 'unix-filename-rubout'.
Unix-words are words separated by whitespace regardless of the buffer's
syntax table.  In a Unix terminal or shell, 'C-w' kills by Unix-word.
The new commands 'unix-word-rubout' and 'unix-filename-rubout' allow
you to bind keys to operate more similarly to such a terminal.

Emacs having it all, you’d think – especially given its roots – it would already a panoply of methods for doing this.

Honestly even if you’re a die-hard fan of this method of killing I would unlearn that habit. Emacs’s combined system of moving-editing-killing by word and so forth is far superior.

New user option 'kill-region-dwim'.
This option, if non-nil, modifies the fall-back behavior of
'kill-region' ('C-w') if no region is active, and will kill the last
word instead of raising an error.  If you have disabled Transient Mark
mode you might prefer to bind 'unix-word-rubout' to a key instead.

No see this is not the right way forward. C-w and M-w absent a region should kill the current line and copy it respectively. That is a far more sensible approach than fall back to dumb non-TMM behavior as it does pre-Emacs 31.

Here is the code I stole from Emacswiki 20+ years ago to do exactly this. One of my favorite Emacs life hacks:

(defadvice kill-ring-save (before slick-copy activate compile)
  "When called interactively with no active region, copy a single line instead."
  (interactive
   (if mark-active (list (region-beginning) (region-end))
     (list (line-beginning-position)
           (line-beginning-position 2)))))

(defadvice kill-region (before slick-cut activate compile)
  "When called interactively with no active region, kill a single line instead."
  (interactive
   (if mark-active (list (region-beginning) (region-end))
     (list (line-beginning-position)
           (line-beginning-position 2)))))
New user option 'delete-pair-push-mark'.
This option, if non-nil, makes 'delete-pair' push a mark at the end of
the region enclosed by the deleted delimiters.  This makes it easy to
act on that region.  For example, you can highlight it using 'C-x C-x'.

Now this is useful. M-x delete-pair (typically not bound to anything) is a long line of helpful editing commands that, alongside things like M-x raise-sexp do not get their time in the sun as they are unbound by default.

One common problem with deleting a little bit here and a little bit over there is exactly that Emacs does not make it easy to capture the extent over the change that took place. Pushing a mark (Emacs’s little point beacon system) is an obvious choice here.

Electric Pair mode

Electric Pair mode can now pair multiple delimiters at once.
You can now insert or wrap text with multiple sets of parentheses and
other matching delimiters at once with Electric Pair mode, by providing
a prefix argument when inserting one of the delimiters.

Neat but I will never remember this. I do not subscribe to the school of “think before you type” where you count your steps and then use the right numeric argument.

Electric Pair mode now supports multi-character paired delimiters.
'electric-pair-pairs' and 'electric-pair-text-pairs' now allow using
strings for multi-character paired delimiters.

To use this, add a list to both electric pair user options: '("/*" . "*/")'.

You can also specify that an extra space should be inserted after the
first string, like this: '("/*" " */" t)'.

Electric pair is a god-send in that it is a million times better than the hacky skeleton template system most of us lugged around before it became standard in Emacs a long time ago. But once again this is the sort of functionality you’d expect it could already do out of the box. Emacs’s own core is written in C where /* */ is used all the time for comments!

New user option 'electric-indent-actions'.
This user option specifies a list of actions to reindent.  The possible
elements for this list are: 'yank' to reindent the yanked text, and
'before-save' to indent the whole buffer before saving it.

As always, how well this works in whitespace-sensitive languages remains to be seen. I know from experience how difficult it is to corral the indentation engines into handling this well; so for Python and such like it really can’t do much more than fixed indentation.

You can now use 'M-~' during 'C-x s' ('save-some-buffers').
Typing 'M-~' while saving some buffers means not to save the buffer and
also to mark it as unmodified.  This is an alternative way to mark a
buffer as unmodified which doesn't require switching to that buffer.

M-~ generally speaking is a key binding for the “is this buffer modified in Emacs” flag. Most people do not know about this obscure command.

New minor mode 'delete-selection-local-mode'.
This mode sets 'delete-selection-mode' buffer-locally.  This can be
useful for enabling or disabling the features of 'delete-selection-mode'
based on the state of the buffer, such as for the different states of
modal editing packages.

Delete selection mode is how most editors work: you select text, and if you start typing, it is deleted and replaced with what you just typed. In Emacs, you have to enable a mode to get this functionality.

New user option 'exchange-point-and-mark-highlight-region'.
When set to nil, this modifies 'exchange-point-and-mark' so that it doesn't
activate the mark if it is not already active.
The default value is t, which retains the old behavior.
This variable has no effect when Transient Mark mode is off.

I have been cargo culting an advice for this exact misbehavior around for the better part of 15 years. See Fixing the mark commands in transient mark mode.

C-x C-x is another hidden gem in Emacs. Emacs has point (your cursor) and mark (a beacon somewhere in your buffer) and back in the good old days Emacs did not highlight text selections by default. You had to slum it with no visual aid at all: all you had was your mark and point. Sounds bad, but… actually it’s not that big a deal. You generally know where you started your marking and it runs to your point.

Transient-mark-mode (obviously enabled by default… nowadays…) made it so you can see your region selection. However… it did break some useful features in weird ways. When you do M-< and such to move to the beginning or end of a buffer or C-s to start isearch Emacs will set the mark, meaning it effectively sets the start of where a region “is”.

Power users would pick a spot, search or jump or whatever to where they needed to go, and execute a “region” command that would work from where ever their point landed, say the match they picked in C-s for example, all the way back from where they were when they first called C-s. Bam — that was how you acted on a region.

So C-x C-x had really, really important purpose back then: you could swap between the mark and point by exchanging their positions. Great if you just wanted to double check the region you were going to act on; or maybe toggle between where you were/are.

So when you then execute a command (like kill-region) Emacs’s code would – slavishly, laboriously, I might add – always ensure the point and mark were the right way ’round internally, and then just act on that range.

Add highlighting into the mix and now you have a dumb rectangle that follows you along everywhere because C-x C-x would ALSO activate your region marker (C-SPC). So dumb, because it screws up keyboard macros and all sorts. Bleh.

So 15 years ago I made it not do that. And you should, too.

You can now use 'M-s t' to swap FROM and TO during 'query-replace'.
Likewise during 'query-replace-regexp'.  The original binding of 'M-s'
('next-matching-history-element') is now available on 'M-s M-s' or 'M-s
s' for query replace minibuffer input.

Hah. Neat. A few versions ago they made it so various query replace functions emit a special -> sigil to indicate from/to. You could edit the whole string and move stuff around, and now they’ve just added a nice shortcut to make it even easier.

New commands for filling text using semantic linefeeds.
The new command 'fill-paragraph-semlf' fills a paragraph of text using
"semantic linefeeds", where a newline is inserted after every sentence.
The new command 'fill-region-as-paragraph-semlf' fills a region of text
using semantic linefeeds, as if the region were a single paragraph.  You
can set the variable 'fill-region-as-paragraph-function' to the value
'fill-region-as-paragraph-semlf' to make commands like 'fill-paragraph'
and 'fill-region' fill text using semantic linefeeds.
Temporary files are named differently when 'file-precious-flag' is set.
When the user option 'file-precious-flag' is set to a non-nil value,
Emacs now names the temporary file it creates while saving buffers using
the original file name with ".tmp" appended.  Thus, if saving the buffer
fails for some reason, and the temporary file is not renamed back to the
original file's name, and you can easily identify which file's saving
failed.
'C-u C-x .' clears the fill prefix.
You can now use 'C-u C-x .' to clear the fill prefix, similarly to how
you could already use 'C-u C-x C-n' to clear the goal column.

Fill prefix C-x . looks at where your point is on a line and designates everything from point to the beginning of the line as the fill prefix. When you type M-q on a long paragraph it’ll reflow it and insert the fill prefix for each new line. Use cases include prefixing email paragraphs with > or what have you.

Now you can reset it without having to move point to the bol.

New prefix argument for 'C-/' in Dired and Proced modes.
The Dired and Proced major modes bind mode-specific undo commands to the
same keys to which 'undo' is globally bound, 'C-/', 'C-_' and 'C-x u'.
These commands did not previously accept a prefix argument.
Now a numeric prefix argument specifies a repeat count, just like it
already did for 'undo'.
New minor mode 'center-line-mode'.
This mode keeps modified lines centered horizontally according to the
value of 'fill-column', by calling 'center-line' on each non-empty line
of the modified region.
New command 'unfill-paragraph'.
This is the inverse of 'M-q' ('fill-paragraph').

I am pretty sure org mode or something has had this for millennia buried in its codebase somewhere. But yeah, nice I guess.

Changes in Specialized Modes and Packages in Emacs 31.1

Project

Project is Emacs’s latest project management suite in a long line of project suites that ship with Emacs already. It’s nice, you should use it.

New command 'project-root-find-file'.
It is equivalent to running 'project-any-command' with 'find-file'.
New command 'project-customize-dirlocals'.
It is equivalent to running 'project-any-command' with
'customize-dirlocals'.
Improved prompt for 'project-switch-project'.
The prompt now displays the project on which to invoke a command.
'project-prompter' values may be called with up to three arguments.
These allow callers of the value of 'project-prompter' to specify a
prompt string; prompt the user to choose between a subset of all the
known projects; and disallow returning arbitrary directories.
See the docstring of 'project-prompter' for a full specification of
these new optional arguments.
'project-current' has a new optional argument, MAYBE-PROMPT.
If 'project-current' is called with this argument non-nil, then it is
passed to the 'project-prompter' to use as a prompt string.
Callers can use this to indicate the reason for which or context in
which Emacs should ask the user to select a project.
New command 'project-find-matching-buffer'.
It can be used when switching between projects with similar file trees
(such as Git worktrees of the same repository).  It supports being
invoked standalone or from the 'project-switch-commands' dispatch menu.
See also the 'C-x v w w' ('vc-switch-working-tree') command, below.

That is a nice bit of symmetry. I usually switch between worktrees in magit with % g, but I am happy to see project gaining some form of generic support for this concept.

New variable 'project-find-matching-buffer-function'.
Major modes can set this to major mode-specific functions to control how
'project-find-matching-buffer' finds matching buffers.
New user option 'project-list-exclude'.
This user option describes projects that should always be skipped by
'project-remember-project'.
New user option 'project-prune-zombie-projects'.
This user option controls the automatic deletion of projects from
'project-list-file', when prompting for a project, that cannot be
accessed.  The value must be an alist where each element is of the
form:

    (WHEN . PREDICATE)

where WHEN specifies where the deletion will be performed, and PREDICATE
is a function which takes one argument, and must return non-nil if the
project should be removed.
New command 'project-save-some-buffers' bound to 'C-x p C-x s'.
This is like 'C-x s', but only for this project's buffers.
'project-remember-project' is now interactive.
'project-shell' and 'project-eshell' support numeric prefix buffer naming.
They now accept numeric prefix arguments to select or create numbered
shell sessions.  For example, 'C-2 C-x p s' switches to or creates a
buffer named "*name-of-project-shell<2>*".  By comparison, a plain
universal argument as in 'C-u C-x p s' always creates a new session.
'project-switch-to-buffer' re-uniquifies buffer names while prompting.
When 'uniquify-buffer-name-style' is non-nil, 'project-switch-to-buffer'
changes the buffer names to only make them unique within the given
project, during completion.  That makes some items shorter.
'project-switch-to-buffer' uses 'project-buffer' as completion category.
The category defaults are the same as for 'buffer', but any user
customizations need to be re-added.
'project-mode-line' can now show the project name only for local files.
If the value of 'project-mode-line' is 'non-remote', project name and
the Project menu will be shown on the mode line only for projects with
local files.

One common source of performance problems in people’s riced Emacs configs is the mode line, believe it or not. It gets re-rendered more often than you think, and a lot of people cram expensive junk into it that require a file system round-trip. Fine when you’re just looking at stuff on your macbook. But over TRAMP? It’ll kill your performance.

The VC-aware project backend caches the current project and its name.
The duration for which the values are cached depends on whether it is
called from a 'non-essential' context, and is determined by the variables
'project-vc-cache-timeout' and 'project-vc-non-essential-cache-timeout'.

Network Security Manager (NSM)

NSM warns about TLS 1.1 by default.
It has been deprecated by RFC 8996, published in 2021.
NSM warns about DHE and RSA key exchange by default.
Emacs now warns about ephemeral Diffie-Hellman key exchange, and static
RSA key exchange, also when 'network-security-level' is customized to
its default 'medium' value.

Etags

Ctags, Etags, etc. are all a family of source code indexers that pull out semantically important stuff like function names and their precise location

New command-line options for handling unrecognized programming languages.
The new command-line option '--no-fallback-lang' disables attempts to
parse as Fortran or C/C++ files whose programming language 'etags' could
not determine.  This allows avoiding false positives and reduces the
time required to scan directories with many such files.  Another new
option '--no-empty-file-entries' disables generation of file entries in
tags tables for files in which no tags were found.

Delete Selection mode

New face 'delete-selection-replacement' for the replacement text.
This comes with a change to how we track what is considered "the
replacement text", which should be more robust, and is made more clear
by the highlighting.

Editorconfig

'editorconfig-apply' is declared obsolete.
You can now use 'editorconfig-display-current-properties' without having
to call 'editorconfig-apply'.

Auth Source

Auth source is Emacs’s declarative secret store wrapper. I’ve written about it: Keeping Secrets in Emacs with GnuPG and Auth Sources

Non-existing or empty files in 'auth-sources' are ignored.
File-based data stores are ignored in 'auth-sources' if the underlying
data file does not exist.  This is relevant if a new secret is stored in
such a file; the first usable entry of 'auth-sources' is selected as the
target file.  If you want files that do not exist to also be selected,
customize the user option 'auth-source-ignore-non-existing-file' to nil.
'auth-sources' set to nil means use only the password cache.

Autoinsert

Autoinsert – not to be confused with Skeletons, Abbrev, Tempo, etc. – is used to insert text templates when you create new files that match certain file patterns.

New condition for 'auto-insert-alist'.
'auto-insert-alist' can now contain predicates taking no argument as
conditions.  These types of conditions should be declared with
'(predicate FUNCTION)'.  This allows triggering 'auto-insert' with
finer-grained control.

Register

Registers are ephemeral stores of text snippets, window/frame configurations, point locations and much more. They’re designed for fast keyboard access and I use them all the time, especially with keyboard macros.

New commands 'buffer-to-register' and 'file-to-register'.
These allow users to interactively store files and buffers in registers.
Killed buffers stored in a register using 'buffer-to-register' are
automatically converted to a file-query value if the buffer was visiting
a file.

So a bit like bookmarks I guess, which are permanent stores of references to files, info manual locations and much more.

The "*Register Preview*" buffer shows only suitable registers.
That was already the case for the "fancy" UI but is now also true in
the default UI you get, i.e., when 'register-use-preview' is 'traditional'.
The "*Register Preview*" buffer shows sorted items.

Tree-sitter

Tree-sitter is a fancy parsing suite for structured text like code, markdown and so on. I have written an ungodly amount about tree-sitter and also code that interacts with tree-sitter.

See How to Get Started with Tree-Sitter, Combobulate: Structured Movement and Editing with Tree-Sitter, etc. etc. etc.

New user option 'treesit-enabled-modes'.
You can customize it either to t to enable all available
tree-sitter-based modes, or to select a list of tree-sitter-based modes
to enable.  Depending on your customization, it modifies the variable
'major-mode-remap-alist' from the corresponding variable
'treesit-major-mode-remap-alist' prepared by tree-sitter-based mode
packages.

I’ve long complained about the remap system and the fact that tree-sitter modes are often just bare bones reimplementations of their original, non-TS-enabled cousins. But configuring the remap alist was a hassle for beginners so I am happy to see some movement here towards simplifying it.

New user option 'treesit-auto-install-grammar'.
It controls the automatic installation of tree-sitter grammar libraries
needed for tree-sitter-based modes, if these grammar libraries are not
available when such modes are turned on.

About time. Having to lug around dozens of complex git refs to the right, magic version of a tree-sitter library was positively terrible UX for everyone.

Tree-sitter adoption is a tiny fraction of what it should be because of the decision to refuse to keep text strings to github release tags that match precisely what each major mode needs to work.

You see, the poindexters who build tree-sitter the library and the grammars break compatibility all. the. time. So you can’t just pull a new version and expect stuff to work — it will NOT.

This is a good thing indeed. But it’s taken several years and what I imagine are a large amount of reported user bugs for this change to take place.

'treesit-extra-load-path' is now a customizable user option.
The first directory in the list is used as the default directory
to install the language grammar when 'treesit-auto-install-grammar'
is 'ask', 'ask-dir' or 'always'.
'treesit-language-source-alist' supports keywords.
The language and URL are mandatory, but remaining data can use keywords like

    (json "https://github.com/tree-sitter/tree-sitter-json" :commit "4d770d3")
The file treesit-x.el defines a number of simple tree-sitter modes.
Using the new macro 'define-treesit-generic-mode', generic modes are
defined including, but not limited to, 'gitattributes-generic-ts-mode'.
Visiting a file in such mode asks for confirmation before installing
its tree-sitter grammar.  Then it highlights the visited file
according to the syntax defined by the grammar.

Excellent. We already have define-generic-mode in generic.el for quickly throwing a major mode together for simple file formats. Here we can leverage the .scm files tree-sitter grammars often ship with that provide out of the box suggestions for syntax highlighting. Honestly our system in Emacs is way better but… someone has to Write a Tree-Sitter Major Mode first properly to do it the right way. So this is a nice compromise

Indirect buffers can have their own parser list.
Before, indirect buffers share their base buffer's parser list and
parsers.  Now they can have their own parser list.

Useful; but I have long complained about the complex and inadequate treatment of multiple parsers in the same buffer. No, the range system is not good enough for complex cases as it depends on queries to work. This won’t help with that, but it’ll help with out-of-buffer rendering which is something.

New variable 'treesit-language-remap-alist'.
This variable allows a user to remap one language into another, such
that creating a parser for language A actually creates a parser for
language B.  By extension, any font-lock rules or indentation rules for
language A will be applied to language B instead.

This is useful for reusing font-lock rules and indentation rules of
language A for language B when language B is a strict superset of
language A.
New accessor functions for each setting in 'treesit-font-lock-settings'.
Now you can access a setting's query, feature, enable flag, and override
flag by 'treesit-font-lock-setting-query',
'treesit-font-lock-setting-feature', 'treesit-font-lock-setting-enable',
and 'treesit-font-lock-setting-override'.
New tree-sitter thing 'list'.
Unlike the existing thing 'sexp' that includes both lists and atoms,
'list' makes only lists be navigated by 'forward-sexp'.
The new command 'treesit-forward-sexp-list' uses 'list'
to move across lists.  But to move across atoms inside the list
it uses 'forward-sexp-default-function'.
New tree-sitter based functions for moving by lists.
If a major mode defines 'list' in 'treesit-thing-settings',
tree-sitter setup for these modes sets 'forward-list-function' to
'treesit-forward-list', 'up-list-function' to 'treesit-up-list', and
'down-list-function' to 'treesit-down-list'.  This enables the
'forward-list', 'up-list', and 'down-list' motion commands for those
modes.
New command 'treesit-cycle-sexp-thing'.
It cycles the type of navigation for commands that move across sexp's
and lists, such as 'treesit-forward-sexp', 'treesit-forward-list',
'treesit-down-list', and 'treesit-up-list'.  The type can be either
'list', the default, or 'sexp'.
With the default 'list' type, these commands move using syntax tables for
symbols and using the thing 'list' for lists.
With the 'sexp' type, these commands move across nodes defined by
the tree-sitter thing 'sexp' in 'treesit-thing-settings'.

So these changes are good in the sense that it is a marked improvement over the status quo before. Basically all the -sexp movement and editing commands were totally broken in TS-enabled modes because they had a really naive understanding of how -sexp commands work in basic Emacs that meant they didn’t do what you’d think they’d do in a tree-sitter-enabled major mode.

It’s not perfect (it never will be with a heuristic) but it’s better. See Combobulate: Structured Movement and Editing with Tree-Sitter for why.

Tree-sitter enabled modes now properly support 'show-paren-mode'.
They do that by letting 'show-paren-mode' use the results of parsing by
the tree-sitter library.  The new function 'treesit-show-paren-data' is
used to communicate the tree-sitter parsing results to 'show-paren-mode'.

Excellent.

Tree-sitter enabled modes now properly support 'hs-minor-mode'.
All commands from hideshow.el can selectively display blocks
defined by the new tree-sitter thing 'list'.
New tree-sitter thing 'comment'.
The new variable 'forward-comment-function' is set to the new function
'treesit-forward-comment' if a major mode defines the thing 'comment'.
New function 'treesit-query-eagerly-compiled-p'.
This function returns non-nil if a query was eagerly compiled.
New function 'treesit-query-source'.
This function returns the string or sexp source query of a compiled query.
New function 'treesit-language-display-name'.
This new function returns the display name of a language given the
language symbol.  For example, 'cpp' is translated to "C++".  A new
variable 'treesit-language-display-name-alist' holds the translations of
language symbols where that translation is not trivial.
New function 'treesit-merge-font-lock-feature-list'.
This function merges two tree-sitter font-lock feature lists.  It
returns a new font-lock feature list with no duplicates at the same
level.  It can be used to merge font-lock feature lists in a
multi-language major mode.

Another complaint of mine was that mode authors would hoard their tree-sitter font lock queries that you use to font lock a file. It means a major mode author can’t go “but I can have CSS and Ruby in this file” and then just pull the font lock queries from a variable from those major modes — it was not possible without introspecting a loaded buffer of each MM you wanted, which was… not great.

This merge feature does not solve that underlying problem but it does acknowledge at least that there are people out there building derivative modes that use multiple tree-sitter grammars. I mean, that’s the whole point of TS!

New function 'treesit-replace-font-lock-feature-settings'.
Given two tree-sitter font-lock settings, it replaces the feature in the
second font-lock settings with the same feature in the first font-lock
settings.  In a multi-language major mode it is sometimes necessary to
replace features from one of the major modes with others that are
better suited to the new multilingual context.

Yes, quite.

New variable 'treesit-aggregated-simple-imenu-settings'.
This variable allows major modes to setup Imenu for multiple languages.
New variable 'treesit-aggregated-outline-predicate'.
This variable allows major modes to setup 'outline-minor-mode'
for multiple languages.
New function 'treesit-simple-indent-add-rules'.
This new function makes it easier to customize indent rules for
tree-sitter modes.

I’m sure this is just wrapper around features already present in Stefan Monnier’s excellent SMIE (Simple-Minded Indentation Engine) that TS major modes typically use by default.

New function 'treesit-simple-indent-modify-rules'.
Given two tree-sitter indent rules, it replaces, adds, or prepends rules
in the old rules with new ones, then returns the modified rules.  In a
multi-language major mode it is sometimes necessary to modify rules from
one of the major modes to better suit the new multilingual context.
New variable 'treesit-simple-indent-override-rules'.
Users can customize this variable to add simple custom indentation rules
for tree-sitter major modes.
New variable 'treesit-languages-require-line-column-tracking'.
Now Emacs can optionally track line and column numbers for buffer edits
and send that information to tree-sitter parsers.  Parsers of languages
in this list will receive line and column information.  This is only
needed for very few languages.  So far only Haskell is known to need it.
New function 'treesit-tracking-line-column-p'.
New function to check if a buffer is tracking line and column for buffer
edits.
New function 'treesit-parser-tracking-line-column-p'.
New function to check if a parser is receiving line and column
information.
'treesit-language-at-point-function' is now optional.
Multi-language major modes can rely on the default return value from
'treesit-language-at' that uses the new function 'treesit-parsers-at'.
New function 'treesit-query-with-optional'.
When used in 'treesit-font-lock-rules', 'treesit-query-with-optional'
returns a default query plus the valid queries from a list of optional
queries.
New function 'treesit-query-with-fallback'.
When used in 'treesit-font-lock-rules', 'treesit-query-with-fallback'
selects the first valid query from a list.
Tree-sitter thing functions now work better with multiple parsers.
The following functions now better handle the case when there are
multiple parsers at point: 'treesit-thing-prev', 'treesit-thing-next',
'treesit-navigate-thing', 'treesit-thing-at'.  When there are multiple
parsers at point, instead of using whatever 'treesit-node-at' returns at
point, these functions now try every relevant parser in descending order
of relevance.  (Deeper-embedded parsers have higher relevance.)  These
functions now also take an additional optional argument, PARSER, that
allows the caller to specify a parser or language to use.  That also
means 'treesit-beginning/end-of-defun' can now move across parsers.

Good. Multi-language parsing in a buffer is still not great but we’re slowly getting there.

New command 'treesit-explore'.
This command replaces 'treesit-explore-mode'.  It turns on
'treesit-explore-mode' if it is not on, and pops up the explorer buffer
if it is already on.
'treesit-explore-mode' now supports local parsers.
Now 'treesit-explore-mode' (or 'treesit-explore') prompts for a parser
rather than a language, and it is now possible to select a local parser
at point to explore.

I could never get these things to behave properly. They’d leave weird minor mode detritus in the calling buffer; stick around and get reactivated when a desktop file is read on startup. I’m hoping that is fixed also.

Tree-sitter query predicates ':equal', ':match', and ':pred' are deprecated.
Use ':eq?', ':match?', and ':pred?' instead.  The change is because
newer tree-sitter libraries mandate query predicates to end with a
question mark.  Emacs will transparently convert ':equal', ':match', and
':pred' to ':eq?', ':match?', and ':pred?', respectively, so existing
queries still work fine with the latest tree-sitter library.  The
predicate ':equal' is changed to ':eq?' to better follow tree-sitter's
convention.  Also, the ':match?' predicate can now take the regexp as
either the first or second argument, so it works with both tree-sitter
convention (regexp arg second) and Emacs convention (regexp arg first).

I don’t understand why this change couldn’t have been handled purely in the backend. It is not possible to talk to the actual tree-sitter library itself; it’s gated behind treesit.el and Emacs core. I do not understand why the backend couldn’t just rewrite these query matchers and just leave it at that. Why deprecate?

Track changes

New variable 'track-changes-undo-only' to distinguish undo changes.

Hideshow

Hideshow is Emacs’s code/structured text hiding system.

New command 'hs-cycle'.
This command cycles the visibility state of the current block between
hiding the parent block, hiding only the nested blocks and showing all
the blocks.
New user option 'hs-cycle-filter' for visibility-cycling with 'TAB'.
This user option controls the positions on the headline of hideable blocks
where the 'TAB' key cycles the blocks' visibility.
New command 'hs-toggle-all'.
This command hides or shows all the blocks in the current buffer.
'hs-hide-level' no longer hides all the blocks in the current buffer.
If 'hs-hide-level' was not inside a code block, it would hide all the
blocks in the buffer like 'hs-hide-all'.  Now it only hides all the
second level blocks.
New user option 'hs-display-lines-hidden'.
If this option is non-nil, Hideshow displays the number of hidden lines
next to the ellipsis.  By default this is disabled.
New user option 'hs-show-indicators'.
This user option determines if Hideshow should display indicators to
show and toggle the block hiding.  If non-nil, the indicators are enabled.
By default this is disabled.
New user option 'hs-indicator-maximum-buffer-size'.
This user option limits the display of Hideshow indicators to buffers
that are not too large.  By default, buffers larger than 2MB have the
indicators disabled; a value of nil will activate the indicators
regardless of the buffer size.
New user option 'hs-indicator-type'.
This user option determines which indicator type should be used for the
block indicators.

The possible values are: 'fringe' to display the indicators in the
fringe (the default); 'margin' to display the indicators in the margin;
and nil, to display the indicators at end-of-line.

The new icons 'hs-indicator-show' and 'hs-indicator-hide' can be used to
customize the indicators appearance, but apply only if
'hs-indicator-type' is set to 'margin' or nil.
The hiding behavior of some hideshow commands has changed.
'hs-hide-block', 'hs-hide-level', 'hs-cycle' and 'hs-toggle-hiding' now
hide the innermost block to which the current line belongs instead of
the block after point.  To restore the old behavior, set the new user
option 'hs-hide-block-behavior' to 'after-point'.
The variable 'hs-special-modes-alist' is now obsolete.
Instead of customizing Hideshow for a mode by setting the elements of
'hs-special-modes-alist', such as START, COMMENT-START,
FORWARD-SEXP-FUNC, etc., major mode authors should set the corresponding
buffer-local variables 'hs-block-start-regexp', 'hs-c-start-regexp',
'hs-forward-sexp-function', etc.
'hs-hide-level' can now hide comments too.
This is controlled by 'hs-hide-comments-when-hiding-all'.
New minor mode 'hs-indentation-mode'.
This buffer-local minor mode configures 'hs-indentation-mode' to detect
blocks based on indentation.

The new user option 'hs-indentation-respect-end-block' can be used to
adjust the hiding range for this minor mode.

That is truly useful. Particularly for YAML.

C-ts mode

This and any future mode with -ts in it, is specifically for the tree-sitter-flavored version of a major mode. Do note that with few exceptions, the TS major modes are usually far less feature rich than the major modes they try to replace.

New user option 'c-ts-mode-enable-doxygen'.
By default, this is nil, and the Doxygen comment blocks in C/C++ sources
are highlighted like other comments.  When non-nil, Doxygen comment
blocks are font locked if the Doxygen grammar library is available.

Csharp-ts mode

Renamed feature in 'treesit-font-lock-feature-list'.
The feature 'property' has been renamed to 'attribute', since this is
what it is generally called among C# programmers.

Go-ts mode

New unit test commands.
Three new commands are now available to run unit tests.

The 'go-ts-mode-test-function-at-point' command runs the unit test at
point.  If a region is active, it runs all the unit tests under the
region.  It is bound to 'C-c C-t t' in 'go-ts-mode'.

The 'go-ts-mode-test-this-file' command runs all unit tests in the current
file.  It is bound to 'C-c C-t f' in 'go-ts-mode'.

The 'go-ts-mode-test-this-package' command runs all unit tests under the
package of the current buffer.  It is bound to 'C-c C-t p' in 'go-ts-mode'.

The 'go-ts-mode-build-tags' user option is available to set a list of
build tags for the test commands.

The 'go-ts-mode-test-flags' user option is available to set a list of
additional flags to pass to the go test command line.

Lua-ts mode

New user option 'lua-ts-auto-close-block-comments'.
When non-nil, inserting a block comment "--[[" will close it by
inserting its respective "]]".  By default, this is disabled.

Java-ts mode

New user option 'java-ts-mode-enable-doxygen'.
By default, this is nil, and the Doxygen comment blocks in Java sources
are highlighted like other comments.  When non-nil, Doxygen comment
blocks are font locked if the Doxygen grammar library is available.
New user option 'java-ts-method-chaining-indent-offset'.
Now method chaining is indented by 8 spaces rather than 4, and this
option controls how much is indented for method chaining.

JSON-ts mode

New command 'json-ts-jq-path-at-point'.
This command copies the path of the JSON element at point to the
kill-ring, but formatted for use with the 'jq' utility.

Now that is handy. One of the actual benefits of tree-sitter’s concrete syntax tree.

PHP-ts mode

'php-ts-mode' now depends on 'mhtml-ts-mode'.
The direct dependency on 'js-ts-mode', 'css-ts-mode' and 'html-ts-mode'
has now been replaced by 'mhtml-ts-mode'.  Navigation, Outline and Imenu
work for all languages, and code maintenance is easier.
'php-ts-mode-run-php-webserver' can now accept a custom "php.ini" file.
You can use the new optional argument CONFIG when calling
'php-ts-mode-run-php-webserver' to pass an alternative "php.ini" file to
the built-in Web server.  Interactively, when invoked with a prefix
argument, 'php-ts-mode-run-php-webserver' prompts for the config file as
well as for other connection parameters.
The user option 'php-ts-mode-css-fontify-colors' has been removed.
'mhtml-ts-mode-css-fontify-colors' replaces this option.
New user option 'php-ts-mode-html-relative-indent'.
In buffers containing both PHP and HTML, this option allows you to
define how the PHP code should be indented relative to the position of
the HTML tags.
New user option 'php-ts-mode-html-indent-offset'.
Offset of PHP code block relative to HTML tags.
New user option 'php-ts-mode-find-sibling-rules'.
Rules for finding siblings of a PHP file.
New user option 'php-ts-mode-phpdoc-highlight-errors'.
When non-nil, it highlights unknown PHPDOC tags using
'font-lock-warning-face' so that the user can identify them more easily.
New command 'php-ts-mode-show-ini'.
Show the location of the PHP ini files.  If the current buffer is
associated to a remote PHP file, show the remote PHP ini files.

Rust-ts mode

New user option 'rust-ts-mode-fontify-number-suffix-as-type'.
Rust number literals may have an optional type suffix.  When this option
is non-nil, this suffix is fontified using 'font-lock-type-face'.

YAML-ts mode

New user option 'yaml-ts-mode-yamllint-options'.
Additional options for 'yamllint', the command used for Flymake's YAML
support.

EIEIO

EIEIO is Emacs’s CLOS-style Object-Oriented Programming system. Named after the nursery rhyme “Old McDonald had a farm… EIEIO”.

Good ole’ Ludlam who wrote it had a thing for farm-related naming. See also his “Semantic Bovinator”, a tree-sitter precursor from the 2000s.

New value 'warn' for 'eieio-backward-compatibility'.
This is the new default value and causes warnings to be emitted
at run-time for the use of the associated deprecated features.
'(setq eieio-backward-compatibility t)' can be used to recover
the previous silence.

Text mode

New commands to convert between ASCII and full-width characters.
New commands 'fullwidth-region' and 'fullwidth-word' convert ASCII
characters in region or in the word at point to the corresponding
full-width characters, which are customarily used instead of ASCII
characters in CJK texts.  For example, 'A' is converted to 'A', '1' is
converted to '1', etc.  Companion commands 'halfwidth-region' and
'halfwidth-word' perform the opposite conversion.

Texinfo mode

Texinfo mode now can auto-close the ``'' pairs.
Now inserting `` in 'texinfo-mode' will close it by inserting its
respective '', if 'electric-pair-mode' is enabled.

ASM mode

'asm-mode-set-comment-hook' is obsolete.
You can now set 'asm-comment-char' from 'asm-mode-hook' instead.

Ibuffer

IBuffer is a fantastic and superior buffer list manager in Emacs. Do try it out and look around online for cool configs for it.

New column 'recency' in Ibuffer display.
The user option 'ibuffer-formats' configures the Ibuffer formats.  Add
'recency' to the format to display the column.
New value 'title' for the user option 'ibuffer-use-header-line'.
Display column titles in the header line if 'ibuffer-use-header-line' is
set to 'title'.
New user option 'ibuffer-human-readable-size'.
When non-nil, buffer sizes are shown in human readable format.
'define-ibuffer-op' prompts can now be functions.
The prompts 'opstring' and 'active-opstring' can now either be strings
or functions.  This is useful when your prompts can benefit from dynamic
content.
New Ibuffer-dedicated faces.
New faces 'ibuffer-marked', 'ibuffer-deletion', 'ibuffer-title', and
'ibuffer-filter-group-name'.  By default, they inherit from the
general-purpose faces Ibuffer previously used, to preserve previous
behavior.

ElDoc

Eldoc is Emacs’s interactive, point-driven documentation and code lookup tool. See Seamlessly Merge Multiple Documentation Sources with Eldoc for more information.

New ElDoc function 'elisp-eldoc-funcall-with-docstring'.
This function includes the current function's docstring in the ElDoc
echo area and can be used as a more detailed alternative to
'elisp-eldoc-funcall'.
New user option 'elisp-eldoc-funcall-with-docstring-length'.
This user option specifies how long function docstrings must be
displayed in 'elisp-eldoc-funcall-with-docstring'.  If set to 'short'
(the default), only display the first sentence of the docstring.
Otherwise, if set to 'full', display the full docstring.
New user option 'elisp-eldoc-docstring-length-limit'.
This user option controls the maximum length of docstrings in character
units that 'elisp-eldoc-funcall-with-docstring' and
'elisp-eldoc-var-docstring-with-value' will show.  By default, it is set
to 1000 characters.

Buffer Menu

The default buffer list manager bound to C-x C-b. Replace it with M-x ibuffer.

New user option 'Buffer-menu-human-readable-sizes'.
When non-nil, buffer sizes are shown in human readable format.  The
default is nil, which retains the old format.

Term

Emacs’s terminal emulator. See Running Shells and Terminal Emulators in Emacs

The terminal emulator now supports auto-margins control.
Term mode now handles DECAWM escape sequences that control whether text
automatically wraps at the right margin:

- \e[?7h enables auto-margins (default)
- \e[?7l disables auto-margins

When auto-margins is disabled, characters that would go beyond the right
margin are discarded, which matches the behavior of physical terminals
and other terminal emulators.  Control sequences and escape sequences
are still processed correctly regardless of margin position.

SMerge mode

SMerge is one of several conflicting (pardon the pun) ways of handling merge conflicts in Emacs. I like it.

New 'repeat-map' for SMerge conflict resolution commands.
With 'repeat-mode' enabled, after invoking an SMerge command (for
example, 'C-c ^ n'), you can repeat further SMerge commands by typing
just the final key (for example, 'n', 'p', 'u', 'l').

Oh gosh yes. I love SMerge but C-c ^ n (and friends) is exactly why people who are not Emacs users bounce hard when they see a key binding like that.

New command 'smerge-extend' extends a conflict over surrounding lines.
New command 'smerge-refine-exchange-point' to jump to the other side.
When used inside a refined chunk, it jumps to the matching position in
the other side of the refinement: if you are in the new text, it jumps
to the corresponding position in the old text and vice versa.
New user option 'smerge-refine-shadow-cursor'.
When 'smerge-refine' shows the conflict diffs at word granularity, a
shadow cursor is now displayed in the lower version when point is in the
upper version, and vice versa.  The shadow cursor is just the character
corresponding to the position where 'smerge-refine-exchange-point' would
jump, shown in a new distinct face 'smerge-refine-shadow-cursor', by
default a box face.
'smerge-refine-regions' can compare regions in different buffers.

Cursor Sensor mode

Now here’s an obscure mode. It and the intangible cursor concept are basically just a way of taking clumps of characters and telling Emacs that it should, and I’m keeping it a bit simple here, treat them as one big cohesive unit for all intents. I have only ever used an intangible cursor property once and that was in Combobulate’s envelope system.

New direction 'moved' used when the cursor moved within the active area.

Image Dired

Yep, Emacs’s dired has a thumbnail image viewer system.

'image-dired-show-all-from-dir' takes the same first argument as 'dired'.
This allows passing a string with wildcards, or a cons cell where the
first element is a list and the rest is a list of files.
New single-letter bindings in 'image-dired-thumbnail-mode-map'.
The keys 'f', 'b', 'n', 'p', 'a' and 'e' are now bound to the same
commands as their 'C-' counterparts.

Browse URL

Browse URL is just a wide-ranging set of internal functions, user-facing commands and variables that govern how Emacs pass URL-related information in and out; stuff like opening a browser when you click an url.

New user option 'browse-url-transform-alist'.
This user option is an alist that allows transforming URLs before asking
a web browser to load them.  For example, it could be used like this:

    (add-to-list 'browse-url-transform-alist
                 '("vim\\.org/.*" . "gnu.org/software/emacs/"))
New command 'browse-url-qutebrowser' for Qutebrowser.
For better integration with Qutebrowser, set
'browse-url(-secondary)-browser-function' to 'browse-url-qutebrowser'.
New GTK-native launch mode.
For better Wayland support, the pgtk toolkit exposes a new
'x-gtk-launch-uri' browse-url handler and uses it by default when URLs
are browsed from a PGTK frame.  For other frames, we fall back to the
default URL launch function.  This change allows us to properly raise
browser windows under Wayland using the xdg_activation_v1 protocol.
'RET' can visit URLs in read-only buffers.
In some keymaps such as 'ansi-osc-hyperlink-map',
'browse-url-button-map', 'goto-address-highlight-keymap', and
'bug-reference-map', it is now possible to visit URLs by typing just
'RET' instead of 'C-c RET' in read-only buffers.
Removed support for some obsolete web browsers.
Conkeror (obsolete since Emacs 28.1), gnome-moz-remote (obsolete since
Emacs 25.1), and gnudoit (obsolete since Emacs 25.1).
'browse-url-firefox-program' now supports LibreWolf and Zen Browser.
LibreWolf, Floorp and Zen Browser, three popular Firefox forks, have been
added to the programs that are automatically recognizable as Firefox
workalikes.  Emacs will set 'browse-url-firefox-program' to the first
one of these found on your system.

Floorp?

CL-Lib

Derived types (i.e. 'cl-deftype') can now be used as method specializers.
Some cl-lib functions and macros are now built-in.
These functions or macros have been added to Emacs Lisp, and the old
names are now aliases for the built-in equivalents:

- 'cl-incf'      renamed to 'incf'
- 'cl-decf'      renamed to 'decf'
- 'cl-oddp'      renamed to 'oddp'
- 'cl-evenp'     renamed to 'evenp'
- 'cl-plusp'     renamed to 'plusp'
- 'cl-minusp'    renamed to 'minusp'
- 'cl-member-if' renamed to 'member-if'

'cl-member-if' is marked obsolete.  The other names are deprecated too,
and will be marked as obsolete in a future release.

Common Lisp idioms continue advancing from the rear echelons into Emacs proper, much to the chagrin of some people.

'cl-labels' now also accepts '(FUNC EXP)' bindings, like 'cl-flet'.
Such bindings make it possible to compute the function to bind to FUNC.
'cl-block' names are now lexically scoped, as documented.
'cl-locally' is now obsolete.
It is an alias for the 'progn' special-form.
'cl-declare' is now obsolete; use 'defvar' instead.
'cl-gensym' is now obsolete; use 'gensym' instead.
New macro 'cl-with-accessors'.
This macro is similar to 'with-slots', but uses accessor functions
instead of slot names.  It is useful when some slot accessor functions
are used repeatedly, such as reading from a slot and then writing to
that slot.  Symbol macros are created for the accessor functions using
'cl-symbol-macrolet', so they can be used with 'setq' and 'setf'.

Unless I am mistaken, with-slots can already use setf to set a slot? I must be reading the text wrong.

Whitespace

Don’t sleep on M-x whitespace-mode and friends for finding errant whitespaces, tabs, and so forth. You won’t need it often, but when you do…

'whitespace-cleanup' now adds a missing newline at end of file.
If 'whitespace-style' includes 'missing-newline-at-eof' (which is the
default), the 'whitespace-cleanup' function will now add the newline.
'whitespace-mode' can now prettify page delimiter characters ('^L').
If 'page-delimiters' is set in 'whitespace-style', or the new minor mode
'whitespace-page-delimiters-mode' is on, the page delimiter character
('^L') is displayed as a pretty horizontal line that spans the entire
width of the window.  The new 'whitespace-page-delimiter' face can be
used to customize the appearance.
New user option 'whitespace-global-mode-buffers'.
Normally, 'global-whitespace-mode' skips special buffers whose name
starts with an asterisk "*".  This user option provides an override: it
contains a list of regular expressions used to match the names of
special buffers in which 'global-whitespace-mode' should turn on.  The
default value preserves the existing exception for the "*scratch*"
buffer.

Bookmark

One of Emacs’s greatest features. The bookmark system. You can bookmark a wide range of things and Emacs will happily pop open that exact spot in the info manual you bookmarked. Low-key fantastic feature.

Bookmark history now saves each bookmark only once.
Previously, the variable 'bookmark-history' accumulated duplicate
bookmark names when bookmark features were used interactively.  This
made their history larger than necessary for frequent bookmark users.
Bookmark names are now saved uniquely.
New user option 'bookmark-bmenu-type-column-width'.
This user option controls the width of the type column on the bookmark
menu 'bookmark-bmenu-list'.  The default value is 8 which is backwards
compatible.
New hook 'bookmark-after-load-file-hook'.
This hook is run by 'bookmark-load' after loading a bookmark file.  This
hook can be used, for example, to reconcile 'bookmark-alist' against
bookmark state that you, or a package that you use, maintains.

Recentf

A store of recent files. Back in the day it was a bit of a pain to use due to the way completion mechanism worked, so I wrote Find files faster with the recent files package. Nowadays your fancy completion package + M-x recentf is all you need

You can now regularly auto-save recently opened files.
Customize user option 'recentf-autosave-interval' to the number of
seconds between auto saving recently opened files.  For example,
customize this variable to 300 to save recently opened files every 5
minutes.  From Lisp, use 'setopt', not 'setq'.  If
'recentf-autosave-interval' is nil, auto saving is disabled; this is the
default.
New user option 'recentf-show-messages'.
'recentf-save-list' can print a message when saving the recentf list.
The new option, if customized to nil, suppresses this message.
New user option 'recentf-suppress-open-file-help'.
By default, invoking 'recentf-open-files' displays a message saying what
action clicking or typing 'RET' on the item at point executes, and tabbing
between items in the "*Open Recent*" buffer likewise displays such
messages.  To suppress these messages, customize the user option
'recentf-suppress-open-file-help' to non-nil.  The default value of this
option is nil.
New user option 'recentf-exclude-ignored-extensions'.
Add the new predicate function 'recentf-exclude-file-by-extension-p' to
the list that is the value of the user option 'recentf-exclude' to
ignore files with certain extensions.  By default, adding this function
to 'recentf-exclude' ignores files whose extensions are listed in
'completion-ignored-extensions'; you can specify a different list of
extensions by customizing the new user option
'recentf-exclude-ignored-extensions'.

Saveplace

M-x save-place-mode stores the exact place you were in a file when you revisit it.

You can now regularly auto-save places.
Customize user option 'save-place-autosave-interval' to the number of
seconds between auto-saving places.  For example, customize this
variable to 300 to save places every 5 minutes.  From Lisp, use 'setopt',
not 'setq'.  If 'save-place-autosave-interval' is nil, auto saving is
disabled; this is the default.

Savehist

Tired of losing history in all manner of completion prompts? Enable M-x savehist-mode and you can choose what and where is saved.

The history file can be modified by external tools.
Emacs can now handle this case gracefully by merging the external and
internal history information.  This feature is activated only when
'savehist-additional-variables' is nil.
Savehist no longer saves additional variables more than once.
If you configured 'savehist-additional-variables' with variables that
were also dynamically accumulated in minibuffer history during
minibuffer use, they are now saved only once in the file specified by
'savehist-file'.  Previously, they were saved twice.

Rectangle Mark

Borrowed from CUA-mode that for some reason got special rectangle selection but the rest of us non-CUA users did not. It was moved out some years ago and nowC-x SPC activates it.

New user option to control whether empty rectangle selections are shown.
The new user option 'rectangle-indicate-zero-width-rectangle' can be
used to disable the default display of empty rectangular selections.
The default is t; set it to nil to disable the indication.  (It causes a
horizontal shift of text on display, which could be distracting.)

Message

One of several ways of crafting RFC-compliant e-mails in Emacs.

"In-Reply-To" header contains only a message id.
The "In-Reply-To" header created when replying to a message now contains
only the originating message's id, conforming to RFC 5322.  The previous
behavior included additional information about the originating message.
The new user option 'message-header-use-obsolete-in-reply-to', nil by
default, can be set to a non-nil value to restore the previous behavior.

Or should I say: compliant now?

'message-subject-re-regexp' default value is derived from 'mail-re-regexps'.
'mail-re-regexps' is a new user option that is easier to customize than
'message-subject-re-regexp'.  'message-subject-re-regexp' is still
honored if it was already set.
'message-strip-subject-re' now matches case-insensitively.
'message-change-subject' inserts the current subject into future history.
Hashcash support has been removed.
It is believed to no longer be useful as a method to fight spam.  The
'message-generate-hashcash' option is now obsolete and has no effect.

Gnus

A one-man marching band that started its life as a usenet group reader. Incredibly complex and feature rich reader that, after decades of being brow-beaten with a rolled-up newspaper, can now also handle your emails, reddit chats, rss feeds and probably a lot more than that.

Replying to icalendar events now supports specifying a comment.
When called with a prefix argument, accepting, declining, or tentatively
accepting an icalendar event will prompt for a comment to add to the
response.
Hashcash support has been removed.
It is believed to no longer be useful as a method to fight spam.  The
'spam-use-hashcash' hook is now obsolete and has no effect.
Add 'M-i' keybinding as the symbolic prefix in the group keymap.
The symbolic prefix is another kind of universal prefix that is used in
Gnus; see "(gnus) Symbolic Prefixes" in the Gnus manual.
Sorting selected groups is now possible with 'gnus-topic-mode'.
gnus-dbus.el is now obsolete.
System sleep integration is now independent of D-Bus.
The system sleep integration previously provided by customizing the
variable 'gnus-dbus-close-on-sleep' is now deprecated.  A new system
using the builtin system-sleep.el library is now available by customizing
'gnus-close-on-sleep'.  This will work on all systems that the
'system-sleep' library supports.

Sieve

Major mode for server-side IMAP sieve filters.

New keybinding to refresh buffer in 'sieve-manage-mode'.
'sieve-refresh-scriptlist' is now bound to 'g' to refresh the contents
of the current sieve buffer.

Button

Emacs’s widget system (what customize uses) is used here to turn stuff like #29382 bug reference notations into little interactive buttons.

If you like this paradigm, just use Bob Weiner’s Hyperbole package. It’s far better.

New function 'unbuttonize-region'.
It removes all the buttons in the specified region.
Disabling 'button-mode' now removes all buttons in the current buffer.

Shell

My favorite method of interacting with shells. Nothing more than a riced Emacs buffer serving up cooked terminal output. See Running Shells and Terminal Emulators in Emacs

Shell buffers now support bookmarks.
You can now bookmark local and remote shell buffers using the bookmark
menu 'bookmark-bmenu-list', or by using the command 'bookmark-set'.
Shell bookmarks can be loaded via the menu and by using the command
'bookmark-jump', which opens a bookmarked shell, restores its buffer name,
its current directory, and creates a remote connection, if necessary.
You can customize 'shell-bookmark-name-function'.

Bookmarks are awesome. I’ll have to play with this for sure

New command to complete the shell history.
'comint-complete-input-ring' ('C-x <UP>') is like 'minibuffer-complete-history'
but completes on comint inputs.

See my Shell & Comint Secrets: History commands

'ansi-osc-directory-tracker' now respects remote directories.
Remote directories are now retained when changes to 'default-directory'
are detected by this filter.  For example, "/ssh:hostname:/home/username"
would have been stripped to just "/home/username" before.

OSC directory tracking is especially important in shell mode for it does not typically ask the underlying shell for “completion”.

Eshell

Emacs’s very own shell written in Elisp. Cool package. See Mastering Eshell.

New interactive command 'eshell-clear'.
This command scrolls the screen so that only the current prompt is
visible, optionally erasing all the previous input/output as well.
Previously, the Eshell built-in command 'eshell/clear' supported this
(e.g., to call it via 'M-x'), but this new command behaves more
consistently if you have a partially typed command at the Eshell prompt.
New user option 'eshell-command-async-buffer'.
This option lets you tell 'eshell-command' how to respond if its output
buffer is already in use by another invocation of 'eshell-command', much
like 'async-shell-command-buffer' does for 'shell-command'.  By default,
this will prompt for confirmation before creating a new buffer when
necessary.  To restore the previous behavior, customize this option to
'confirm-kill-process'.
'eshell-execute-file' is now an interactive command.
Interactively, this now prompts for a script file to execute.  With the
prefix argument, it will also insert any output into the current buffer
at point.
'eshell-command' and 'eshell-execute-file' can now say where stderr goes.
These functions now take an optional ERROR-TARGET argument to control
where to send the standard error output.
See the Info node "(eshell) Entry Points" for more details.
You can now loop over ranges of integers with the Eshell 'for' command.
When passing a range like 'BEGIN..END' to the Eshell 'for' command,
Eshell will now iterate over each integer between BEGIN and END, not
including END.
Conditional statements in Eshell now use an 'else' keyword.
Eshell now prefers the following form when writing conditionals:

    if {conditional} {true-subcommand} else {false-subcommand}

The old form (without the 'else' keyword) is retained for compatibility.
You can now chain conditional statements in Eshell.
When using the newly-preferred conditional form in Eshell, you can now
chain together multiple 'if'/'else' statements.  For more information,
see "(eshell) Control Flow" in the Eshell manual.
Eshell's built-in 'wait' command now accepts a timeout.
By passing '-t' or '--timeout', you can specify a maximum time to wait
for the processes to exit.  Additionally, you can now wait for external
processes by passing their PIDs.
New hook 'eshell-after-initialize-hook'.
This hook runs after an Eshell session has been fully initialized,
immediately before running 'eshell-post-command-hook' for the first
time.
Improved history Isearch.
History Isearch in Eshell has been reworked.  Two new commands
'eshell-isearch-backward-regexp' and 'eshell-isearch-forward-regexp' are
added for incrementally searching through the input history.
'eshell-isearch-backward-regexp' is bound to 'M-r' by default, and 'M-s'
is freed for normal search commands.  If you would like to restore the
previous key-bindings for the non-incremental search commands, put in
your configuration:

    (with-eval-after-load 'em-hist
      (keymap-set eshell-hist-mode-map "M-r"
                  #'eshell-previous-matching-input)
      (keymap-set eshell-hist-mode-map "M-s"
                  #'eshell-next-matching-input))

Eshell was sorely missing the interactive reverse search

New user option 'eshell-history-isearch'.
When 'eshell-history-isearch' is nil (the default), Isearch commands
search in the buffer contents.  If you customize it to t, those commands
only search in input history.  If you customize it to the symbol 'dwim',
those commands search in input history only when point is after the last
prompt.
Eshell 'alias' command now sorts the alias list.
When adding an alias interactively, Eshell now sorts the list of aliases
before saving the alias file.  This maintains the stability of the
list of aliases to make the diff between versions more readable if you
store your aliases in version control.

I’m happy somebody’s taken ownership of eshell and is busy adding cool new features.

Mail Utils

New user option 'mail-re-regexps'.
This contains the list of regular expressions used to match "Re:" and
international variants of it when modifying the Subject field in
replies.

Mairix

Mairix is apparently a mail search engine. I have never used it.

'mairix-search' now keeps its own minibuffer history.

Imap

'imap-authenticate' can now use PLAIN authentication.
"AUTH=PLAIN" support is auto-enabled if the IMAP server supports it.  If
you do not wish to use "AUTH=PLAIN", pass a specific authentication type
to 'imap-open' for 'imap-authenticate' to use, or remove 'plain' from
'imap-authenticators'.

Rmail

Another way to author your emails in Emacs. So that’s 3 so far: Message, Gnus and Rmail.

'rmail-re-abbrevs' default value is now derived from 'mail-re-regexps'.
'mail-re-regexps' is a new user option that is easier to customize than
'rmail-re-abbrevs'.  'rmail-re-abbrevs' is still honored if it was
already set.
New user options for formatting Rmail summary lines.
'rmail-summary-sender-function' and 'rmail-summary-recipient-function'
control how the sender/recipient fields are displayed in the summary.
'rmail-summary-address-width' controls the width of that field.
New user option 'rmail-mime-save-action'.
This option specifies an action to take after saving a MIME attachment.
Predefined values include visiting the file in Emacs, jumping to the
file in Dired, or opening the file with an external program.  You can
also provide a custom function.
Rmail now detects email messages from suspicious sender addresses.
If the "From" header of a message contains a suspicious email address,
Rmail will now highlight it in a distinct face and provide a 'help-echo'
tooltip explaining the reason.  (What exactly is considered as
suspicious email addresses is determined by the function
'textsec-suspicious-p', which see.)  This is controlled by the new user
option 'rmail-detect-suspicious-headers', whose default value is
non-nil; customize to nil to disable the check.

Sendmail

Sending an email via sendmail.el checks for suspicious addressees.
The command 'mail-send', used to send email in Mail mode, now checks the
addressees for suspicious email addresses.  If such addresses are found,
the command will show them and the reason they are considered
suspicious, and will request a confirmation before sending the message.
This follows the behavior of Message mode, and affects users who
customize 'mail-user-agent' to the value 'sendmail-user-agent'.

SHR

SHR is Emacs’s internal HTML rendering engine. It powers M-x eww, Emacs’s Web Wowser.

SHR now slices large images into rows.
Sliced images allow for more intuitive scrolling up/down by letting you
scroll past each slice, instead of jumping past the entire image.
Previously, SHR sliced images when zoomed to their original size, no
matter how large or small that was.  Now, SHR slices any images taller
than 'shr-sliced-image-height'.  For more information, see the Info node
"(eww) Advanced".
You can now customize the image zoom levels to cycle through.
By customizing 'shr-image-zoom-levels', you can change the list of zoom
levels that SHR cycles through when calling 'shr-zoom-image'.
New user option 'shr-fill-text'.
When 'shr-fill-text' is non-nil (the default), SHR will fill text
according to the width of the window.  If you customize it to nil, SHR
will leave the text as-is; in that case, EWW will automatically enable
'visual-line-mode' when displaying a page so that long lines are
visually wrapped at word boundaries.

EWW

Emacs’s Web Wowser. M-x eww. Handy little text and image browser; great for documentation.

EWW now enables 'visual-wrap-prefix-mode' when 'shr-fill-text' is nil.
By default, 'shr-fill-text' is t, and EWW fills the text according to
the width of the window.  If you customize 'shr-fill-text' to nil, EWW
will now automatically turn on 'visual-wrap-prefix-mode' in addition to
'visual-line-mode', so that long lines are wrapped at word boundaries
near the window edge, and continuation lines are indented using prefixes
computed from the surrounding context.
New user option 'eww-guess-content-type-functions'.
The value is a list of functions that EWW should call to determine the
content-type of Web pages which don't have a valid 'Content-Type'
header.  The default value is a function that considers a page with an
HTML 'doctype' declaration to have content-type "text/html".
'eww-switch-to-buffer' falls back to calling 'eww'.
When there is no EWW buffer, 'eww-switch-to-buffer' falls back to
calling 'eww'.

URL Queue

One of several ways of querying a HTTP endpoint.

'url-queue-retrieve' now makes use of some url request variables.
The variables 'url-request-data', 'url-request-method', and
'url-request-extra-headers' can now be bound around a call to
'url-queue-retrieve'.  Binding them has the same effect as for
'url-retrieve'.

CC mode

Classic C mode.

New type of 'c-offsets-alist' element.
The 'cdr' of such an alist element may now be a syntactic symbol.  A
source line with a syntactic element whose symbol is the 'car' of that
alist element is indented as though it were the 'cdr'.
Enums now have their own syntactic symbols.
The new symbols 'enum-open', 'enum-close', 'enum-intro' and
'enum-entry' are used in the analysis of enum constructs.  Previously,
they were given 'brace-list-open', etc.  These are fully described in
the "(ccmode) Enum Symbols" node of the CC mode manual.
Enums are now, by default, indented like classes, not brace-lists.
To get the old behavior back, add an element '(enum-open
. brace-list-open)' to 'c-offsets-alist' in your CC mode style, or amend
'c-offsets-alist' likewise in any of the other ways detailed in the
"(ccmode) Config Basics" node of the CC mode manual.

Emacs Lisp mode

Semantic highlighting support for Emacs Lisp.
'emacs-lisp-mode' can now use code analysis to highlight more symbols
more accurately.  Customize the new user option
'elisp-fontify-semantically' to non-nil to enable this feature, and see
the Info node "(emacs) Semantic Font Lock" for more information.

Awesome. Can’t wait to try it out.

Checkdoc will now flag incorrect formatting in warnings.
This affects calls to 'warn', 'lwarn', 'display-warning', and
'message-box'.
New function 'checkdoc-batch'.
It checks the buffer in batch mode, prints all found errors
and signals the first found error.
New file-local variable 'lisp-indent-local-overrides'.
This variable can be used to locally override the indent specification
of symbols.
Checkdoc no longer warns about missing footer lines in some cases.
Emacs Lisp libraries have traditionally ended with a footer line
(sometimes referred to as "terminating comment").  Their purpose was to
easily detect files that had been truncated in transit on ancient and
less reliable connections:

    ;; some-cool-package.el ends here

'checkdoc' will no longer warn if that line is missing for packages that
explicitly only support Emacs 30.1 or later, as specified in the
"Package-Requires" header.  The reason for keeping the warning for
packages that support earlier versions of Emacs is that package.el in
those versions can't install packages where that line is missing.

This change affects both 'checkdoc' and the corresponding flymake
backend.
Checkdoc no longer warns about wide docstrings.
The Checkdoc warning for wide docstrings duplicates the byte-compiler
warning added in Emacs 28.1.  This redundancy is now removed.
New user option 'checkdoc-arguments-missing-flag'.
Set this to nil to disable warnings for function arguments that are not
documented in docstrings.
New user option 'checkdoc-allow-quoting-nil-and-t'.
Customizing this option to a non-nil value stops checkdoc from warning
for docstrings where symbols 'nil' and 't' are in quotes.
The default of 'checkdoc-verb-check-experimental-flag' is now nil.
In most cases, having it enabled leads to a large amount of false
positives.

IELM

IELM is the Interactive Emacs Lisp Mode. A REPL for Elisp. See Evaluating Elisp in Emacs.

IELM input history is now saved also when the IELM process is killed.
When you kill the IELM process with 'C-c C-c', the input history is now
saved to the file specified by 'ielm-history-file-name', just like when
you exit the Emacs session or kill the IELM buffer.
New value 'point' for user option 'ielm-dynamic-return'.
When 'ielm-dynamic-return' is set to 'point', typing 'RET' has dynamic
behavior based on whether point is inside an sexp.  While point is
inside an sexp typing 'RET' inserts a newline, and otherwise Emacs
proceeds with evaluating the expression.  This is useful when
'electric-pair-mode', or a similar automatic pairing mode, is enabled.

Very handy as the multi-line editing behavior in IELM is a little bit wonky. Honestly IELM is a terrible choice if you’re doing multi-line stuff; use a scratch buffer or better still an ert test.

Doc View

Doc View turns complex documents like PDF and MS word into images so Emacs can render and show them to you.

Dedicated buffer for plain text contents.
When switching to the plain text contents with 'doc-view-open-text',
Doc View now creates a dedicated buffer to display it.  'C-c C-c' gets you
back to the real Doc View buffer if it still exists.
New commands to save and restore pages in buffer-local registers.
Doc View can store the current page to buffer-local registers with the new
command 'doc-view-page-to-register' (bound to 'm'), and later the stored
page can be restored with 'doc-view-jump-to-register' (bound to ''').
Doc View can generate imenu indices for DjVu and ODF documents.
When the 'djvused' program is available, Doc View can now generate an imenu
index for DjVu files from its outline.  Indices for Open Document Format
(ODF) files as used by OpenOffice and LibreOffice are generated using
the 'mutool' program after their initial conversion to PDF format.  The
name of the 'djvused' program can be customized by changing the user
option 'doc-view-djvused-program'.

Ispell

Emacs’s spell checker. I like M-x flyspell-mode (and M-x flyspell-prog-mode for programming that just does comments and strings) as they add squiggly lines to misspellings, and of course M-$ to spell check the word at point.

The default value of 'ispell-help-timeout' has changed.
The default value is now 30 seconds, as the old value was too short to
allow reading the help text.
Ispell can now save spelling corrections as abbrevs.
In the Ispell command loop, type 'C-u' immediately before selecting a
replacement to toggle whether that correction will be saved as a global
abbrev expansion for its misspelling.  The new user option
'ispell-save-corrections-as-abbrevs' determines whether abbrev saving
is enabled by default.

Oh that’s awesome. My article on Correcting Typos and Misspellings with Abbrev talks about how i turned Wikipedia’s list of common misspellings into abbrevs. I use it every day, without really knowing. It just… silently corrects typos.

Flyspell

As mentioned, this gives you squiggly underlines when you misspell something.

New user option 'flyspell-delay-use-timer'.
By default, Flyspell waits after so-called "delayed" commands by calling
'sit-for'.  If you customize this option to non-nil, Flyspell instead
sets up a timer to perform spell-checking after a short delay, which
allows idle timers and other code to run during this delay period.  We
plan to make this behavior the default in a future Emacs release, so we
invite Flyspell users to enable this new option and report any issues.
'turn-on-flyspell' and 'turn-off-flyspell' are obsolete.
To unconditionally enable 'flyspell-mode' from a hook, use this instead:

    (add-hook 'text-mode-hook #'flyspell-mode)

Tramp

Emacs’s system of communicating and interfacing with remote systems via Docker containers, Kubernetes, SSH, Android debug bridge and so many more. Truly one of the greatest features in Emacs.

Tramp signals 'remote-file-error' in case of connection problems.
This is a subcategory of 'file-error'.  Therefore, all checks for
'file-error' in 'condition-case', 'ignore-error', 'error-conditions' and
the like still work.
New command 'tramp-cleanup-bufferless-connections'.
Connection-related objects for which no associated buffers exist, except
for Tramp internal buffers, are flushed.  This is helpful to prune
connections after you close remote-file buffers without having to either
cherry-pick via 'tramp-cleanup-connection' or clear them all via
'tramp-cleanup-all-connections'.
External methods can now be used in multi-hop connections.
This is implemented for 'tramp-sh' methods, like "/scp:user@host|sudo::".
New command 'tramp-dired-find-file-with-sudo'.
This command, bound to '@' in Dired, visits the file or directory on the
recent Dired line with superuser, or root, permissions.

I already have a little helper command called sudo that does just this for files and dired buffers. Very welcome addition though. Remember this should work with multi-hops also.

'C-x x @' is now bound to 'tramp-revert-buffer-with-sudo'.
You can use 'C-u C-x x @' to select a Tramp method other than the
default, "sudo".

As above, this is genuinely great. I like that you can pick something other than sudo.

'tramp-file-name-with-method' can now be set as connection-local variable.
New optional connection methods "surs" and "sudors".
These connection methods are similar to "su" and "sudo", but they use
the modern 'su-rs' and 'sudo-rs' commands.
Connection method "kubernetes" now supports an optional namespace.
The host name for Kubernetes connections can be of kind
"[CONTAINER.]POD[%NAMESPACE]", in order to specify the namespace to be
used.  This overrides the setting in 'tramp-kubernetes-namespace', if
any.
Different proxies for the same destination host name can be specified.
A typical example are docker containers, which run on different hosts
under the same docker name.  When the user option
'tramp-show-ad-hoc-proxies' is non-nil, such ad-hoc multi-hop file names
can be used in parallel.  Example: on both remote hosts "host1" and
"host2" there is a docker container "name", respectively:

    /ssh:user1@host1|docker:name:
    /ssh:user2@host2|docker:name:

This feature is experimental.

Wonderful stuff. Tramps works with podman also so I expect it’ll work with those too.

Implementation of filesystem notifications for connection method "smb".
Remote process support has been rewritten for the "smb" connection method.
For more information, see "(tramp) Running remote processes on MS
Windows hosts" in the Tramp manual.
New functions to extend the set of operations with a remote implementation.
The new functions 'tramp-add-external-operation' and
'tramp-remove-external-operation' allow adding an implementation for
other operations than the defined set of magic file name operations.
This can be used by external ELPA packages for performance optimizations
in special cases.  For more information, see "(tramp) New operations" in
the Tramp manual.
New user option 'tramp-propagate-emacsclient-tramp'.
When this option is non-nil, Tramp propagates the environment variable
EMACSCLIENT_TRAMP with a proper value to remote processes.  This is
helpful if you want to start emacsclient on a remote host from a process
started inside Emacs.

Isearch and Replace

Isearch is Emacs’s interactive search bound to C-s. Replace is of course replacing text; Emacs has many of those too.

Typing 'd' during 'query-replace' shows the diff buffer with replacements.

Neat. Pair it with project-wide replacement like C-x p r to make sure you did not replace stuff you should not have.

Diff

.diff files, modes and related things. Emacs has a bunch of this stuff in various guises.

'diff-mode' now refrains from automatically refining big hunks.
What is big is defined by the new 'diff-refine-threshold' user option.
Refining a hunk shows a shadow cursor at the beginning/end of region.
By default, the shadow cursor looks like an empty rectangle the size of
a character cell.  It is displayed at the beginning or the end of the
refined region, to better show where the refined region starts or ends.
This can be controlled by the new user option
'smerge-refine-shadow-cursor', which also affects SMerge mode.
New command 'diff-kill-ring-save'.
This command copies to the 'kill-ring' a region of text modified
according to diffs in the current buffer, but without applying the diffs
to the original text.  If the selected range extends beyond a hunk, the
command attempts to look up and copy the text in between the hunks.

Ha that’s really cool. I like that I can now copy the patched diff hunk from a buffer. The challenge will be in remembering the command; this is not something I have to do frequently.

New command 'diff-revert-and-kill-hunk' bound to 'u' and 'C-c M-u'.
This command reverts the hunk at point (i.e., applies the reverse of the
hunk), and then removes the hunk from the diffs.
This is useful to undo or revert changes, committed and uncommitted, when
you are in buffers generated by 'C-x v =' and 'C-x v D'.
When the region is active, the command reverse-applies and kills hunks
that the region overlaps.

Oh yeah that’s awesome. I use Magit but I honestly prefer VC mode for in-buffer VC actions, so C-x v = and friends I use a lot.

'v' is now bound to 'vc-next-action' in read-only Diff mode buffers.
's' is now bound to 'diff-split-hunk' in read-only Diff mode buffers.
'diff-file-prev' and 'diff-hunk-prev' always move to start of header.
Previously, 'diff-file-prev' and 'diff-hunk-prev' would move when point
is after the corresponding file or hunk header, but not when inside it.
Now they will always move to the start of the current header.
New command 'diff-delete-other-hunks' bound to 'C-c RET n'.
This command deletes all hunks other than the current hunk.  It is
useful to prepare a "*vc-diff*" buffer for committing a single hunk.
When the region is active, it deletes all hunks that the region does not
overlap.
'vc-version-diff' and 'vc-root-version-diff' changed default for REV1.
They now suggest the previous revision as the default for REV1, not the
last one as before.  This makes them different from 'vc-diff' and
'vc-root-diff' when those are called without a prefix argument.
'diff-apply-hunk' now supports creating and deleting files.
'diff-apply-buffer' supports creating files but not deleting them, yet.
Diff mode's application and killing commands now consider the region.
If the region is active, 'diff-apply-hunk', 'diff-apply-buffer' and
'diff-hunk-kill' now apply or kill all hunks that the region overlaps.
Otherwise, they have their existing behavior.
'diff-apply-buffer' can reverse-apply.
With a prefix argument, it now reverse-applies hunks.
This matches the existing prefix argument to 'diff-apply-hunk'.

Ediff

Ediff is a 3-way interactive merge tool in Emacs. It’s excellent but it will take a little getting used to.

Ediff's copy commands now apply to all changes with 'C-u' prefix.
The Ediff copy commands, bound to 'a', 'b', 'ab', etc., now copy all
changes when supplied with a universal prefix argument via 'C-u':

- 'C-u a' copies all changes from buffer A to buffer B (in a 2-way diff)
  or to buffer C (in a 3-way diff or merge).
- 'C-u b' copies all changes from buffer B to buffer A (in a 2-way diff)
  or to buffer C (in a 3-way diff or merge).
- 'C-u a b' copies all changes from buffer A to buffer B.
- 'C-u b a' copies all changes from buffer B to buffer A.
- 'C-u a c' copies all changes from buffer A to buffer C.
- 'C-u b c' copies all changes from buffer B to buffer C.
- 'C-u c a' copies all changes from buffer C to buffer A.
- 'C-u c b' copies all changes from buffer C to buffer B.
Ediff now supports more flexible custom window layouts.
Custom implementations of 'ediff-window-setup-function' no longer need
to display all Ediff windows.  Any of the A, B, C, and control windows
can be left undisplayed and the corresponding variable set to nil.
This change enables custom layouts without a control panel window.

Dired

Dired is Emacs’s superlative directory editor and file browser. It has a wide range of advanced features. See Dired Shell Commands: The find & xargs replacement, WDired: Editable Dired Buffers and Working with multiple files in dired.

New user option 'dired-create-empty-file-in-current-directory'.
When non-nil, 'dired-create-empty-file' creates a new empty file and
adds an entry for it (or its topmost new parent directory if created)
under the current subdirectory in the Dired buffer by default
(otherwise, it adds the new file (and new subdirectories if provided) to
whichever directory the user enters at the prompt).  When nil,
'dired-create-empty-file' acts on the default directory by default.

Note that setting this user option to non-nil makes invoking
'dired-create-empty-file' outside of a Dired buffer signal an error
(like other Dired commands that always prompt with the current
subdirectory, such as 'dired-create-directory').
New user option 'dired-check-symlinks' allows disabling validity checks.
Dired uses 'file-truename' to check symbolic link validity when
fontifying them, which can be slow for remote directories.  Setting
'dired-check-symlinks' to nil disables these checks.  The new option
defaults to t and can be set as a connection-local variable.
New user option 'dired-hide-details-hide-absolute-location'.
When Dired's 'dired-hide-details-mode' is enabled, also hide the
'default-directory' absolute location, typically displayed as the first
line in a Dired buffer.

With 'dired-hide-details-hide-absolute-location':

    project: (100 GiB available)

Without 'dired-hide-details-hide-absolute-location':

    /absolute/path/to/my/important/project: (100 GiB available)
Clicking on the base name of a directory reverts the buffer.
When 'dired-make-directory-clickable' is non-nil, clicking on the base
name of the directory now reverts the Dired buffer.
'dired-copy-filename-as-kill' supports project-relative names.
With a prefix argument value of 1, this command now copies file names
relative to the root directory of the current project.

Lovely, but gated behind a prefix value of 1 makes it very utilitarian and hard to find without reading the docstring.

Warning when Dired displays a file name with a literal newline.
If Dired uses an 'ls' implementation that supports the '-b' switch, then
on visiting a directory that contains a file whose name has a newline,
and Dired displays that character as a literal newline, Emacs now
automatically pops up a buffer warning that such a display can be
problematic for Dired and showing a way to change the display to use the
unproblematic character '\n'.

Dired (except on Windows and other such platforms where it is emulated) uses the output of ls to furnish the dired buffer with its directory information. That is why stuff like my Dired Shell Commands: The find & xargs replacement that show you how to use find-name-dired and friends is so powerful. It’s just a buffer with text enriched with Emacs commands, font locking and such. Such a powerful concept

See Why Emacs has Buffers.

New user option 'dired-auto-toggle-b-switch'.
When this user option is non-nil and Dired uses an 'ls' implementation
that supports the '-b' switch and 'dired-listing-switches' does not
include the '-b' switch, then on visiting a directory containing a file
whose name has a newline, Emacs automatically adds the '-b' switch and
redisplays the directory in Dired to show '\n' in the file name instead
of a literal newline.  This prevents executing many Dired operations on
such a file from failing and signaling an error.  The default value of
this user option is nil.
New Dired handling of errors from 'ls'.
When invoking a Dired command causes 'ls' to emit an error message,
Emacs now displays the message in a popped up buffer instead of
outputting it in the Dired buffer and signalling an error.

Grep

Grep refers to Emacs’s wide range of grep wrapper commands. Note that you don’t have to use grep with them; you can of course change the tool to something else.

Grep results can be edited to reflect changes in the originating file.
Like Occur Edit mode, typing 'e' in the "*grep*" buffer will now make
the 'grep' results editable.  The edits will be reflected in buffers
visiting the originating files.  Typing 'C-c C-c' will leave the Grep
Edit mode.

Wonderful. Decades ago I wrote a little “extract” tool to do something similar as I often had to do precision edits across many files. This would’ve saved me a lot of time back in the day.

Occur edit mode is another feature most people sleep on. Do try out M-s o occur to match stuff, and then edit it with e. Occur works with multiple buffers but it does need a little encouragement.

Imenu

Imenu is Emacs’s generic selection interface for contextually interesting things in the current buffer: functions, classes, markdown headings, org mode headings, etc.

For decades it had no key binding. Now it’s bound to M-g i. I use M-i because that has a worthless default command.

New user option 'imenu-allow-duplicate-menu-items'.
This specifies whether Imenu can include duplicate menu items.
Duplicate items are now allowed by default (option value t), which
restores the behavior before Emacs 29.  Customize this to nil to get the
behavior of Emacs 29 and Emacs 30.

Time Stamp

You don’t see it as much any more, but back in the day people would commonly update files (Changelogs, source code files, etc.) with the timestamp of when the file was last changed. I’m not saying it’s not needed any more, but source control in many ways supplanted the need for it. But if you still want Emacs to find and update time stamps, you can with M-x time-stamp, though you’ll want a hook on before save to ensure changes are applied automatically.

'time-stamp' can up-case, capitalize and down-case date words.
This control can be useful in languages in which days of the week and/or
month names are capitalized only at the beginning of a sentence.  For
details, see the built-in documentation for user option 'time-stamp-format'.

Because this feature is new in Emacs 31.1, do not use it in the local
variables section of any file that might be edited by an older version
of Emacs.
Some historical 'time-stamp' conversions now warn.
'time-stamp-pattern' and 'time-stamp-format' had quietly accepted
several 'time-stamp' conversions (e.g., "%:y") that have been deprecated
since Emacs 27.1.  These now generate a warning with a suggested
migration.

Merely having '(add-hook 'before-save-hook #'time-stamp)'
in your Emacs init file does not expose you to this change.
However, if you set 'time-stamp-format' or 'time-stamp-pattern'
with a file-local variable, you may be asked to update the value.

TeX modes

TeX and friends. I recommend AUCTeX to anybody serious about writing LaTeX especially in Emacs.

New Xref backend for TeX modes.
The new backend ('tex-etags') is on by default, and improves the
functionality of the standard Xref commands in TeX buffers.  You can
restore the standard 'etags' backend with the 'xref-etags-mode' toggle.

Xref of course is Emacs’s generic cross-referencing feature that surfaces matches against search terms. Note again the “etags” here; back in the day, Emacs’s only real cross-referencing system was the wrapper around the external TAGS app. For compatibility with it, there are shims that try to serve both systems.

BibTeX mode

New user options facilitate customization of BibTeX and biblatex entries.
Entry definitions via the user options 'bibtex-BibTeX-aux-entry-alist',
'bibtex-biblatex-aux-entry-alist', 'bibtex-BibTeX-aux-opt-alist', and
'bibtex-biblatex-aux-opt-alist' take precedence over
'bibtex-BibTeX-entry-alist' and 'bibtex-biblatex-entry-alist'.
These user options now support the definition of aliases that inherit
the definition of another entry.
'bibtex-user-optional-fields' has been renamed to 'bibtex-aux-opt-alist'.
The old name is an obsolete alias.
'bibtex-include-OPTkey' is now obsolete and its default is nil.
Use 'bibtex-aux-opt-alist' instead.
New user option 'bibtex-entry-ask-for-key'.
When non-nil, 'bibtex-entry' asks for a key.
'bibtex-string-file-path' and 'bibtex-file-path' are lists of directories.
For backward compatibility, considered obsolete, these user options
may still be strings of colon-separated lists of directories.

Midnight mode

Midnight mode is a garbage collector for buffers that runs, well, around midnight, or at a time of your choosing. It is well worth using, in my opinion, but watch your six: you want it to delete trash buffers and never anything important. It comes with a quite conservative (and to me mostly useless) set of defaults

My setting looks like this:

(use-package midnight
  :custom ((midnight-mode 1)
           (clean-buffer-list-delay-general 5)
           (clean-buffer-list-kill-buffer-names
            '("*Buffer List*" "*Compile-Log*" "*vc*" "*vc-diff*" "*diff*" "*gnus work*" "*Backtrace*"))
           (clean-buffer-list-kill-regexps (list (rx bos (? " ") (| "*magit" "magit-" "*Customize" "*mm*-")))))
  :config
  (midnight-delay-set 'midnight-delay 6400))
Change for activating the mode.
Putting '(require 'midnight)' in your init file no longer activates the
mode.  Now, one needs to customize 'midnight-mode' to non-nil or say
'(midnight-mode 1)', instead.

Python mode

Python, but note not the TS mode. If you’re a Python hacker try my Combobulate package.

New 'repeat-map' for Python indentation commands.
The commands 'python-indent-shift-left' and 'python-indent-shift-right'
can now be repeated using 'repeat-mode'.  With 'repeat-mode' enabled,
after invoking one of these commands via 'C-c <' or 'C-c >', you can
type '<' or '>' to repeat the command.
Prefer "python" for 'python-interpreter' and 'python-shell-interpreter'.
On recent versions of mainstream GNU/Linux distributions, "python"
either does not exist or it points to Python 3.  These user options now
default to using "python", falling back to "python3" if it does not
exist.  If "python" points to Python 2 on your system, you now need to
customize these variables to "python3" if you want to use Python 3
instead.
Python 2 support is now optional and disabled by default.
Since Python 2 EOL was over 5 years ago, this release removes Python
2-only builtins such as "file" from the default highlighting in
'python-mode' and 'python-ts-mode'.  If you would like them highlighted,
customize the new user option 'python-2-support' to a non-nil value and
restart Emacs.
New Python support for 'electric-layout-mode'.
'DEL' can delete text in the active region.
When point is between indentation, the command
'python-indent-dedent-line-backspace' (by default bound to 'DEL') now
deletes the text in the region and deactivates the mark if Transient
Mark mode is enabled, the mark is active, and the value of the prefix
argument is 1.

Mmm, no, thank you. Python is a twitchy whitespace language. And heuristic whitespace deletion across multiple lines possible? Nah.

'python-eldoc-function-timeout' now accepts floating-point numbers.
To allow for finer-grained adjustment of timeout for
'python-eldoc-function', 'python-eldoc-function-timeout' now accepts
floating-point numbers as well as integers.

But why is that a python-only construct?

The default value of 'python-shell-completion-setup-code' is changed.
A new function is added to the setup code.  Users who modify this option
may need to update the value for the Python shell completion to work.

Tmm Menubar

This is an in-buffer emulation of Emacs’s menu-bar-mode using a modal-like system that is reminiscent of what Magit would eventually end up looking like, sort of. It’s one of several ways of opening the menu bar (M-x menu-bar-open being another)

New shortcut '^' to navigate to the previous menu.
New user option 'tmm-shortcut-inside-entry'.
When non-nil, highlight the character shortcut in the menu entry's
string instead of prepending it and 'tmm-mid-prompt' to said entry.

Foldout

Outline-mode, which is what Org (was? is?) based off of back in the day as an outliner, has a number of extensions. foldout is one of them; it is not autoloaded by default.

Improved behavior of 'foldout-exit-fold' with a negative prefix argument.
When 'foldout-exit-fold' is called with a negative argument (so that the
exited fold remains visible), the position of point and window view are
preserved.
New command 'foldout-widen-to-current-fold'.
This command widens the view to the current fold level when in a fold,
or behaves like 'widen' if not in a fold.

MPC

MPC is a thin wrapper around the MPC daemon, a music player. I have never used it. I recommend EMMS if you want something with a wider reach: there are spotify plugins and all sorts for it.

New user option 'mpc-notifications'.
When non-nil, MPC (the Emacs front-end to Music Player Daemon) displays
a desktop notification when the song changes, using
'notifications-notify'.  The notification's title and body can be
customized using the new user options 'mpc-notifications-title' and
'mpc-notifications-body'.
New user option 'mpc-crossfade-time'.
When non-nil, MPC will crossfade between songs for the specified number
of seconds.  Crossfading can be toggled using the command
'mpc-toggle-crossfade' or from the MPC menu.
New command 'mpc-describe-song'.
This command displays information about the currently playing song or
song at point in the "*MPC-Songs*" buffer.  The list of tags to display
can be customized using the new user option 'mpc-song-viewer-tags' and
the appearance of the list with the new faces 'mpc-table-key',
'mpc-table-value', and 'mpc-table-empty'.
New command 'mpc-server-stats'.
This command displays information about the connected MPD server.  The
appearance of the list can be customized with the new faces
'mpc-table-key' and 'mpc-table-value'.

VC

VC is Version Control, a wrapper around a slew of version control systems, including git, subversion, hg and many more. I really rate it. Before Magit/git became a thing, it was a life saver, abstracting away the tedium of each VCS into a set of common key bindings.

Honestly, between me you and the lamp post, as VC gains more and more functionality, I find I reach for Magit less and less. I always preferred the way VC works.

Printing root branch logs has moved to 'C-x v b L'.
Previously, the command to print the root log for a branch was bound to
'C-x v b l'.  It has now been renamed from 'vc-print-branch-log' to
'vc-print-root-branch-log', and bound to 'C-x v b L'.  This is more
consistent with the rest of the 'C-x v' keymap, and makes room for a new
fileset-specific branch log command.

To undo this change you can use

    (keymap-global-set "C-x v b l" #'vc-print-root-branch-log)
    (with-eval-after-load 'vc-dir
      (keymap-set vc-dir-mode-map "b l" #'vc-print-root-branch-log))
New command 'C-x v b l' ('vc-print-fileset-branch-log').
This command prints the log of VC changes to the current fileset on
another branch.
VC Annotate for Mercurial repositories shows changeset hashes.
To restore showing revision numbers instead of changeset hashes,
customize the new user option 'vc-hg-annotate-show-revision-numbers' to
non-nil.
'vc-hg-working-revision' now returns changeset hashes.
Previously, it returned local revision numbers, but hashes are more
robust for how this function is typically used.
New commands to handle repositories with multiple working trees.
Some VCS support more than one working tree with the same backing
revisions store, such as with Git's 'worktree' subcommand and
Mercurial's 'share' extension.  Emacs now has some commands to manage
other working trees:

- 'C-x v w c': Add a new working tree.
- 'C-x v w w': Visit this file in another working tree.
- 'C-x v w k': Kill buffers visiting this file in other working trees.
- 'C-x v w s': Like 'C-x p p' but limited to other working trees.
- 'C-x v w a': Copy or move fileset changes to another working tree.
- 'C-x v w A': Copy or move all changes to another working tree.
- 'C-x v w x': Delete a working tree you no longer need.
- 'C-x v w R': Relocate a working tree to another file name.

The new user option 'vc-no-confirm-moving-changes' controls whether
'C-x v w a' and 'C-x v w A' ask for confirmation when moving changes
between working trees.  The default is to ask for confirmation.

In addition, Lisp programs that extend VC can invoke the new backend
functions to obtain a list of other working trees, and to add, remove
and relocate them.

Worktrees are becoming a lot more popular because of AI development in particular. I won’t lie: git’s worktrees suck, but at least it has some level of support for shallow copying a repository. I’m glad VC has added a generic wrapper system on top of it. I will have to experiment with it.

Using 'e' from Log View mode to modify change comments now works for Git.
New user option 'vc-allow-rewriting-published-history'.
Some VCS commands can change your copy of published change history
without warning.  In VC we try to detect before that happens, and stop.
You can customize this option to permit rewriting history even though
Emacs thinks it is dangerous.
'vc-clone' is now an interactive command.
When called interactively, 'vc-clone' now prompts for the remote
repository address, and the directory into which to clone the
repository.  It tries to automatically determine the VC backend for
cloning, or prompts for that, too.

Useful. VC is weird like that; it’s surprisingly feature rich, and yet it is often missing little convenience features like this.

'vc-clone' now accepts an optional argument OPEN-DIR.
When the argument is non-nil, the function switches to a buffer visiting
the directory into which the repository was cloned.
'vc-revert' is now bound to '@' in VC Directory.

C-x v d asks your VCS to show a directory overview. It’s the focal point of Emacs’s VC in many ways.

'vc-revert' is now additionally bound to 'C-x v @'.
This is in addition to 'C-x v u'.
'vc-rename-file' is now bound to 'C-x v R'.
'vc-revert' now works on directories listed in VC Directory.
Reverting a directory means reverting changes to all files inside it.
New global minor mode 'vc-auto-revert-mode'.
This is like 'global-auto-revert-mode' but limited to VCS-tracked files.
As compared with VC's existing, default support for reverting files
after VCS operations, the new mode is a more reliable way to ensure that
Emacs reverts buffers visiting tracked files when VCS operations change
the contents of those files.
New commands to cherry-pick and revert revisions.
The commands 'vc-cherry-pick', 'vc-revert-or-delete-revision',
'vc-revert-revision' and 'vc-delete-revision' let you copy revisions
between branches, revert and delete revisions.
From Log View buffers, you can use 'C' to cherry-pick the revision at
point or all marked revisions, and 'R' to undo the revision at point or
all marked revisions.
New commands to rewind branches.
In Log View mode, 'x' deletes revisions newer than the revision at point
from the history of the current branch, though without undoing the
changes made by those revisions to the working tree.  'X' is similar
except that it does remove the changes from the working tree.
New command 'log-edit-done-strip-cvs-lines'.
This command strips all lines beginning with "CVS:" from the buffer.
It is intended to be added to the 'log-edit-done-hook' so that
'vc-cvs-checkin' behaves like invoking 'cvs commit [files...]' from the
command line.
New user options 'vc-resolve-conflicts' and 'vc-*-resolve-conflicts'.
They control whether to mark a conflicted file as resolved when saving.
You can now control it globally, with 'vc-resolve-conflicts', or for
specific backends with 'vc-bzr-resolve-conflicts',
'vc-hg-resolve-conflicts', and 'vc-svn-resolve-conflicts'.
New value for 'vc-git-resolve-conflicts'.
The option now accepts the symbol 'default' as a value, which is
its default value.  Effectively, the default value hasn't changed,
since 'vc-resolve-conflicts' defaults to t, the previous default value
for 'vc-git-resolve-conflicts'.
VC Directory can now automatically add and remove marks on other lines.
When you try to use a mark or unmark command where doing so would
be permitted only if other lines were marked or unmarked first, Emacs
will now ask you if you'd like to change the marks on those other lines.
For example, if you try to mark a file contained within a directory that
is already marked, Emacs will offer to unmark the directory, first.
Previously, Emacs would simply refuse to make any changes.
You can customize 'vc-dir-allow-mass-mark-changes' to restore the old
behavior or dispense with the prompting.
'C-x v x' and VC Directory's 'd' command can now delete unregistered files.
Previously, these commands could only delete registered files.
To restore the old, more limited behavior for VC Directory, you can do

    (keymap-set vc-dir-mode-map "d" #'vc-dir-clean-files)
New VC Directory bindings 'z d' and 'D' to delete Git stashes.
These correspond to the existing 'z p' to pop a stash and 'P' to pop the
stash at point (deleting the stash at point is also bound to 'C-k').
New VC Directory command 'V' ('vc-dir-root-next-action').
This is like 'v' ('vc-next-action') but applies to the whole VC Directory
buffer, ignoring the position of point and any marks.  This is useful to
check in all local changes at once.
VC Directory can now register files when checking in mixed filesets.
Previously, if some files to be checked in were unregistered but others
were added, removed or edited, Emacs would refuse to proceed.
Now Emacs prompts to register the unregistered files, so that all
files in the fileset are in a compatible state for a checkin.
'C-x v v' handles missing and removed files more consistently.
Missing files are those which have been removed from the filesystem but
which are still tracked by version control.  Removed files are those
scheduled to be removed from version control in the next commit.
Previously, different backends were inconsistent about applying these
statuses to files, and 'C-x v v' behaved subtly differently for the two
statuses.  The combination of these differences between backends and in
'C-x v v' behavior was confusing.  Now,

- in VC Directory, you can use 'C-x v v' on missing files to mark them as
  removed
- when committing, you can include missing files in a set of files with
  different statuses, just like you've always been able to include
  removed files.

In addition, the Git backend has been fixed to display missing files as
'missing' instead of incorrectly subsuming them to the 'removed' status.

There is still some further work to do to rationalize VC's handling of
file removal.

C-x v v is the workhorse command. It does the next logical action: add a file; commit changes; etc.

New user option 'vc-dir-auto-hide-up-to-date'.
If you customize this option to 'revert', the 'g' command to refresh
the VC Directory buffer also has the effect of the 'x' command.
That is, typing 'g' refreshes the buffer and also hides items in the
'up-to-date' and 'ignored' states.
If you customize this option to any other non-nil value, then in
addition, hide items whenever their state would change to 'up-to-date'
or 'ignored'.
New user option 'vc-dir-save-some-buffers-on-revert'.
If you customize this option to non-nil, Emacs will offer to save
relevant buffers before generating the contents of a VC Directory buffer
(like the third-party package Magit does with its status buffer).
New commands to report incoming and outgoing diffs.
'vc-root-diff-incoming' and 'vc-root-diff-outgoing' report diffs of all
the changes that would be pulled and would be pushed, respectively.
They are the diff analogues of the existing commands
'vc-root-log-incoming' and 'vc-root-log-outgoing'.

In particular, 'vc-root-diff-outgoing' is useful as a way to preview
your push and ensure that all and only the changes you intended to
include were committed and will be pushed.

'vc-diff-incoming' and 'vc-diff-outgoing' are similar but limited to the
current VC fileset.
New commands to report information about unintegrated changes.
'C-x v T =' ('vc-diff-unintegrated') and 'C-x v T D'
('vc-root-diff-unintegrated') report diffs of changes since the merge
base with the remote branch, including uncommitted changes.
'C-x v T l' ('vc-log-unintegrated') and 'C-x v T L'
('vc-root-log-unintegrated') show the corresponding revision logs.
These are useful to view all outstanding (unmerged, unpushed) changes on
the current branch.  They are also available as 'T =', 'T D', 'T l' and
'T L' in VC Directory buffers.

'C-x v T R =' ('vc-diff-remote-unintegrated'), 'C-x v T R D'
('vc-root-diff-remote-unintegrated'), 'C-x v T R l'
('vc-log-remote-unintegrated') and 'C-x v T R L'
('vc-root-log-remote-unintegrated') are corresponding commands which
report information about the remote versions of a topic branch.

Ah this is really useful. I’ll have to experiment with them and see if they present things in a more useful way than Magits’. One common problem I often have with work is that I do a lot of work in worktrees nowadays. I’ve built out some shell scripts to see how far ahead/behind they are, and whether they cleanly merge. So it’d be nice if I can re-use some of the code here maybe to do that.

New commands to report combined diffs of all local changes.
'C-x v E =' ('vc-diff-outgoing-and-edited') and 'C-x v E D'
('vc-root-diff-outgoing-and-edited') report combined diffs of all
outgoing changes plus any uncommitted changes.  They are useful to show
all work that's present only locally.
New user option 'vc-use-incoming-outgoing-prefixes'.
If this is customized to non-nil, 'C-x v I' and 'C-x v O' become prefix
commands, such that the new incoming and outgoing commands have global
bindings:

- 'C-x v I L' is bound to 'vc-root-log-incoming'
- 'C-x v I =' is bound to 'vc-diff-incoming'
- 'C-x v I D' is bound to 'vc-root-diff-incoming'
- 'C-x v O L' is bound to 'vc-root-log-outgoing'
- 'C-x v O =' is bound to 'vc-diff-outgoing'.
- 'C-x v O D' is bound to 'vc-root-diff-outgoing'.
New display of outgoing revisions count in VC Directory.
If there are outgoing revisions, VC Directory now includes a count of
how many in its headers, to remind you to push them.  If this is slow,
you can disable it by customizing 'vc-dir-show-outgoing-count' to nil.
(In Emacs 32 it will be populated asynchronously.)
New user option 'vc-async-checkin' to enable async checkin operations.
Currently only supported by the Git and Mercurial backends.
New user option 'vc-display-failed-async-commands'.
If non-nil, displays the buffer with the output of the failed command
when an asynchronous VC command (e.g., pulls and pushes) fails.
New 'log-edit-hook' option to display diff of changes to commit.
You can customize 'log-edit-hook' to include its new
'log-edit-maybe-show-diff' option to enable displaying a diff of the
changes to be committed in a window.  This is like the 'C-c C-d' command
in Log Edit mode buffers, except that it does not select the "*vc-diff*"
buffer's window, and so works well when added to 'log-edit-hook'.
'vc-annotate' now abbreviates the Git revision in more cases.
In Emacs 30, 'vc-annotate' gained the ability to abbreviate the Git
revision in the buffer name.  Now, it also abbreviates the Git revision
when visiting other revisions, such as with
'vc-annotate-revision-previous-to-line'.
New buffer-local variable 'vc-buffer-overriding-fileset'.
Primarily intended for buffers not visiting files, this specifies the
VC backend and VCS-managed file name or file names to which the buffer's
contents corresponds.  It overrides the behavior of 'vc-deduce-fileset'.
This replaces and generalizes the old 'vc-annotate-parent-file'.
New buffer-local variable 'vc-buffer-revision'.
This specifies the revision to which the buffer's contents corresponds.
This replaces and generalizes the old 'vc-annotate-parent-rev'.
The 'log-incoming' and 'log-outgoing' functions are deprecated.
Backend authors should implement the 'incoming-revision' and 'mergebase'
backend functions instead.  These are jointly sufficient to support the
'C-x v I' and 'C-x v O' commands.
Marking revisions in Log View now works more like other modes.
Previously, 'm' toggled whether the current revision was marked, and
didn't advance point.  Now 'm' only adds marks, 'u' removes marks, and
both advance point, like how marking works in Dired and VC Directory.
You can get back the old behavior with something like this:

    (with-eval-after-load 'log-view
      (keymap-set log-view-mode-map "m" #'log-view-toggle-mark-entry))

In addition, a new command 'U' removes all marks.
New commands 'M-RET', 'M-p' and 'M-n' in Log View mode.
'M-RET' expands the current entry, if relevant, and displays its diff in
another window.  'M-p' and 'M-n' move to the previous and next entries,
respectively, expand them if relevant, and display their diffs.

You can use these three commands together to more easily view all the
log entries and diffs of a series of revisions: use 'M-RET' on the first
revision, then either 'M-n' or 'M-p' repeatedly to view the others.

Log view (C-x v l) is a feature (along with C-x v = for a diff of changes in the current buffer) that I use the most. They work anywhere and give me immediate insight into changes in a file.

New command 'w' in Log View mode.
The new command 'log-view-copy-revision-as-kill', bound to 'w' in Log
View mode, copies to the kill ring the ID of the revision at point in
the log entry.  If there are marked revisions, it copies the IDs of
those, instead.
New commands 'vc-print-change-log' and 'vc-print-root-change-log'.
These are just like 'vc-print-log' and 'vc-print-root-log' except that
they have a different prefix argument that some users may prefer.
With a prefix argument, these commands prompt for a branch, tag or other
reference to a revision to log, and a maximum number of revisions to
print.  If you find this prefix argument more useful, or more mnemonic,
than the prefix arguments that 'vc-print-log' and 'vc-print-root-log'
already have, consider replacing the default global bindings, like this:

    (keymap-global-set "C-x v l" #'vc-print-change-log)
    (keymap-global-set "C-x v L" #'vc-print-root-change-log)
New command alias 'vc-restore' for 'vc-revert'.
The 'diff-restrict-view' command is disabled by default.
This command is Diff mode's specialized 'narrow-to-region'.
'narrow-to-region' has long been disabled by default, so for
consistency, 'diff-restrict-view' is now too.
To enable it again, use 'enable-command'.
'C-x v !' now has its own input history.
'C-x v +' for Git pulls from a configured push remote.
If the current branch has a configured push remote, the default
arguments to 'git pull' will cause a pull from the push remote.
You can use 'C-u C-x v +' to preview or change the arguments.
'C-u C-x v +' and 'C-u C-x v P' for Git have an input history.
This was already in place for Mercurial.
vc-dav.el is now obsolete.

Package

Emacs’s package manager.

No longer warn if a package has no footer line.
package.el no longer warns for packages without a "footer line", which
is the line that usually appears at the very end of an Emacs Lisp file:

    ;;; FILENAME ends here
New optional argument to 'package-autoremove'.
An optional argument NOCONFIRM has been added to 'package-autoremove'.
If it is non-nil (interactively, with a prefix argument),
'package-autoremove' will not prompt the user for confirmation before
removing packages.
New prefix argument for 'package-install-selected-packages'.
When invoked with a prefix argument, 'package-install-selected-packages'
will not prompt the user for confirmation before installing packages.
'package-refresh-contents' runs asynchronously.
Refreshing the package index will no longer block when invoked
interactively.
'package-upgrade' no longer accepts a string argument.
When called from Lisp, it now only accepts a symbol.
'package-install-from-buffer' respects files marked by Dired.
When invoking the command in a Dired buffer with marked files,
the command will copy only those files.
'package-isolate' can now also install packages.
If a package is missing, 'package-isolate' will fetch the missing
tarballs and prepare them to be activated in the sub-process.
package-x.el is now obsolete.
The command 'package-vc-install-from-checkout' is now obsolete.
Use the User Lisp directory instead: see Info node "(emacs) User Lisp
Directory".  This also means that combining the 'use-package' keywords
':vc' and ':load-path' is obsolete.

Er… what. So the idea is that you put files in 31’s new user lisp directory, which is the feature I talked about earlier that finds and installs autoload cookies; byte compiles; etc.

And the idea here is that you just git clone or whatever in this directory instead of using the just recently added(!!) :vc feature in use-package.

EDIT: Seems this is only when combined with :load-path which is still puzzling. :load-path is not necessarily the same as the :vc root.

But now that disconnects people who prefer colocating all their changes in use-package definitions. They now need the new user directory; a method of installing/cloning their packages into that directory; and at no point does that tie back to a use-package definition.

Package menu now highlights packages marked for installation or deletion.
Package menu now displays the total number of the package type.
The package menu now displays in the mode line the total number of
packages installed, the total number of packages from all the package
archives, the total number of packages to upgrade and the total number
of new packages available.
New functions to query builtin package information.
'package-versioned-builtin-packages' returns a list of symbols of
built-in packages; 'package-builtin-package-version' returns the
version-list of a given package symbol.  These functions provide public
interfaces for external tools to query information about built-in
packages.
Uninstalling a package now removes its directory from 'load-path'.
Packages can be reviewed before installation or upgrade.
The user option 'package-review-policy' can configure which packages
the user should be allowed to review before any processing takes place.
The package review can include reading the downloaded source code,
presenting a diff between the downloaded code and a previous
installation or displaying a ChangeLog.
New command 'package-autosuggest'.
Using a built-in database of ELPA package suggestions, this command will
install viable packages if no specific major mode is available for the
contents of the current buffer.

That feels useful to people. Teach them about other useful packages…

New minor mode 'package-autosuggest-mode'.
When enabled, this displays indications about the availability of add-on
ELPA packages for the current buffer and suggestions for installing
those packages.  The default is to show a button on the mode line that
can be used to install such packages, but you can customize the
presentation style of these suggestions using
'package-autosuggest-style'.

Buuut of course it is not enabled by default.

New user option 'package-retention-policy'.
This user option controls what previous packages versions to keep on
upgrade.  By default, this is set to nil, to keep the previous behavior.
Packages are now checked for recursive dependencies before installing.
If a package has dependencies not available on any of the archives in
'package-archives', it will appear as unavailable, with the reasons
stated in its description.

Rcirc

One of two IRC clients in Emacs.

Authentication via NickServ can access auth source passwords.
For details, consult 'rcirc-authinfo'.

Xref

As I mentioned before, xref is Emacs’s cross-referencing implementation, and a replacement for the old TAGS finder system.

Xref commands that jump to some location use 'display-buffer'.
The commands that jump to some location use 'display-buffer' and specify
the category 'xref-jump'.  As a result, you can customize how the
destination window is chosen using 'display-buffer-alist'.  Example:

    (setq display-buffer-alist '(((category . xref-jump)
                                  (display-buffer-reuse-window
                                   display-buffer-use-some-window)
                                  (some-window . mru))))

Neat change. But of course it’s yet more complexity front-loaded into display-buffer-alist, probably the most complex variable in Emacs today. Basically when Emacs has to pick where a buffer goes, it goes through a cascading list of user-customized and predefined options with safe fallbacks. But display-buffer-alist (and therefore Emacs) has to be taught to show buffers in just the right way, or your customizations won’t apply.

But how can display-buffer-alist (or you, the customizer) know that a buffer that is about to appear came from xref? Well, you can’t… so there is a new (category N) selector that effectively says “if you come from an xref-jump, reuse a an existing window that displays this buffer; otherwise pick a window, but make sure that you pick the most-recently-used.”

Confused? See Demystifying Emacs’s Window Manager.

New minor mode 'xref-mouse-mode'.
This minor mode binds 'xref-find-definitions-at-mouse' to
'C-<down-mouse-1>', allowing you to control-click to jump to a
definition, following the convention from other editors.  The global
minor mode 'global-xref-mouse-mode' enables this in all buffers.
New command 'xref-change-to-xref-edit-mode'.
It is bound to 'e' and it switches an Xref buffer into an "editable"
mode, like similar features in Occur and Grep buffers.

Wonderful news. Editable buffers is such an Emacs-shaped feature to have, I was quite surprised when Xref first came out that it did not do this at all.

Revert

Variable 'revert-buffer-in-progress' has been renamed.
The old name, 'revert-buffer-in-progress-p', is kept as an obsolete
variable alias.  (Symbol names with a trailing '-p' are conventionally
reserved for predicates.)

Autorevert

The auto-revert-mode minor mode reverts a buffer when certain conditions are met: VC changes, file changed on the file system, and so on.

New variable 'inhibit-auto-revert-buffers'.
While a buffer is member of this variable, a list of buffers,
auto-reverting of that buffer is suppressed.

I actually thought Emacs already had this feature. So I am a little surprised that it only just made an appearance in Emacs 31.

I suspect most people won’t have an immediate use for this, as auto-revert is rather conservative about reversions.

New macro 'inhibit-auto-revert'.
This macro adds the current buffer to 'inhibit-auto-revert-buffers',
runs its body, and removes the current buffer from
'inhibit-auto-revert-buffers' afterwards.
New variable 'auto-revert-buffer-in-progress'.
'auto-revert-buffer' binds this variable to a non-nil value while it is
working.  This can be used by major mode 'revert-buffer-function'
implementations to suppress messages in Auto Revert modes, for example.

Strokes

Strokes is a mouse gesture recognition mode. Draw shapes with your mouse and you can trigger a range of actions. See M-x strokes-help.

'strokes-mode' no longer demands the presence of a mouse.
'strokes-mode' now permits itself to be enabled if no mouse is
connected, to facilitate enabling 'strokes-mode' in sessions where the
availability of a mouse device varies during execution (as is frequently
observed on Android).

Okay so the mouse thing no longer needs a mouse, but I can’t intuit if that means touch gestures now trigger it on android? Because that… seems like a natural fit for strokes-mode?

Yank Media

'yank-media' now auto-selects the most preferred MIME type.
Major-mode authors can customize the variables
'yank-media-autoselect-function' and/or 'yank-media-preferred-types' to
change the selection rules.

Most people know you can yank text with C-y; few know you can yank media (images in clipboard) with M-x yank-media. I use it with mu4e, the Emacs email client. In typical Emacs fashion, the yank-media command asks me a lot of useless information like whether I want to use mime type ppm or equally antique stuff when what I really want is just… you know, to insert an image? So this is a welcome change.

Remember

Remember is a bit like org-capture, but predates it. Pretty sure org mode can capture remember snippets for you also.

Remember mode is now a minor mode.
The 'remember' command enables the major mode set in
'remember-initial-major-mode' and then the 'remember-mode' minor mode in
the 'remember-buffer'.  This allows users to customize the major mode
used to write notes.
New handler that appends remember data in directory.
The 'remember-append-in-data-directory' handler appends remember data in
a file, that file being chosen by the user through the minibuffer.
New prefix map for remember commands.
Meant to be given a global binding convenient to the user.  Example:

    (keymap-global-set "C-c r" 'remember-prefix-map)

Speedbar

Speedbar is a file browser that pops up a frame. I never liked it: looks awful, opens a frame, hoards space, and tells me mostly-useless information: what is going on in a directory. I prefer dired for when I want to know what’s up in a directory, but I appreciate that many people do not agree with me here.

New commands for Speedbar.

- 'speedbar-window-mode' opens Speedbar in a window instead of a frame.
- 'speedbar-window' is an alias for 'speedbar-window-mode'.

About time. Speedbar got merged in as part of the CEDET large hadron collider event some 15 years ago and it’s sat there untouched ever since.

New user options for Speedbar.

- 'speedbar-prefer-window' tells 'speedbar' to open a side window
  instead of a frame.
- 'speedbar-window-dedicated-window' defines whether 'speedbar' is
  displayed in a dedicated window.
- 'speedbar-window-default-width' defines the initial width of the
  'speedbar-window'.
- 'speedbar-window-max-width' defines the maximum width of the
  'speedbar-window' when it is closed and then restored.

Ugh. I mean look I get that if you’re going to cram speedbar into a window, you probably want it flush in a sidebar (meaning it always appears in one of four edges of your frame) and that it should never share its window with another buffer (hence marking it dedicated.)

But Emacs already has a display buffer system for this; by adding weird variables (even if they edge-trigger and set a display-buffer-alist rule) you’re effectively creating the same two-tier system Emacs has spent 10 years trying to undo.

'speedbar-easymenu-definition-trailer' is now a function.

Icomplete

Icomplete is a fast completion system going back to the 90s. It saw a lot of updates as Emacs modernized its Minibuffer Completion system. Ido Mode is loosely inspired by/has borrowed code from icomplete.

Change in meaning of 'icomplete-show-matches-on-no-input' (again).
For Emacs 28 to Emacs 30, when 'icomplete-show-matches-on-no-input' was
non-nil, 'RET' had special behavior when the minibuffer's contents was
equal to the initial input it had right after minibuffer activation.
In that case, 'RET' would choose the first completion candidate, if
there was one, instead of the minibuffer's default value.
'RET' has now returned to selecting the default value in this case; you
can use 'C-j' to choose the completion under point instead.
You can opt back in to the special behavior of 'RET' like this:

    (keymap-set
      icomplete-minibuffer-map "<remap> <minibuffer-complete-and-exit>"
      #'icomplete-ret)
New user options for 'icomplete-vertical-mode'.
New user options have been added to enhance 'icomplete-vertical-mode':

- 'icomplete-vertical-in-buffer-adjust-list' aligns in-buffer
  completion to the original cursor column.
- 'icomplete-vertical-render-prefix-indicator' adds a prefix indicator
  to completion candidates.
- 'icomplete-vertical-selected-prefix-indicator' specifies the prefix
  string for the selected candidate.
- 'icomplete-vertical-unselected-prefix-indicator' specifies the prefix
  string for non-selected candidates.
New faces for 'icomplete-vertical-mode'.
New faces have been added to 'icomplete-vertical-mode':

- 'icomplete-vertical-selected-prefix-indicator-face' controls the
  appearance of the selected candidate prefix.
- 'icomplete-vertical-unselected-prefix-indicator-face' controls the
  appearance of unselected candidate prefixes.

Custom

Custom is Emacs’s customize interface and internal subsystems.

New function 'custom-initialize-after-file-load'.
Useful to delay initialization to the end of the file, so it can use
functions defined later than the variable, as is common for minor modes.
'define-globalized-minor-mode' now automatically uses it if the
':init-value' is non-nil.

Probably a sensible thing to enable if you’re a heavy Customize user.

New major mode 'Custom-dirlocals-mode'.
This is intended for customizing directory-local variables in the
current directory's ".dir-locals.el" file.
New binding 'C-c C-k' for 'Custom-reset-standard'.
New command 'C-c TAB' ('Custom-goto-first-choice').
When first opening the customization interface for a user option, you
can use this command as a shortcut to jump to the first actionable
button or field (for instance an on/off button for boolean options, or a
text field for other values).
':set' functions should accept an optional argument BUFFER-LOCAL.
This is the third argument, in addition to SYMBOL and VALUE.  If that
argument's value is 'buffer-local', the ':set' function should use
'set-local' to set the value of its SYMBOL argument locally in the
current buffer.  This is used by 'setopt-local', which will signal an
error if this optional argument is not supported by the ':set' function.
The 'defcustom' ':local' keyword can now be 'permanent-only'.
This means that the variable's 'permanent-local' property is set to t,
without marking it as automatically buffer-local.

Pulse

Another CEDET feature that landed in Emacs some 15 years ago. This one pulses stuff in the ui. Quite handy if a bit distracting, and now used in a wide range of things.

New user option 'pulse-face-duration'.
This option controls the flash duration for 'flash-face-bell-function'
and 'flash-echo-area-bell-function'.
New function 'pulse-faces'.
This function pulses a specified list of faces.  The pulse duration is
determined by the new user option 'pulse-face-duration'.

Edebug

Emacs’s Emacs Lisp debugger, written in (of course) Emacs Lisp. Not to be confused with the classic debug elisp debugger, of course.

New command 'edebug-bounce-to-previous-value' (bound to 'P').
This command temporarily displays the outside current buffer with the
outside point corresponding to the previous value, where the previous
value is what Edebug has evaluated before its last stop point or what
the user has evaluated in the context outside of Edebug.

This replaces the binding of command 'edebug-view-outside' to 'P', which
is still available on 'v'.

Flymake

Flymake is Emacs’s “on-the-fly” syntax/error/lint checker that highlights info, warnings and errors in the buffer.

Enhanced 'flymake-show-diagnostics-at-end-of-line'.
The new value for this user option 'fancy' attempts to lay out
diagnostics below the affected line, using unicode graphics to point to
the diagnostic locus.

Flymake is really a two-tier system. There’s the high-level overview: squiggly underlines, backgrounded text, etc. that tells you there is a reported issue. The second tier is the detail tier that tells you exactly what happens. You can opt for a popup buffer and/or eldoc integration.

Now there is a third. You can now tell Emacs to insert diagnostic messages flush in the buffer at the end of the line where the change occurred, like other IDEs.

I’m sure a lot of people will find it very useful, but it is not for me. Emacs’s overlay system is nice and all, but flickering text and stuff disappearin and reappearing as the flymake mode reruns will surely drive a lot of people crazy.

Enhanced 'flymake-show-buffer-diagnostics'.
The command 'flymake-show-buffer-diagnostics' is now capable of
highlighting a nearby diagnostic in the resulting listing.
Additionally, it is bound to mouse clicks on fringe and margin
indicators, operating on the diagnostics of the corresponding line.
You can bind it yourself in other situations too, such as in the
diagnostic overlay map.
More powerful 'flymake-make-diagnostic' API.
Flymake backends can now specify origin and code attributes, allowing
Flymake and other extensions to segregate diagnostics based on this
extended information.
New user option 'flymake-diagnostic-format-alist'.
This provides fine-grained control over diagnostic formatting across
different contexts, allowing you to specify which components (origin,
code, message or one-liner message) appear in each output destination.

One of the problems with flymake is that its backends do crude filtering to separate the wheat from the chaff. But if you want to exclude very particular things, it’s often a bit of a chore, as you’re effectively forced to rewrite the filter routine to handle weird exceptions like never show warnings X, Y, and Z.

Dynamic column sizing in diagnostic listings.
The tabulated listings produced by 'flymake-show-buffer-diagnostics' and
'flymake-show-project-diagnostics' now automatically adjust their column
widths based on content, optimizing display space and readability.
New value 'auto' of user option 'flymake-indicator-type'.
This value (set by default) tries to use fringes if possible, otherwise
falls back to margins.
New user option 'elisp-flymake-byte-compile-executable'.
This allows customizing the Emacs executable used for Flymake byte
compilation in 'emacs-lisp-mode'.  This option should be set when editing
Lisp code which will run with a different Emacs version than the running
Emacs, such as code from an older or newer version of Emacs.  This will
provide more accurate warnings from byte compilation.

SQLite

Emacs has builtin support for SQLite, though it does require that you compile Emacs with it. Most distros seem to do so by default.

SQLite databases can now be opened in read-only mode.
The new optional argument READONLY of function 'sqlite-open' allows
opening an existing database only for reading.
'sqlite-open' now recognizes 'file://' URIs as well as file names.
'file://' URIs are supported by default.  In the unusual case that a
normal file name starts with "file:", you can disable the URI
recognition by calling 'sqlite-open' with the new optional argument
DISABLE-URI non-nil.
'sqlite-close' now does nothing if the connection is already closed.

GUD

GUD, or the Grand Unified Debugger, is a multi-window debugger for the likes of gdb, pdb and so on. It is very feature rich.

'pdb', 'perldb', and 'guiler' suggest debugging the current file via 'M-n'.
When starting these debuggers (e.g., 'pdb') while visiting a file,
pressing 'M-n' in the command prompt suggests a command line including
the file name, using the minibuffer's future history.

Calendar

Emacs’s M-x calendar is surprisingly useful and feature rich, with a laundry list of builtin calendaring features.

New command 'C-l' ('calendar-recenter').
This command recenters the month of the date at point.
Mouse wheel bindings for scrolling the calendar.
You can now use the mouse wheel to scroll the calendar by 3 months.
With the shift modifier, it scrolls by one month.  With the meta
modifier, it scrolls by one year.
Simpler key bindings for navigation in calendar by months and by years.
The month and year navigation key bindings 'M-}', 'M-{', 'C-x ]' and
'C-x [' now have the alternative keys '}', '{', ']' and '['.
Avoid modifying Calendar's user options.
The user options 'calendar-mark-holidays-flag' and
'calendar-mark-diary-entries-flag' are no longer modified when changing
the marking state in the calendar buffer.
New library for iCalendar data.
A new library has been added to Emacs for handling iCalendar (RFC 5545)
data.  The library is designed for reuse in other parts of Emacs and in
third-party packages.  Package authors can find the new library in the
Emacs distribution under "lisp/calendar/icalendar-*.el".

Most of the functions and variables in the older icalendar.el have been
marked obsolete and now suggest appropriate replacements from the new
library.  diary-icalendar.el provides replacements for the diary-related
features from icalendar.el; see below.

Diary

Diary - a diary that sort-of goes hand in hand with the calendar feature. It’s got a lot of esoteric features that warrants a deeper dive, to be honest. If you’re in the market for such a feature, do check out its (rather basic) info pages and associated calendaring features.

New user option 'diary-date-insertion-form'.
This user option determines how dates are inserted into the diary by
Lisp functions.  Its value is a pseudo-pattern of the same type as in
'diary-date-forms'.  It is used by 'diary-insert-entry' when inserting
entries from the calendar, or when importing them from other formats.
New library diary-icalendar.el.
This library reimplements features previously provided by icalendar.el:
import from iCalendar format to the diary, and export from the diary to
iCalendar.  It also adds the ability to include iCalendar files in the
diary and display and mark their contents in the calendar without
importing them to the diary file.  The library uses the new iCalendar
library (see above) and makes diary import and export more customizable.
New commands to display more months in the calendar.
'calendar-show-more-months' and 'calendar-show-fewer-months' display
more or fewer months in the calendar, respectively.  The calendar shows
at most 12 months and at least 3 months.
'calendar-scroll-left-three-months' and its variants are obsolete aliases.
Because the calendar can now display more than three months, commands
'calendar-scroll-left-three-months' and
'calendar-scroll-right-three-months' have been renamed to
'calendar-scroll-calendar-left' and 'calendar-scroll-calendar-right'.
The old names are kept for now as obsolete aliases.

Calc

One of two calculators in Emacs. This is the uber-advanced Computer Algebra System and reverse polish notation calculator bound to M-x calc. Well worth learning if you need a calculator. Every time I read about it or explore the manual, I learn new amazing things about it.

New user option 'calc-string-maximum-character'.
Previously, the 'calc-display-strings', 'string', and 'bstring'
functions only considered integer vectors whose elements are all in the
Latin-1 range 0-255.  This hard-coded maximum is replaced by
'calc-string-maximum-character', and setting it to a higher value allows
the display of matching vectors as Unicode strings.  The default value
is 0xFF or 255 to preserve the existing behavior.
New user option 'calc-inhibit-startup-message'.
If it is non-nil, inhibit Calc from printing its startup message.  The
default value is nil to preserve the existing behavior.

Time

M-x world-clock, M-x lunar-phases, etc.

New user option 'world-clock-sort-order'.
This option controls the order of timezone entries in the 'world-clock'.
By default, no sorting is done, and entries appear in the same order as
in 'world-clock-list'.  Any format understood by 'format-time-string'
can be used to specify a key for the sort order, which is updated upon
each refresh.  The sort direction can be controlled by using a cons cell
of a format string and a boolean.  Alternatively, a sorting function can
be provided directly.
New user option 'display-time-help-echo-format'.
This option controls the format of the help echo when hovering over the
time.

Fill

Fill region and its new counterpart in 31, the M-x unfill-paragraph.

New variable 'fill-region-as-paragraph-function'.
The new variable 'fill-region-as-paragraph-function' provides a way to
override how functions like 'fill-paragraph' and 'fill-region' fill
text.  Major modes can bind this variable to a function that fits their
needs.  It defaults to 'fill-region-as-paragraph-default'.

Newsticker

This is an RSS feed reader that is built into Emacs. I never really got on with it. I always felt like it had too many stateful things going on, and if you snuck up behind it and ran the wrong command, something always got wedged, and it’d end up in a weird doom loop.

If you love RSS, as I am sure you do, try the Elfeed package instead.

New user option 'newsticker-hide-old-feed-header'.
It controls whether to automatically hide the header of feeds whose
items are all old or obsolete in the plainview "*newsticker*" buffer.
This is only visually interesting if the content of those feeds are also
hidden (see 'newsticker-hide-old-items-in-newsticker-buffer' and
'newsticker-show-descriptions-of-new-items').
New commands to hide and show headers of old newsticker feeds.
The new commands 'newsticker-hide-old-feed-header' and
'newsticker-show-old-feed-header', bound to 'h h' and 's h',
respectively, hide and show the headers of feeds whose items are all old
or obsolete.

CPerl mode

Syntax of Perl up to version 5.42 is supported.
CPerl mode creates Imenu entries for ":writer" generated accessors and
recognizes the new builtin functions "all" and "any".
See https://perldoc.perl.org/5.42.0/perldelta for details.

Zone

M-x zone is Emacs’s screensaver. I do recommend turning it on, but only in an environment where your colleagues are likely to walk past your monitor and see your screen go completely postal.

Zone can scramble multiple windows across multiple frames; it may also
reorganize frames to be a single window.  As before, when a key or mouse
event occurs, all of the frames and windows are restored to their
original state.  This is controlled by three new user options which
control the use of frames and windows beyond the currently active ones.
It identifies suitable buffers for zoning out so that potentially
important buffer contents are not exposed.

Ordinarily, if you’re read this, you’d think what the heck, but… yeah, this is Zone alright.

New user option 'zone-delete-other-windows'.
When non-nil, the frame is made into a single full frame window to hold
the zoned buffer.  If all frames were to be used ('zone-all-frames' set
to non-nil), then all frames are converted to single window frames.
New user option 'zone-all-frames'.
When non-nil, Zone will appear on all visible frames.  While the buffer
scrambling will appear on each frame, it will be the same buffer so they
will all behave the same way.
New user option 'zone-all-windows-in-frame'.
When non-nil, the zoned buffer will be mapped to all of the windows
present on the frame.  If the option is nil, then only the selected
window will show the zoned buffer.  Note, however, that each window
holding the zoned buffer is showing the same zoned buffer.
New variable 'zone-ignored-buffers'.
This variable is a list of criteria for excluding a buffer from
consideration as the source of zoning.  The list has entries that are
tested against each buffer until a suitable one is found.  The criteria
can be a symbol whose name ends in "-mode" which excludes buffers that
are in a mode derived from the specified mode.  It may also be a
function-bound symbol or anonymous function (lambda expression) that is
called with a buffer that returns a non-nil value if it should not be
the Zone source.  Finally, an entry can also be a regular expression
that must not match the buffer's name.

Initially, the list excludes buffers in 'special-mode', in 'image-mode',
containing an encrypted file, is empty, is hidden, or is the "*scratch*"
buffer.  If it cannot locate any acceptable buffers, it will
begrudgingly use the "*scratch*" buffer.

Sometimes I wonder what prompts people to build out all this elaborate complexity for a screensaver that ostensibly exactly no one would ever really get to sit at and admire for any length of time. But that’s Emacs for you.

Abbrev mode

Abbrevs are little word auto corrections you can set up. See Correcting Typos and Misspellings with Abbrev.

You can now enable Abbrev mode by default using Easy Customization.
Customize the user option 'abbrev-mode' to non-nil to enable Abbrev mode
by default in all buffers.

Antlr mode

For writing language grammars for the ANTLR tool.

Variable 'antlr-tool-version' is no longer a user option.
It is now buffer-local and has the symbol 'antlr-v2', 'antlr-v3' or
'antlr-v4' as its value.  The value determines which of the tool
version-dependent customization options are used.  For example, the
command 'antlr-run-tool' uses the option 'antlr-v3-tool-command' (with
default value "java org.antlr.Tool") when 'antlr-tool-version' has the
value 'antlr-v3'.
'antlr-mode' now also works on ANTLR v3 or v4 grammars.
If the variable 'antlr-tool-version' is not set locally, e.g., by the
file's local variables specs, the command sets its local value to
'antlr-v2' if a keyword "class" or "header" appears at the beginning of
the source, or to 'antlr-v3' otherwise.
New 'antlr-v4-mode' is a derived mode of 'antlr-mode'.
It sets 'antlr-tool-version' to value 'antlr-v4', and is automatically
used for files with extension ".g4".
The variable 'antlr-language' is now used more generally.
The variable's value is a symbol which determines which of the
language-dependent customization options are used.  These options
influence font-locking and indentation commands.  Its value is usually
set according to the grammar option "language", see the default values
of 'antlr-v2-language-list', 'antlr-v3-language-list' and
'antlr-v4-language-list'.  This mode now supports C, Delphi, JavaScript,
ObjC, Python and Ruby, in addition to Java and C++.
New user option 'antlr-run-tool-on-buffer-file'.
The command 'antlr-run-tool' now usually runs on the file for the
current buffer.  Customize this user option to nil to get the previous
behavior back.

Hi Lock

Hi-Lock or “Highlighting” (see Highlighting by Word, Line and Regexp) is a cool feature that highlights stuff based on words, regexp, etc.

It’s called “lock” because font locking is how you syntax highlight, and hi locking is therefore the highlighter equivalent of that.

The active region is used for default values in more functions.
If an active region exists, the commands 'hi-lock-line-face-buffer' and
'hi-lock-face-phrase-buffer' now use its contents as their default
value.  Previously, only 'hi-lock-face-buffer' supported this.

Shadowfile

Obscure Emacs feature that maintains copies of files you edit in other places.

'shadow-info-buffer' and 'shadow-todo-buffer' now use ephemeral buffer names.

Display Battery mode

For laptop users or people with fantastically complex battery-backed desktops and servers.

UPower battery status can update automatically without polling.
On systems where the user option 'battery-status-function' is set to
'battery-upower', it is now possible to get battery status updates on
the mode line without polling for changes every
'battery-update-interval' seconds.  Setting this user option to nil
means the mode line will update only when the battery power state,
percentage, or presence in the bay changes.

Etags Regen mode

The tags table is no longer created during completion.
Previously, when there was no tags table loaded and the default
completion function was called, 'etags-regen-mode' ensured that tags
were created.  This has been disabled, and the new user option
'etags-regen-create-on-completion' can be used to enable it again.

Miscellaneous

'tooltip-mode' now shows tooltips on TTY frames after a delay.
Display of tooltips on text-only terminals now happens after
'tooltip-delay', as it already did on GUI terminals.  To get back the
old behavior, customize the value of 'tooltip-delay' to zero.
New user option 'follow-mode-prefix-key'.
This user option replaces 'follow-mode-prefix', which had to be set
before loading Follow mode.  This new option allows you to change the
prefix even after it was loaded, using 'customize-option' or 'setopt'.
cdl.el is now obsolete.
Use 'shell-command' and 'shell-command-on-region' instead.
echistory.el is now obsolete.
hashcash.el is now obsolete.
It is believed to no longer be useful as a method to fight spam.
kermit.el is now obsolete.
New user option 'ns-click-through' on Nextstep (GNUstep/Mac OS).
This controls whether activation clicks are passed through to Emacs
commands.  When nil, clicking on an inactive Emacs frame will only
activate it.  When t (the default), the click will both activate the
frame and be interpreted as a command.
New user option 'global-hl-line-buffers'.
This specifies the buffers in which 'global-hl-line-mode' should be
switched on.  The default is all buffers except the minibuffer, and
buffers that enable 'cursor-face-highlight-mode', like the
"*Completions*" buffer.
New value 'window' for the user option 'global-hl-line-sticky-flag'.
Unlike the value t that highlights the line containing point, it
highlights the line with the window's point.  Also it uses the new face
'hl-line-nonselected' for highlighting the line with the window's point
in non-selected windows.
New user option 'display-fill-column-indicator-warning'.
Customize it to a non-nil value to have the fill-column indicators
change their face if the current line exceeds the 'fill-column'.  The
new face 'display-fill-column-indicator-warning-face' is used to
highlight the fill-column indicators.  By default, this is disabled.
New function 'flash-face-bell-function'.
This function flashes a face briefly.
It is intended to be used in 'ring-bell-function'.
New function 'flash-echo-area-bell-function'.
This function flashes the current frame's echo area briefly.
It is intended to be used in 'ring-bell-function'.
New user option 'flash-face-faces'.
This option tells 'flash-face-bell-function' which faces should flash.
New user option 'flash-face-attributes'.
This option tells 'flash-face-bell-function' and
'flash-echo-area-bell-function' which face attributes should be used
to flash.
'face-all-attributes' now accepts an optional argument INHERIT.
It has the same meaning as the INHERIT argument to 'face-attribute',
which already takes this argument for a single attribute.  This is
useful when you want the face attributes to be absolute and not
'unspecified'.
New user option 'ffap-prefer-remote-file'.
If non-nil, FFAP always finds remote files in buffers with remote
'default-directory'.  If nil, FFAP finds local files first for absolute
file names in those buffers.  The default is nil.
New theme 'newcomers-presets'.
This new theme configures user options and minor modes that might
interest new users, but would otherwise be too invasive to enable by
default.  It is basically a set of alternative defaults.  (An Emacs
theme is a generic collection of settings, and need not affect Emacs's
appearance.)  See the Info node "(emacs) Newcomers Theme" for more
information.

Shame this got buried all the way at the bottom. This is a subject that has been debated to death, again and again, on mailing lists and elsewhere.

Should Emacs have better defaults? Which of course is a loaded question because it does change, and it has absolutely improved its defaults. Fire up a copy of Emacs 21 and Emacs 31 without mods and daily drive them for a day and see what I mean by that.

But anyway. Yes, this is a good thing. It does not affect Emacs veterans (though I’m sure curmudgeons will find a way of claiming it does) and newbies can activate a mode that will give them slightly saner defaults out of the box.

So what does it do then? It enables a bunch of incredibly conservative features that, honestly, should be the default already. Like electric pair mode (auto-insert closing braces and quotes, that sort of thing); saving the history of completions you’ve made; display line numbers and enable flymake. And a smattering of not terribly interesting long tail of changes most people (certainly not newbies) would have much of an opinion about.

This speaks to the conservatism of the maintainers who made collectively arrived at these decisions, or perhaps… the absence of changes you can reasonably make to make a newbie’s life easier.

My view is on the latter. Emacs is already in a pretty good place, but Emacs is an advanced tool. No amount of turning stuff on or off will ever change that.

'report-emacs-bug' now checks whether the bug report is about Org.
The command 'report-emacs-bug' looks in the report text for symbols that
indicate problems in Org, and if found, will ask whether the bug report
is actually about Org (in which case users should use the Org-specific
command for reporting bugs).
The elint.el package is now obsolete.
Use the byte-compiler instead; it provides more and more useful warnings.

New Modes and Packages in Emacs 31.1

New major mode 'icalendar-mode'.
A major mode for displaying and editing iCalendar (RFC 5545) data.  This
mode handles line unfolding and fontification, including highlighting
syntax errors in invalid data.
New minor mode 'delete-trailing-whitespace-mode'.
A simple buffer-local mode that runs 'delete-trailing-whitespace'
before saving the buffer.

A minor mode that does something a lot of people end up hand-building themselves.

New major mode 'conf-npmrc-mode'.
A major mode based on 'conf-mode' for editing ".npmrc" files.

New major modes based on the tree-sitter library

New major mode 'mhtml-ts-mode'.
An optional major mode based on the tree-sitter library for editing HTML
files.  This mode handles indentation, fontification, and commenting for
embedded JavaScript and CSS.
New major mode 'go-work-ts-mode'.
A major mode based on the tree-sitter library for editing "go.work"
files.  If tree-sitter is properly set-up by the user, it can be
enabled for files named "go.work".
New package 'lua-mode'.
The 'lua-mode' package from NonGNU ELPA is now included in Emacs.
New library 'timeout'.
This library provides functions to throttle or debounce Emacs Lisp
functions.  This is useful for corralling overeager code that is slow
and blocks Emacs, or does not provide ways to limit how often it runs.
New mode 'system-taskbar-mode'.
This is a global minor mode and companion functions that integrate Emacs
with system GUI taskbars (also called docks or launchers or something
similar) to display a taskbar icon badge overlay, a progress bar report
overlay, and alerts that an Emacs session needs attention, often by
flashing or bouncing the Emacs application icon.  Supported on GNU/Linux
via D-Bus, macOS/GNUstep 10.5+ and MS-Windows 7+.

On GNU/Linux systems, shell extensions or similar helpers such as
"dash-to-dock" may be required.  See
<https://extensions.gnome.org/extension/307/dash-to-dock/> and
<https://wiki.ubuntu.com/Unity/LauncherAPI>.

Hey that’s pretty neat. I don’t use a WM where that makes a difference to me, I don’t think (I use Hyprland) but I’d love to hear from people who do use it, and if it helps.

New package 'system-sleep'.
This package provides platform-neutral interfaces to block your system
from entering idle sleep and a hook to process pre-sleep and post-wake
events.  You can use this to avoid the system entering an idle sleep
state and interrupting a long-running process due to a lack of user
activity.  The sleep event hook can, for example, close external
connections or serial ports before sleeping, and re-establish them when
the system wakes up.

Supported on GNU/Linux via D-Bus (sleep blocking and sleep events
require the org.freedesktop.login1 service, display sleep blocking
requires org.freedesktop.Screensaver service), macOS (sleep/display
blocking requires version 10.9 or later, sleep events are supported on
all versions), MS-Windows (sleep blocking is supported on all versions,
sleep events require Windows 8 or later).

Incompatible Lisp Changes in Emacs 31.1

Boundaries of 'cursor-sensor-functions' now obey stickiness.
'cursor-sensor-mode' now uses 'get-pos-property' to decide whether a
boundary is considered to be inside or outside.

This means that by default, the boundaries have changed: the end
position of a stretch of a 'cursor-sensor-functions' text property used
to be considered outside of the stretch whereas it is now considered to
be inside.  You can recover the previous behavior by controlling the
stickiness, for example with a call like:

    (add-text-properties BEG END
                         '(cursor-sensor-functions (MY-FUNCTION)
                           rear-nonsticky (cursor-sensor-functions)))
'makunbound' on a variable alias undoes the alias.
Previously, it had the effect of applying the 'makunbound' to the target
of the alias (which can fail for some builtin variables).
'FOO-ts-mode-indent-offset' renamed to 'FOO-ts-indent-offset'.
When the new TS modes were introduced, a mistake was made where those
modes used 'FOO-mode-indent-offset' instead of the conventional
'FOO-indent-offset'.  The following are the new names:
'toml-ts-indent-offset', 'mhtml-ts-js-css-indent-offset',
'html-ts-indent-offset', 'typescript-ts-indent-offset',
'php-ts-indent-offset', 'php-ts-html-indent-offset',
'json-ts-indent-offset', 'java-ts-indent-offset',
'go-ts-indent-offset', 'csharp-ts-indent-offset',
'cmake-ts-indent-offset', 'c-ts-indent-offset'.
String mutation has been restricted further.
'aset' on unibyte strings now requires the new character to be a single
byte (0-255).  On multibyte strings the new character and the character
being replaced must both be ASCII (0-127).

These rules ensure that mutation will never transform a unibyte string
to multibyte, and that the size of a string in bytes (as reported by
'string-bytes') never changes.  They also allow strings to be
represented more efficiently in the future.

Other functions that use 'aset' to modify string data, such as
'subst-char-in-string' with a non-nil INPLACE argument, will signal an
error if called with arguments that would violate these rules.
More program constants are combined by the compiler.
The compiler now unifies more constants that are 'equal' for better code
generation.  This does not affect correct programs but may expose some
coding mistakes.  For example,

    (eq (cdr '(1 2 3)) '(2 3))

may return either nil or t.
Nested backquotes are no longer supported in Pcase patterns.
The obsolete variable 'redisplay-dont-pause' has been removed.
The 'rx' category name 'chinese-two-byte' must now be spelled correctly.
An old alternative name (without the first 'e') has been removed.
'text-property-default-nonsticky' is now buffer-local.
This variable now becomes buffer-local when set.  Use 'setq-default' in
the (unlikely) case you want to change the global value.
All the digit characters now have the 'digit' category.
All the characters whose Unicode general-category is Nd now have the
'digit' category, whose mnemonic is '6'.  This includes both ASCII and
non-ASCII digit characters.
All the symbol characters now have the 'symbol' category.
All the characters that belong to the 'symbol' script (according to
'char-script-table') now have the 'symbol' category, whose mnemonic is
'5' (it looks like an 'S').
Some libraries obsolete since Emacs 24.4 and 24.5 have been removed:
cc-compat.el, info-edit.el, meese.el, otodo-mode.el, rcompile.el,
sup-mouse.el, terminal.el, vi.el, vip.el, ws-mode.el, and yow.el.
'if-let' and 'when-let' are now obsolete.
Use 'if-let*', 'when-let*' and 'and-let*' instead.

This effectively obsoletes the old '(if-let (SYMBOL SOMETHING) ...)'
single binding syntax, which we'd kept only for backwards compatibility.

Sigh.. more unnecessary obsolescence churn, all because of some chicanery in a few extreme cases where the macros didn’t do what people thought it did.

The Eshell 'pwd' command now expands the directory name on all systems.
This ensures that user directories are properly expanded to their full
name.  Previously, Eshell only did this for MS-Windows systems.  To
restore the old behavior, you can set 'eshell-pwd-convert-function' to
'identity'.
The rx 'eval' form now uses the current Lisp dialect for evaluation.
Previously, its argument was always evaluated using dynamic binding.
Unused block comment variables have been removed.
The unused variables 'block-comment-start' and 'block-comment-end',
which never actually had any effect when set by major modes, have been
removed.
'delete-frame' now needs a non-nil FORCE to delete the daemon frame.
The initial terminal frame of an Emacs process running as daemon can be
deleted via 'delete-frame' if and only if its optional FORCE argument is
non-nil.
'date-to-time' no longer accepts malformed times with time zone like "EDT".
Time strings like "2025-06-04T13:21:00 EDT" are not in valid ISO 8601
time format, and 'date-to-time' now signals an error for them.  Use a
numerical time-zone specification, like "2025-06-04T13:21:00-0400",
instead, which gives the time offset as +/-hh or +/-hh:mm.  A designator
"Z" for UTC time is also supported.  Less formal space-separated time
formats, like "2025-06-04 13:21:00 EDT", without the ISO 8601 "T"
separator, are also supported.
The obsolete variable 'load-convert-to-unibyte' has been removed.
The experimental variable 'binary-as-unsigned' has been removed.
Instead of '(let ((binary-as-unsigned t)) (format "%x" N))' you can use
'(format "%x" (logand N MASK))' where MASK is for the desired word size,
e.g., #x3fffffffffffffff for typical Emacs fixnums.
The 'exec-path' variable now uses same default PATH as other programs.
That is, if the PATH environment variable is unset or empty, 'exec-path'
now acts as if PATH is the system default, which is "/bin:/usr/bin"
on GNU/Linux systems.
New variable 'tty-cursor-movement-use-TAB-BS'.
The display optimization where the combination 'TAB' characters +
'BACKSPACE' is used to move to a position on a TTY frame is now disabled
by default and controlled by this variable; it can be set to non-nil
to keep the old behavior.  This change is to accommodate screen
readers.
'next-completion' and 'previous-completion' now use 'completions-format'.
Previously, these commands only took horizontal format into account;
now, they call either '{next,previous}-line-completion' or the new
commands '{next,previous}-column-completion', depending on the value of
'completions-format'.  The latter two commands improve and extend the
previous implementations of '{next,previous}-completion', which better
reflect that they only take the (default) horizontal completions format
into account.  Any external code using '{next,previous}-completion' that
assumes the previous implementation must be adjusted accordingly; see
'minibuffer-next-completion' for an example of such an adjustment in
Emacs core.
A thread's current buffer can now be killed.
We introduce a new attribute for threads called "buffer disposition".
See the new argument in 'make-thread' as well as the
'thread-buffer-disposition' and 'thread-set-buffer-disposition' functions.
The default value allows the thread's current buffer to be killed by another
thread.  This does not apply to the main thread's buffer.
Defining or modifying a face so that it inherits from itself signals error.
Calling any function that defines or modifies a face in a way that
causes cyclical inheritance (i.e., the face inherits from itself, either
directly or indirectly) now signals an error.  Previously, Lisp programs
could get away with this, and the problem would either be detected at
display time or even cause Emacs to hang trying to display such a face.
Affected APIs include 'defface', 'set-face-attribute', their callers,
and other similar functions.
Original behavior of 'overlays-in' and 'overlays-at' has been restored.
Before Emacs 28.1, the list of overlays returned by these two functions
included overlays outside of the current narrowing of the buffer, and
there wasn't a special exception for including empty overlays at end of
accessible portion of the buffer.  This behavior has been restored, and
the special behavior for empty overlays is again reserved only to the
actual end of buffer, disregarding narrowing.  As result,
'remove-overlays' can now again remove overlays outside of the
narrowing, as it did before Emacs 28.1.
'help-setup-xref' now re-enables the major mode of the Help buffer.
As a result, in many cases the buffer will be read-only afterwards.
This should not cause any trouble as long as the actual buffer
modification takes place inside 'with-help-window' or
'with-output-to-temp-buffer' after the call to 'help-setup-xref'.
Xref commands don't automatically suggest visiting a tags table anymore.
When no tags file is loaded, symbol completion now just won't provide
any suggestions.  Thus, so the 'M-?' command now works without a tags
table.  And the 'M-.' command will show a message describing the several
built-in options that will provide an Xref backend when used.
Calling 'debug' in batch sessions no longer kills Emacs.
If you want Emacs to exit, your program will now have to call
'kill-emacs' explicitly.

Lisp Changes in Emacs 31.1

The API to manipulate error descriptors has been improved.
There are new functions: 'error-type-p', 'error-type',
'error-has-type-p', and 'error-slot-value'.  You can now say '(signal
err)' instead of '(signal (car err) (cdr err))', which is not only more
concise, but also preserves the equality (under 'eq') of the error
descriptor.
'secure-hash' now supports generating SHA-3 message digests.
The list returned by 'secure-hash-algorithms' now contains the symbols
'sha3-224', 'sha3-256', 'sha3-384', and 'sha3-512'.  These symbols can
be used as the ALGORITHM argument of 'secure-hash' to generate SHA-3
hashes.
New function 'garbage-collect-heapsize'.
Same as 'garbage-collect' but just returns the info from the last GC
without performing a collection.
Improve 'replace-region-contents' to accept more forms of sources.
It has been moved from subr-x.el to editfns.c.  You can now directly
pass it a string or a buffer rather than a function.  Passing a function
is now deprecated.
New function 'char-displayable-on-frame-p'.
'char-displayable-on-frame-p' returns non-nil if Emacs ought to be able
to display its char argument on a given frame.  This new function,
unlike 'char-displayable-p', does not check whether the character can be
encoded by the underlying terminal.
New function 'frame-initial-p'.
This predicate returns non-nil if a given frame or terminal is or holds,
respectively, the initial text frame that is used internally during
daemon mode, batch mode, and the early stages of startup.  Interactive
and graphical programs, for instance, can use this predicate to avoid
operating on the initial frame, which is never displayed.
New macros 'static-when' and 'static-unless'.
Like 'static-if', these macros evaluate their condition at
macro-expansion time and are useful for writing code that can work
across different Emacs versions.
New feature to speed up repeated lookup of Lisp files in 'load-path'.
If the new variable 'load-path-filter-function' is set to the new
function 'load-path-filter-cache-directory-files', calling 'load' will
cache the directories it scans and their files, and the following
lookups should be faster.
'let-alist' supports indexing into lists.
The macro 'let-alist' now interprets symbols containing numbers as list
indices.  For example, '.key.0' looks up 'key' in the alist and then
returns its first element.

Lexical binding

You can change the default value of 'lexical-binding'.
While the default is still the use of dynamic binding dialect of Emacs
Lisp in those places that don't explicitly set 'lexical-binding', you
can change it globally with:

    (set-default-toplevel-value 'lexical-binding t)

But don’t do that. Lexical binding is great; it’s the future; we have dynamic binding forever for special forms like defvar so most things just work as they should. But don’t go enabling this. Let package authors update their code in their own time.

Loading a file displays a warning if there is no 'lexical-binding' cookie.
Files loaded from '-x' and '--script' now use lexical binding.
If you don't have time to adapt your script's code to the lexical
binding dialect (see "(elisp) Converting to Lexical Binding"), you can
wrap your code in:

    #!/usr/bin/env -S emacs --batch --script
    (eval
     '(progn
        YOUR CODE HERE))

Huh I thought they already used lexical binding.

New function 'set-local'.
This is the buffer-local equivalent of the function 'set'.
New macro 'setopt-local'.
This is the buffer-local version of 'setopt' for user options rather
than plain variables, and uses 'custom-set'/'set-local' to set variable
values.  A new argument, BUFFER-LOCAL, is passed to 'custom-set'
functions to indicate the buffer-local context.
New macros 'incf' and 'decf'.
They increment or decrement the value stored in a variable (a symbol),
or in a generalized variable.
New functions 'plusp' and 'minusp'.
They return non-nil if a number is positive or negative, respectively,
and signal an error if they are given a non-number.
New functions 'oddp' and 'evenp'.
They return non-nil if an integer is odd or even, respectively, and
signal an error if they are given a non-integer.
New functions 'drop-while' and 'take-while'.
These work like 'drop' and 'take' but use a predicate instead of
counting.
New function 'all' and function alias 'any'.
These return non-nil for lists where all and any elements, respectively,
satisfy a given predicate.
'equal' now compares circular lists without signalling an error.
Comparing very deeply nested objects will still fail, but 'equal'
will no longer signal the 'circular-list' error.
The 'defvar-local' macro's second argument is now optional.
This means that you can now call it with just one argument, like
'defvar', to declare a variable both special and buffer-local.
The rx atom 'any' is obsolete and usage emits warnings.
When used as an atom, 'any' is an old alias for 'not-newline' but is
often mistakenly used where 'anychar' was intended.  Note that the
construct '(any ...)' is unrelated and not obsolete.

ERT

ERT is Emacs’s elisp test framework.

Some experimental ERT macros are now considered stable.
The following macros, previously only available in the experimental
'ert-x' module, are now considered stable and have been moved to 'ert':

- 'ert-with-test-buffer'
- 'ert-with-buffer-selected'
- 'ert-with-buffer-renamed'

See "(ert) Helper Functions" node in the ERT manual for more information.
New function 'ert-play-keys'.
Previously, 'ert-simulate-keys' could be used for sending keys to input
functions such as 'read-from-minibuffer', but not for other interactive
input such as starting key-mapped commands.
Show executed tests from erts files via the ERT results buffer.
For tests that call 'ert-test-erts-file', the ERT results buffer now
allows you to list the tests defined in the referenced erts files that
have been executed by the test at point.  See Info node "(ert) Running
Tests Interactively" for more information.

Time & Date

'seconds-to-string' supports new formatting options.
Optional arguments are provided to produce human-readable time-duration
strings in a variety of formats, for example "6 months 3 weeks" or "5m
52.5s".
New function 'hash-table-contains-p'.
This function returns non-nil if a given key is present in a hash table.
The function 'purecopy' is now an obsolete alias for 'identity'.
New function 'native-compile-directory'.
This function natively compiles all Lisp files in a directory and in its
sub-directories, recursively, excluding those already natively compiled.
New function 'color-blend'.
This function takes two RGB lists and optional ALPHA and returns an RGB
list whose elements are blended in linear space proportional to ALPHA.
New function 'dom-inner-text'.
This function gets all the text within a DOM node recursively, returning
it as a concatenated string.  It replaces the now-obsolete functions
'dom-text' and 'dom-texts'.
The obsolete face attribute ':reverse-video' has been removed.
Use ':inverse-video' instead.

D-Bus

D-Bus is a generic mechanism for inter-application and app-OS communication on Linux.

Support interactive D-Bus authorization.
A new ':authorizable t' parameter has been added to 'dbus-call-method'
and 'dbus-call-method-asynchronously' to allow the user to interactively
authorize the invoked D-Bus method (for example via polkit).
Support D-Bus file descriptor manipulation.
A new ':keep-fd' parameter has been added to 'dbus-call-method' and
'dbus-call-method-asynchronously' to instruct D-Bus to keep a file
descriptor, which has been returned by a method call, internally.  The
functions 'dbus--fd-open', 'dbus--fd-close' and 'dbus--registered-fds'
implement managing these file descriptors.  See the Info node "(dbus)
File Descriptors" for details.
The customization group 'wp' has been removed.
It has been obsolete since Emacs 26.1.  Use the group 'text' instead.
New optional BUFFER argument for 'string-pixel-width'.
If supplied, 'string-pixel-width' will use any face remappings from
BUFFER when computing the string's width.
New function 'truncate-string-pixelwise'.
This function truncates a string to the specified maximum number of
pixels rather than by characters, as in 'truncate-string-to-width', and
respects face remappings if BUFFER is specified.  You can also specify
an optional ellipsis string to append, similar to
'truncate-string-to-width'.
New macro 'with-work-buffer'.
This macro is similar to the already existing macro 'with-temp-buffer',
except that it does not allocate a new temporary buffer on each call,
but tries to reuse those previously allocated (up to a number defined by
the new variable 'work-buffer-limit', which defaults to 10).
'date-to-time' now defaults to local time.
The function now assumes local time instead of Universal Time when
its argument lacks explicit time zone information.  This has been the
de-facto behavior since Emacs 24 although documentation said otherwise.
Also, the fallback on 'timezone-make-date-arpa-standard' has been
removed because its supported date styles can be handled by
'parse-time-string'.  To restore the previously documented behavior,
specify "+0000" or "Z" as the time zone in the argument.
The 'min-width' property is now supported for overlays as well.
This 'display' property was previously supported only as text property.
Now overlays can also have this property, with the same effect for the
text covered by the overlay.
New function 'remove-display-text-property'.
This function removes a display property from the specified region of
text, preserving any other display properties already set for that
region.
New macro 'cond*'.
The new macro 'cond*' is an alternative to 'cond' and 'pcase'.
Like them, it can be used to define several clauses, each one with its
own condition; the first clause that matches will cause its body to be
evaluated.
'cond*' can use Pcase's pattern matching syntax and also provides
another pattern matching syntax that is different from that of 'pcase',
which some users might find less cryptic.
See the Info node "(elisp) cond* Macro" for details.

I’m not going to get into if we need or want cond*, but I do feel we need a better cond — but cond* is just a far more complex, in my opinion, way of doing something pcase can already do well.

New function 'shell-command-do-open'.
This lets a Lisp program access the core functionality of the
'dired-do-open' command.  It opens a file or files using an external
program, choosing the program according to the operating system's
conventions.
'make-vtable' can create an empty vtable.
It is now possible to create a vtable without data, by leaving the
':objects' list empty, or by providing an ':objects-function' that
(initially) produces no data.  In such a case, it is necessary to
provide a ':columns' spec, so that the number of columns and their
widths can be determined.  Column widths can be set explicitly, or they
will be calculated based on the window width.
New symbol property 'repeat-continue' for 'repeat-mode'.
A command with the 'repeat-continue' symbol property, which can be a
list of keymaps or t, will continue an already active repeating sequence
for a keymap in that list (resp. all keymaps).  The new property does
not affect whether the command starts a repeating sequence, which
remains governed by the 'repeat-map' property.  'defvar-keymap' supports
a new keyword ':continue', a list of commands, and adds the keymap to
the 'repeat-continue' property of each command in that list.  The
'use-package' and 'bind-keys' macros support a similar keyword
':continue-only'.
New function 'completion-table-with-metadata'.
It offers a more concise way to create a completion table with metadata.
'all-completions' and 'unintern' no longer support old calling conventions.
New symbol property 'find-function-type-alist'.
Used by 'find-function' and similar commands.  Macros that define an
object in a way that makes it hard to associate the object's name with
the macro call site defining the object can add an entry to the property
'find-function-type-alist' on the object's name to provide information
for finding the definition.

The new convenience function 'find-function-update-type-alist' offers a
concise way to update a symbol's 'find-function-type-alist' property.
New function variable 'comment-setup-function' for multi-language modes.
It can set comment-related variables such as 'comment-start' depending
on the language under point.

Required for any language that embed other languages. So mostly of use for tree-sitter major modes or stuff like the web-mode package.

'inhibit-message' can now inhibit clearing of the echo area.
Binding 'inhibit-message' to a non-nil value will now suppress both
the display of messages and the clearing of the echo area, such as
caused by calling 'message' with a nil argument.
'minibuffer-message' no longer blocks while displaying message.
'minibuffer-message' now uses a timer to clear the message printed to
the minibuffer, instead of waiting with 'sit-for' and then clearing it.
This makes 'minibuffer-message' usable in Lisp programs which want to
print a message and then continue to perform work.

Special Events

New primitive 'insert-special-event'.
This function inserts the special EVENT into the input event queue.
New event type 'sleep-event'.
This event is sent when the device running Emacs enters or leaves the
sleep state.
Function aliases obsolete since Emacs 23.2 have been removed:
'advertised-undo', 'advertised-widget-backward', and
'dired-advertised-find-file'.
New functions to get and set top-level buffer-local values.
'buffer-local-toplevel-value' and 'set-buffer-local-toplevel-value' get
and set the top-level buffer-local value of a variable.  A top-level
value is the one that variable has outside of any let-bindings.
New function 'exec-suffixes'.
This function by default returns the value of the corresponding user
option, but can optionally return the equivalent of 'exec-suffixes' from
a remote host.  It must be used in conjunction with the function
'exec-path'.
'read-directory-name' now accepts an optional PREDICATE argument.
JSON parse error line and column are now obsolete.
The column number is no longer available; the line number will be
removed in the next release of Emacs.
'defvar-keymap' can now take a ':prefix t' option.
This is an abbreviation for using the name of the keymap as the prefix
command name.  E.g., '(defvar-keymap foo-map :prefix t)' is equivalent
to '(defvar-keymap foo-map :prefix 'foo-map)'.
New 'R' code letter for 'interactive' forms.
This specifies the beginning and end of an active region, and nil twice
if the region is inactive.  The interactive specification

    (interactive "R")

is equivalent to

    (interactive (list (use-region-beginning) (use-region-end)))

Toolkit

The Emacs PGTK toolkit respects dark and light modes.
Emacs when built with the pure GTK toolkit now respects desktop dark and
light modes for drawing the GTK toolbar and widgets, automatically
toggling between them.

That’s lovely for people who love toggling between it and want their OS to govern this.

'toolkit-theme-set-functions' called when the toolkit theme is set for Emacs.
When the theme is set on PGTK, Android, or MS-Windows systems,
'toolkit-theme-set-functions' is called.  The result is stored in the
variable 'toolkit-theme' as either symbol 'dark' or 'light', but may be
extended to encompass other toolkit-specific symbols in the future.
Progress reporter context.
'make-progress-reporter' now accepts the optional argument CONTEXT,
which if it is the symbol 'async', inhibits updates in the echo area
when it is busy.  This is useful, for example, if you want to monitor
progress of an inherently asynchronous command such as 'compile'.
Binary format specifications '%b' and '%B' added.
These produce the binary representation of a number.  '%#b' and '%#B'
prefix the bits with '0b' and '0B', respectively.
'pp-eval-expression' can now insert results into the current buffer.
With a prefix argument, 'pp-eval-expression' inserts the result into the
current buffer, just like 'eval-expression' already did.
New function 'multiple-command-partition-arguments'.
This function partitions a list of command arguments that might be
arbitrarily long.  It can be used in cases in which it is known to be
safe to run the command multiple times on subsequent partitions of the
list of arguments.  The variable 'command-line-max-length' controls the
partitioning.
New function 'ensure-proper-list'.
This function is a variation on 'ensure-list' that checks if an object
is a proper list, in which case the list will be returned as is,
otherwise the function will return the object wrapped in a singleton
list.
In batch mode, 'C-c' (i.e. SIGINT) can either 'quit' or kill Emacs.
By default it kills Emacs, as before, but 'kill-emacs-on-sigint'
can be set to nil to change that.
The response to SIGINT in interactive sessions is unaffected,
e.g., in a normal GUI session it still kills Emacs whereas in a terminal
it causes 'quit' since it is used for 'C-g'.
New ':interactive-only' way to add an advice.
While it is marginally more efficient than ':after' or ':before',
the main purpose is to make the intention more obvious when the advice
modifies only the interactive form and not the actual behavior
of the function.

Changes in Emacs 31.1 on Non-Free Operating Systems

Support macOS Accessibility Zoom focus tracking.
This is an important change for visually-impaired users.  If macOS
Accessibility Zoom is enabled via (System Settings, Accessibility,
Zoom...) with keyboard focus tracking (Advanced...), Zoom is informed
of updated cursor positions during each redisplay cycle.
New macOS function 'ns-process-is-accessibility-trusted'.
This function returns t if the macOS Accessibility Framework trusts
Emacs.  This is a necessary condition for Accessibility Zoom and other
accessibility features.  Enable Emacs via (System Settings, Privacy &
Security, Accessibility...) and add the Emacs.app installed directory to
the enabled application list.
Process execution has been optimized on Android.
The run-time performance of subprocesses on recent Android releases,
where a userspace executable loader is required, has been optimized on
systems featuring Linux 3.5.0 and above.
It is now possible to read GUI events from non-main Lisp threads on Android.
Put differently, this enables input events to be read and recursive
editing sessions to be started from non-main threads.  The only platform
where this remains unsupported is Nextstep (GNUstep or macOS).
'desktop-restore-frames' has been disabled by default on Android systems.
Restrictions imposed on clients by the window manager on these systems
are too prohibitive and don't allow restoring frame configurations.
(For the same reason many window management facilities are also not
implemented by Emacs.)
Emacs responds to runtime display configuration changes on Android.
The upshot of this is that Emacs will adapt to display resolution /
layout changes applied while an Emacs session is active, which is
possible on some recently released devices.
'NSSpeechRecognitionUsageDescription' now included in "Info.plist" (macOS).
Should Emacs (or any built-in shell) invoke a process using macOS speech
recognition APIs, the relevant permission dialog is now displayed, thus
allowing Emacs users access to speech recognition utilities.

Note: Accepting this permission allows the use of system APIs, which may
send user data to Apple's speech recognition servers.
Re-introduced dictation, lost in Emacs 30 (macOS).
We lost macOS dictation in Emacs 30 when migrating to NSTextInputClient.
We have now implemented 'selectedRange' in 'nsterm.m' to enable it in
the new subsystem.  You may notice a slight change in dictation UI
provided by macOS.
On Mac OS X, stipples now render with color.
Emacs on MS-Windows now supports GUI dialogs and message boxes better.
In particular, it is now possible to show text with embedded newlines in
a dialog popped by 'message-box'.  This is supported on Windows Vista
and later versions.
Emacs on MS-Windows now supports drag-n-drop of text into a buffer.
This is in addition to drag-n-drop of files, which was already
supported.  As on X, the user options 'dnd-scroll-margin' and
'dnd-indicate-insertion-point' can be used to customize the process.
Emacs on MS-Windows now supports color fonts.
On Windows 8.1 and later versions, Emacs now uses DirectWrite to draw
text, which supports color fonts.  This can be disabled by setting the
variable 'w32-inhibit-dwrite' to t.  Also see 'w32-dwrite-available' and
'w32-dwrite-reinit' to check availability and to configure the
DirectWrite rendering parameters.

To show color Emojis in Emacs, customize the default fontset to use a
color Emoji font installed on your system for the 'emoji' script.
Emacs on MS-Windows now supports 'yank-media'.
This command inserts clipboard data of different formats into the
current buffer, if the major mode supports it.
Emacs on MS-Windows now supports up to 1024 sub-processes.
Changes in implementation of monitoring sub-processes allow Emacs on
MS-Windows to start up to 1024 sub-processes, similar to GNU/Linux and
other free systems.
Images on MS-Windows now support the ':transform-smoothing' flag.
Transformed images are smoothed using the bilinear interpolation by
means of the GDI+ library.
Emacs on MS-Windows is now capable of exporting frame screenshots to files.
The new primitive 'w32-export-frame' can be used to export a screenshot
of a specified frame to an image file in one of the supported image
formats, such as JPEG or PNG.
Emacs on MS-Windows now supports the ':data' keyword for 'play-sound'.
In addition to ':file FILE' for playing a sound from a file, ':data
DATA' can now be used to play a sound from memory.
New primitive 'w32-sound-volume'.
This primitive allows getting and setting the volume of the system's
default audio device (or the "optimal device", if there are several
devices).
The MS-DOS port of Emacs now supports more recent releases of GCC and Binutils.
Accordingly, we have revised our recommendations for a suitable DJGPP
toolchain to GCC 14.2.0 and Binutils 2.35.1 in lieu of GCC 3.4.x and
Binutils 2.26.
Windows Terminal can now display 256 and 24-bit RGB color.
Previously, terminal sessions on Windows supported display of 16 colors.
There is now support for 8-bit (256 color) and 24-bit RGB (true color).
The new mechanism will be enabled automatically when supported.
It defaults to 24-bit RGB color, but can be set to 8, 16, '8bit', or
'24bit' by passing the '--color' flag or setting the 'tty-color-mode'
frame parameter.  Use of the new mechanism is controlled by an internal
variable that can be set and inspected via the functions
'w32-use-virtual-terminal' and 'w32-use-virtual-terminal-p'
(respectively).  See the manual entry "(emacs) Windows Misc" for more
details.
-1:-- What's New in Emacs 31.1? (Post Mickey Petersen)--L0--C0--2026-08-24T11:25:31.000Z

Irreal: Setq And The Others

James Cherti has an interesting and useful post about the relative efficiency of setq, setq-default, setopt, and customize-set-variable. Cherti justifies this concern on their effect on Emacs startup time but according to well established Irreal dicta this doesn’t matter because we seldom restart Emacs so a few seconds more or less simply don’t matter.

Oddly, there are some people who pay no attention to Irreal dicta and who obsess about every millisecond—Cherti’s term—of Emacs startup time. For those people and those who worry about execution times in general, Cherti’s post is very useful and explains why some of those commands are faster than others.

The crux of the matter is that defcustom can include a setter method that can be arbitrarily complex. Cherti has a nice example of this that will help you understand what’s going on. Cherti says that very often there’s no reason to invoke that setter method during startup and that setq or setq-default is faster and just as useful. Commands like setopt and customize-set-variable always call the setter method so they may be slower. Cherti’s examples show how to avoid unnecessary bottlenecks.

Cherti also shows how even the :custom keyword in use-package can trigger the setter methods of those variables and gives some workarounds. The main problem is that it’s hard to know when you can avoid the setter method and when you can’t. Sometimes, you just have to read the source code to figure it out.

Obsessing about startup times aside, it’s nice to have an explanation of what these commands do and how their use might affect run time. Cherti’s post is relatively short and well worth a minute or two to read.

-1:-- Setq And The Others (Post Irreal)--L0--C0--2026-08-23T15:15:54.000Z

Sacha Chua: Emacs Chat 29: Abdallah Maouche (en français)

Nous allons parler en direct avec Abdallah Maouche (heiwiper) (heiwiper/emacs.d, 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. =)

J'ai essayé de corriger la transcription, mais je crois qu'il y a encore beaucoup d'erreurs, donc n'hésitez pas à m'envoyer un message.

La transcription un peu corrigée

Details

0:01 Introduction

Sacha: Ok, vous êtes en direct.

Prot: On a le nouveau lien ou c'est le même ?

Sacha: C'est le même lien que Google Meet mais je vais copier le nouveau lien. C'est YouTube.com, sachactube... C'est difficile. Oui.

Prot: Oui.

Sacha: Je ne peux pas poster le chat. Donc, je vais poster sur les conférences. Comment dire? Ah! Ajoutez un commentaire. Et je vais aussi ouvrir. Oui. Donc, le chat est ici. Je pense.

Prot: Ah, take two. J'ai trouvé. Oui, oui.

Abdallah: Oui, j'ai trouvé moi aussi.

Sacha: Ça marche? Merci beaucoup pour votre patience. L'introduction ! Bonjour à toutes et à tous et bienvenue dans le troisième épisode d'Emacs Chat en français et le vingt-neuvième épisode de la série. Notre invité aujourd'hui est Abdallah Maouche, qui s'est fait aussi appeler par son pseudo heiwiper. Il vit en Algérie et le français n'est pas sa langue maternelle, mais il s'y est assez courageusement avec nous. Si on se retrouve totalement bloqué, comme moi, justement, pas de souci, nous pouvons passer au franglais ou à l'anglais. Ce n'est pas grave. Bonjour Abdallah et encore merci d'être là.

Abdallah: Merci, merci beaucoup. Merci pour l'invitation. Ça me fait un grand plaisir de parler à vous deux, car je vous suis sur votre blog et sur la chaîne YouTube. Donc, j'ai très hâte de converser avec vous.

Sacha: Et merci aussi à Prot de se rejoindre à nous pour cette expérience. Nous faisons de notre mieux ensemble. Merci beaucoup.

Prot: Je t'en prie, je t'en prie. C'est parti. Oui, c'est magnifique, c'est magnifique.

3:13 Apprentissage

Sacha: Abdallah, contrairement aux invités précédents, tu utilises Emacs depuis seulement sept ans. Je plaisante. C'est déjà pas mal, c'est génial. Peux-tu me raconter ton parcours d'apprentissage d'Emacs moderne? Comment est-ce que tu l'as découvert?

Abdallah: Donc, ça a commencé environ 2019-2020. À ce moment, j'ai découvert Linux pour la première fois et j'ai commencé à explorer les applications open source. C'était quelque chose de nouveau pour moi. Donc, à un certain niveau, j'avais besoin d'un texte éditeur parce qu'en université, on avait utilisé des environnements de développement spécialisés pour des langages spécifiques. Par exemple, il y avait Eclipse pour Java et je pense CodeBlock, c'était pour C, C++. Et puis au master, il y avait tellement de langage que c'était difficile de garder le même flux de travail. Donc, je me suis rendu au répertoire des applications de distribution Linux que j'utilisais à l'époque. Et j'ai parcouru la liste et j'ai trouvé plusieurs éditeurs et parmi ces éditeurs, bien sûr Emacs. Je l'avais installé, j'ai installé tous les éditeurs en fait. Mais le problème c'est quand je l'ai testé, ça n'a pas vraiment, ça m'a pas plu. C'était un peu, l'interface graphique était un peu, je ne sais pas comment dire, un peu moche. Oui donc, et aussi il y avait le logo Emacs avec le GNU et le E ça ressemblait à le symbole Epsilon. Donc ça me rappelait un peu des maths et je voyais les keybindings avec les moins et tout. Je me suis dit c'est un éditeur pour les mathématiciens. Donc j'ai installé Atom et j'ai utilisé Atom. Après quelques mois, je pense, j'ai vu une vidéo à propos de Spacemacs. Parce que j'apprenais Game Engine Godot. Donc j'ai vu dans une chaîne Spacemacs et c'était très intéressant. Donc j'ai installé et j'ai appris les keybindings de Vim parce que c'est plus recommandé dans Spacemacs. Et je l'ai utilisé pour une période et puis je pense que j'ai J'ai changé la configuration pour utiliser les keybindings Emacs après parce que l'intégration de Evil, l'intégration des Vim keybindings n'était pas parfaite. Il y avait des trucs qui manquent et c'était un peu frustrant. Donc, j'ai dû switcher à Emacs Keybindings. Et je pense, après un certain temps, j'ai eu mal à configurer Spacemacs. La communauté était un peu trop petite. Oui, quand je rencontre des problèmes et je vais dans le Reddit d'Emacs, je ne sais pas comment appliquer les solutions à Spacemacs. Donc, je me suis dit, je vais devoir changer et utiliser Emacs, ce qu'on appelle Vanilla Emacs. Je pense que j'ai vu les vidéos de David Wilson. Est-ce que vous le connaissez?

Sacha: Oui, bien sûr.

Abdallah: Oui, System Crafters. C'est sa chaîne YouTube. Et j'ai suivi son tutoriel et depuis ce moment, je configure mon Emacs from scratch depuis le début. Et maintenant, j'ai une configuration, celle que je vous ai envoyée. Je l'ai publiée sur mon GitHub. Et je l'utilise quotidiennement pour les trucs personnels et au travail aussi.

Sacha: Les kits de débutants Spacemacs réduisent les peurs pour toi. Et après que tu [t'es] habitué à Emacs, tu repasses vers Emacs.

Abdallah: Oui, exactement. Je pense que le point important à l'époque, c'était l'interface graphique. Spacemacs avait une interface déjà configurée et c'était un peu attractif quand on voit la config Spacemacs. C'est pour ça que j'ai utilisé Atom, parce que c'était un peu aussi... Il avait une interface qui m'a plu.

Prot: Je crois que c'est aussi plus facile de découvrir les fonctionnalités avec quelque chose comme Spacemacs. Oui, je pense... Oui, c'est difficile.

Abdallah: Oui, parce qu'il y a des... La configuration de Spacemacs, c'est comme si... La configuration de Emacs avec beaucoup d'abstractions. C'est comme si on ajoute des couches de programmation, couche de langage, je ne sais pas, Java ou bien quelque chose comme ça. Donc, on va ignorer le fait qu'il y a des serveurs LSP et tout. En fait, il faut juste spécifier quel serveur tu veux utiliser et Spacemacs va configurer tout. Donc, c'est une abstraction de la configuration Emacs. Mais ça reste en Emacs Lisp.

Sacha: Est-ce que c'est difficile de traduire ta configuration de Spacemacs à ta propre configuration?

Abdallah: En fait, je ne connaissais pas assez Emacs Lisp, donc je n'ai pas essayé, mais ce n'était pas évident parce que Spacemacs, je pense que c'est C'est très différent de la configuration Emacs. Je pense qu'il y a beaucoup d'abstractions. Ce n'est pas évident de transférer la configuration et la traduire.

Prot: Parce que si Spacemacs utilise [les macros Emacs Lisp], tu dois connaître les macros. Autrement, tu ne peux pas transférer le même code.

Abdallah: Oui, évidemment, vous avez raison. Oui, les macros, c'est un peu sensible.

Sacha: J'aime bien ta configuration. Ta façon d'utiliser des variables pour simplifier ta configuration d'org-capture est une bonne idée. Peux-tu rendre cette section de ta configuration pour nous la montrer?

Abdallah: Oui, bien sûr. Je vais juste partager mon écran.

Sacha: J'avais piqué l'idée. C'est plus simple que mienne.

Prot: Voler à volonté.

Abdallah: Oui, bien sûr. C'est disponible dans GitHub, donc je pense que c'est... Ok, donc là, j'ai essayé de créer une configuration spécifique pour cette démo, juste pour montrer les différents trucs d'Org Mode et tout. Donc, on va aller vers la configuration des Org Capture, c'est ça ?

Sacha: Oui, org-capture-templates. Oui, oui. Oui, oui, oui, j'adore. J'adore l'utilisation de variables pour simplifier la configuration.

Abdallah: Oui, ce qui me plaît dans cette configuration, c'est que la partie où je définis le template, On dirait que c'est la note parce qu'il y a le titre et puis il y a des sauts de ligne et la concaténation ici, elle fait comme si c'est une note réelle. Et puis, la partie ici, ce n'est pas assez chargée parce que là, je définis juste la variable. Donc, je peux voir toutes mes captures dans une seule page. Je n'ai pas besoin de... Faire un scroll tout au long du fichier pour voir toutes les captures que j'ai définies.

Sacha: Ta variable heiwiper/org-jira-task est définie dans un autre fichier, je pense?

Abdallah: Non, j'ai transféré toutes les parties intéressantes dans un seul fichier juste pour la démonstration. Donc, vous pouvez l'avoir ici. Donc, cette template, elle me permet de créer une tâche et aussi associer le lien Jira pour cette tâche parce qu'on utilise Jira au travail. Donc, je vais vous montrer un exemple. Par exemple, ici, je vais... Bon, j'ai exécuté l'org-capture. Vous voyez ici, il y a plusieurs captures, ce qu'on a défini ici. Et là, il y a Jira Task. Donc, je vais cliquer le J. Et bon, vous pouvez ignorer ceci. On va voir le prompt. Donc ici, ce que je fais, c'est que je prends la tâche Jira que j'ai sur email ou bien sur le navigateur. Je copie le lien. Par exemple, on va faire un exemple. https://jira.com/... Et là, d'habitude, les tâches Jira, c'est quelque chose comme ça. On va dire BG comme si c'était un bug ou... et une référence. On va dire 1, 2, 3, 4. Et puis, je vais cliquer entrer. On suppose que celui-ci est le lien de la tâche. Et là, je vais copier le titre de la tâche. Là, je vais juste écrire quelque chose. Et puis là, j'ai quelques tags. Ça va me servir plus tard pour organiser les tâches. Donc j'ai Bugfix, Feature, Refactor, Enhancement. Je vais sélectionner Bugfix. Et là, vous pouvez voir la tâche finale. Il y a le lien de la tâche. On peut cliquer sur C-c C-o pour accéder directement à la tâche. Il y a aussi la description. Et ce que j'aime dans ce workflow, c'est que je peux écrire Des choses ici à propos de cette tâche. Par exemple, des fois, j'écris des commandes de shell pour exécuter un appel d'API ou quelque chose comme ça. Je vais écrire tout ici. Par exemple, curl. Je ne sais pas. Donc, c'est ça. Et puis, on clique sur C-c C-c pour sauvegarder. Et à ce point, j'ai sauvegardé la tâche. Je vais vous montrer l'agenda que j'ai spécifique pour les tâches. Vous pouvez voir ici Tasks by Type et Tasks by Service. On va dire Tasks by Type. Voici la tâche que j'ai définie. C'est bien catégorisé sous la section Bug fix. Et vous pouvez aussi l'avoir ici parce que là j'aime déplacer les tâches vers des fichiers spécifiques donc parfois je déplace les tâches ici pour avoir des fichiers un peu organisés.

Sacha: Tu as un fichier très organisé, je pense, parce que tu as aussi des comptes de tâches dans ta mode-line.

Abdallah: Ah oui. Oui, en fait, à propos de ce compteur, c'est quelque chose que je suis en train d'expérimenter avec. Donc, j'utilise une méthode para, je ne sais pas si vous avez entendu parler, pour organiser un peu mes notes et mes tâches. Et j'aime savoir combien de notes j'ai sur le fichier inbox. Parce que la plupart du temps, les tâches, je les déplace vers des autres fichiers parce que j'utilise l'application Orgzly. Donc, les tâches ne restent pas généralement dans ce fichier inbox. C'est plutôt les notes. Bon, je peux vous montrer le fichier ici, c'est juste un exemple. Mais c'est plutôt comme ça. Il y a des notes et des tâches. Voilà. Plus la tâche qu'on a créée maintenant. Et pour être honnête avec vous, j'ai jamais atteint le nombre zéro. D'habitude c'est 10, 20, mais ça n'augmente pas trop, parce que, généralement, c'est juste des notes. Ils sont disponibles au cas où je veux chercher les notes. Je me sens pas la pression de les déplacer. C'est just quand j'ai assez du temps J'ai le compteur devant moi et c'est bon, je vais devoir déplacer quelques notes puisque j'ai un peu de temps.

Sacha: De mon côté, je pense que ma boîte de réception a grandi constamment, sans cesse. Est-ce que tu as déjà ton bullet journal?

Abdallah: Oui, je l'ai utilisé il y a deux ans. J'ai aussi écrit un blog post à ce propos. Pour être honnête, c'était une expérience, mais je pense que ce n'est pas pour moi parce que j'ai arrêté après un an car je pense que j'ai besoin des notifications. Ce que je faisais, en fait, c'est que je pouvais garder la routine de voir mon journal chaque jour. Mais le problème, c'est que parfois, j'ai besoin de me rappeler que je dois faire quelque chose, par exemple, 12h30. Et je ne peux pas garder les tâches sur ma tête. Donc ce que je faisais, c'est chaque matin, je vois les tâches sur le bullet journal et je le déplace. Je les écris dans mon téléphone, in to do list. Mais c'est un peu pourquoi ne pas utiliser le téléphone.

Prot: C'est double de travail.

Abdallah: Oui, double de travail. J'ai essayé de l'intégrer avec Emacs en utilisant Org Journal, mais Le problème, c'est que j'utilise pas beaucoup l'ordinateur, donc c'est plutôt le téléphone. Donc la méthode Bullet Journal, c'était pas pour moi. Donc j'utilise maintenant Org Mode avec Orgzly et ça marche très bien.

Sacha: Est-ce que tu as aussi de l'agenda d'Orgzly Revived ou juste pour saisir les tâches?

Abdallah: J'ai pas bien compris.

Sacha: Orgzly a aussi des agendas. Maintenant, ton agenda sur ton ordinateur est bien organisé. Il a beaucoup de sections. Mais sur ton téléphone, tu utilises aussi Orgzly pour voir ton agenda?

Abdallah: En fait, mon agenda personnel est celui par défaut du Org Mode parce que ma vie personnelle est assez simple. Donc j'utilise l'agenda par défaut. L'agenda que je vous ai montré, c'est pour le travail seulement. Et pour le travail, je ne garde pas les notes dans Oxley, c'est juste dans l'ordinateur de travail. Donc dans mon cas, je pense que Orgzly, il fait le travail. Donc j'utilise le même agenda, c'est pour ça.

Prot: Et c'est plus facile comme ça, d'avoir l'agenda seulement à l'ordinateur. L'agenda plus organisé.

Abdallah: Oui, l'agenda de travail, c'est plus efficace.

Sacha: L'écran est plus grand.

Abdallah: Oui, et aussi pour les tâches en relation avec le travail, je n'ai pas besoin de mon téléphone pour les accéder. Quand je suis au travail, je vais voir sur mon PC, sinon ce n'est pas la peine.

Sacha: Utiliser Emacs pour d'autres intérêts comme l'apprentissage d'une langue étrangère ou...

Abdallah: En fait, j'ai passé par la phase de ce qu'on appelle Rabbit Hole, Emacs. Donc, je peux vous dire que j'utilisais Emacs pour plein de choses. J'ai même utilisé EXWM, le Window Manager. Oui. J'ai aussi utilisé l'autre, je ne me rappelle pas comment il s'appelait. C'était un Window Manager en Common Lisp, pas en Emacs Lisp. Je ne me rappelle pas de son nom, mais il est assez connu dans la communauté Lisp. Donc oui, j'utilisais plein d'autres trucs, mais pas pour l'apprentissage de langue. Mais avec le temps, j'essaie d'être plus pragmatique. Donc j'utilise les outils adaptés quand je trouve qu'il y a des difficultés ou bien il y a des problèmes. Par exemple, je vais vous donner un exemple réel. Je voulais utiliser Emacs pour envoyer des requêtes quand je fais du programmation pour tester des API et tout. Parce que c'est très efficace pour mon workflow. Je vais vous dire un exemple plus tard. Mais parfois, je rencontre des problèmes où il y a certains types de requêtes où ils ne sont pas supportés par le package qu'offre Emacs. Donc je me trouve avoir switché à un logiciel qui est adapté pour ce truc. Et donc dans ce cas, j'utilise plutôt les logiciels adaptés. Et pour l'exemple que je voulais mentionner, c'est que j'utilisais des macros pour envoyer des requêtes à travers Org Mode. Et puis je récupère des données de cette requête et pour exécuter une autre requête, juste pour simuler un workflow d'un utilisateur réel. C'était très utile parce que ça m'évite de cliquer plusieurs boutons dans plusieurs logiciels et j'exécute juste le macro Emacs et il fait tout et je vais juste voir le résultat.

Prot: C'est mieux comme ça.

Abdallah: Oui, c'est bien mieux. Plus efficace. Oui, bien sûr.

Sacha: Et moins d'erreurs. Je déteste le clic qui est nécessaire pour le flux de travail dans d'autres applications.

01: Oui.

Sacha: Ton flux de travail. J'aime les petites conditions que tu utilises dans ta configuration, comme helm-chart-template-p dans magic-mode-alist ou org-refile-target-verify-functions pour filtrer les tâches et ton-before-save Hook qui reformat le tampon selon le mode majeur. Dans ta configuration, quelles sont tes modifications préférées?

Abdallah: En fait, j'ai du mal à répondre à cette question parce que c'est tout le fichier qui est configuré et ça fonctionne un peu interconnecté. Donc, je ne peux pas vraiment choisir une partie préférée. Mais je vais dire les modifications qui ont relation avec Org Mode, c'est ce que j'aime trop parce que ça me facilite un peu le flux de travail avec Org Mode.

25:10 Compter les items dans l'inbox

Abdallah: Par exemple, celui-ci où je compte les items que j'ai dans l'inbox.

Sacha: Parce que nos auditeurs peuvent rater les petites icônes, les petites affiches dans ta mode-line avec les boîtes.

Abdallah: Ah oui, je ne sais pas s'il y a une méthode pour l'afficher un peu plus grand, mais c'est juste là, à côté du Org Agenda. Je vais changer ici pour montrer que ça compte ces lignes.

Sacha: C'est une bonne idée. Si tu accumules beaucoup de tâches, les chiffres dans ta mode line te harcèlent.

Abdallah: Oui, bien sûr. C'est toujours là pour me rappeler en cas où j'oublie de... En fait, mon Emacs actuel, il contient, je pense, environ 160. Mais attention, c'est juste parce que je viens de migrer mes notes depuis un autre logiciel. Après avoir migré vers Org Mode, j'ai utilisé AnyType pour les notes. Donc je viens juste de l'émigrer la semaine passée et j'ai pas eu assez de temps pour les déplacer. Donc là je sais que je dois les déplacer parce que sans eux c'est trop.

Sacha: Le problème de tout le monde.

Abdallah: Oui bien sûr.

Sacha: J'aime aussi ta fonction pour récupérer des données, app-core-get-device-ids-by-username. Il y a aussi une autre fonction app-core-delete-items-by-store-id qui semble utile pour ton travail. Tu utilises Emacs pour automatiser des fonctions et tu utilises le shell script.

27:36 Emacs Lisp et d'autres langages

Sacha: Dans quelle situation est-ce que tu préfères écrire ce genre de fonction dans Emacs Lisp plutôt que Shell ou un autre langage?

Abdallah: En fait, j'aime bien le langage Lisp en général. J'aime bien écrire en Lisp malgré que je n'ai pas assez de code base écrit en Lisp. J'ai un peu contribué à, enfin, 1 ou 2 Repos. Mais pour ces fonctions-là, je les générais avec l'intelligence artificielle parce que ça me facilite des tâches un peu répétitives, trop répétitives. Et pour optimiser un peu le temps et aussi pour pouvoir les appeler à travers Emacs. C'est très utile parce que juste en cliquant un bouton, je peux savoir l'information que je vais utiliser. Et je pense que c'est mieux comparé à un shell script où je dois savoir où est le shell script et aller l'exécuter et un peu ouvrir un terminal. Donc là, j'utilise Emacs pour programmer. Je peux appeler cette commande pour extraire une information. Il y a aussi le fait qu'il y a des prompts. Dans Emacs, donc je sais exactement ce que je dois fournir. Et ça me facilite trop le travail, surtout les tâches répétitives, parce que si ça ne répète pas, je ne vais pas écrire une fonction. Et puisque je suis intéressé juste aux résultats, donc je les génère en utilisant l'intelligence artificielle, puisque ça me donne un bon résultat. Je ne m'intéresse pas à ce que ça soit optimisé ou non.

29:32 L'intelligence artificiale

Sacha: Je me demande que tu utilises quelques interfaces pour l'IA dans Emacs comme aidemacs. Ah oui, oui. Qu'est-ce que tu penses?

Abdallah: En fait, je ne sais pas si tu as remarqué dans ma config, il y a pas mal de packages d'IA. J'ai dû utiliser la plupart pour les comparer. Parce qu'en fait, la partie plus difficile pour moi, c'est d'observer les changements qui sont faits par l'IA. Donc, j'ai utilisé tous les packages d'Emacs pour les comparer. Je pense que le meilleur package que j'ai utilisé était eca. Je ne sais pas si vous êtes au courant. Mais actuellement, j'utilise OpenCode dans Emacs. Donc, j'ouvre un terminal dans Emacs et j'utilise OpenCode. Ça me permet de mieux observer les changements et tout. Et parfois j'utilise gptel pour des petites questions sur Emacs sans avoir à ouvrir le terminal. Donc les packages que vous voyez dans ma config, c'est juste pour tester les différentes intégrations.

30:56 Les terminals

Prot: Et quel est le terminal de ton choix ?

Abdallah: Actuellement, j'ai migré vers Ghostel. Ça fait un mois, je pense. Mon problème, c'était Vterm, c'était très bien. Il a résolu tous mes problèmes. Mais il restait un seul problème, c'est les applications TUI. Ça ne marche pas bien sur Vterm. Donc, j'ai dû migrer vers Ghostel. Et là, ça marche très bien. Je peux vous montrer, par exemple, je vais ouvrir un exemple. Vim, par exemple. Bon, je ne sais pas si j'ai... Oui, mais je sais bien comment quitter, mais je ne vais pas ouvrir Vim. Bon, je ne sais pas si ça marche dans cette configuration, puisque c'est une nouvelle configuration. Peut-être je vais partager ma configuration originale pour juste vous montrer la différence.

Sacha: Je pense que tu as partagé seulement la fenêtre d'Emacs.

Abdallah: Oui, je vais changer vers l'autre fenêtre et je vais mettre sur que je ne partage pas quelque chose de sensible. Merci. D'accord, donc là... Est-ce que vous voyez la nouvelle fenêtre avec un... Oui. Parfait. D'accord, donc v-term... Ça doit compiler, d'accord. Je pense que j'ai lancé une mise à jour. Donc si je vais ouvrir Open Code ici. Je ne sais pas si ça va marcher bien. Ah oui, vous pouvez voir il y a un peu de décalage. Il y a ici le prompt. Là, c'est un peu bizarre. Oui, je vais ouvrir une autre fenêtre Ghostel juste pour vous donner une idée. Donc cette fenêtre est Ghostel, je vais ouvrir la même application. Donc là, vous remarquez déjà les couleurs, ils sont mieux que la session Vterm. Et oui, je pense que c'est clair que l'intégration est un peu mieux dans Ghostel. C'est très intéressant. Oui, parce que j'utilise pas mal de terminal TUI dans le travail, donc j'ai toujours une terminal ouvert, mais depuis que j'ai installé Ghostel, ce n'est plus le cas parce que je n'ai plus besoin d'une terminal pour ouvrir ces applications.

Sacha: Emacs assimile une autre fonction.

Abdallah: Oui.

34:14 Les jeux et les amusements

Sacha: Ça m'amuse quand je vois les jeux dans ta configuration et aussi la présence de Nyan Cat dans ta moodline. Quel est ton jeu préféré dans Emacs?

Abdallah: En fait, mon jeu préféré c'est Tetris. Le jeu du Emacs, oui. Celui qui est built-in. Mais j'ai découvert un truc, je ne sais pas si vous êtes au courant, mais le package Emacs de Fedora, il ne contient pas le jeu Tetris.

Prot: Oh non ! Seulement Tetris ? Les autres jeux ?

Abdallah: Oui, seulement Tetris. Parce qu'apparemment, il y a des trucs de copyright, je pense. Parce que j'ai cherché et j'ai trouvé que Ils l'ont enlevé parce qu'il y avait des trucs de droits. Mais les autres jeux que j'ai dans ma configuration, c'était juste pour les tester. Parfois, je lance Tetris, mais c'est rare.

Sacha: Le bricolage d'Emacs est un jeu aussi.

Abdallah: Oui, bien sûr. Et pour le Nyan Cat, je pense que ça fait un changement. Par exemple, quand tu es en train de travailler sur quelque chose, tu prends assez d'efforts et tout, et tu vois le Nyan Cat courir dans le Mood Line, ça te fait un peu du plaisir. Et je me rappelle que je dois relaxer. Oui.

36:07 La communauté

Sacha: Tu as des collègues qui utilisent Emacs aussi ? Ou tous les collègues pensent que tu es fou ?

Abdallah: Oui, je pense que c'est le deuxième cas. La communauté Emacs dans l'Algérie, ce n'est pas grande. J'ai seulement rencontré une seule personne dans ma vie qui utilise Emacs. Je n'ai même pas son contact, donc je l'ai juste rencontré dans une formation, je pense. Parce que je l'ai vu utiliser Magit. Je pense qu'il y a deux collègues qui utilisent Neovim. Je ne sais pas s'ils sont en train de nous regarder parce que j'ai partagé le lien avec eux. Donc s'ils sont en train de nous regarder, je leur passe le bonjour. Et je pense que les autres utilisent VS Code. Donc seulement ces deux utilisent Neovim et les autres VS Code. C'est tout.

Prot: Je crois que c'est le même partout.

Abdallah: Ah bon? Je pensais que c'est un peu différent en Europe ou bien en Amérique. Parce qu'il y a, quand je lis le feed de Sacha, je vois toujours des meets. Des événements organisés, oui. Et à chaque fois que je vois ces événements, je me dis peut-être il y aura un événement en Afrique ou bien en Afrique du Nord, mais il n'y a jamais un événement dans ces endroits. Je pense que c'est juste en Europe ou bien en Asie ou en Amérique.

Sacha: Les événements sont ouverts à tous, donc... Je pense que je vais à l'Emacs Berlin en ligne de temps en temps. Tu veux aussi le rejoindre?

Abdallah: Bon, rejoindre un événement dans l'Europe, c'est un peu compliqué. Donc, je peux seulement rejoindre virtuellement?

Sacha: Oui, bien sûr.

Abdallah: Mais je parlais des événements présentiels. Il n'y a pas d'événement présentiel dans ces régions. C'est juste en Europe.

Sacha: Je ne peux pas aller à l'événement présentiel.

Abdallah: Ah d'accord.

Sacha: Donc je me focalise aussi sur les événements virtuels.

Abdallah: Ah d'accord. Donc c'est le cas pour tous. Je pense que Prot aussi.

Prot: Je ne [connais] personne ici. Même que Vim.

Abdallah: Bon, mais je pense que... Est-ce que tu as des contacts avec des gens qui ont un peu le... Un peu de connaissances techniques dans la région où tu vis, parce que j'ai vu que c'est un peu éloigné de la ville et tout.

Prot: Oui, non, non. Ici, les gens ne sont pas techniques. Avec les ordinateurs. Ils sont plus techniques avec les autres choses. Par exemple, l'agriculture.

Abdallah: Ah oui, oui, bien sûr. Oui, donc... Ton cas est pire que moi, parce que moi je rencontre des gens, mais ils n'utilisent pas Emacs.

Sacha: Heureusement, la communauté Emacs est très bien en ligne, donc je ne me sens pas isolée.

Abdallah: Oui, oui, évidemment. Je pense que si la communauté en Reddit, je trouve que c'est rare qu'il y a une communauté qui est assez, comment dire? Donc tous les gens sont bien, ils parlent d'une bonne façon, ils te donnent des conseils. Il y a rarement ces commentaires toxiques qu'on trouve généralement sur Reddit. Donc c'est ce que j'aime dans la communauté Emacs. Et tout le monde, ils sont bien.

Prot: Est-ce que tous les gens veulent t'aider? Parce que tous les gens comprennent que Emacs est très difficile.

Abdallah: Oui, c'est le cas, oui. Je peux imaginer. Parce que j'ai partagé un post il y a quelques jours sur Magit et il y a plein de gens qui me donnaient des astuces parce que j'ai demandé s'il y avait des astuces supplémentaires. Ils m'ont donné des astuces. Il y avait même des paragraphes très longs. C'était très sympa, oui.

41:44 Accent

Sacha: En lisant ta configuration, j'ai aussi des questions sur le paquet accent pour saisir. Utilises-tu, au lieu de méthode de saisie ou input method sur Emacs ou sur ton système, qu'est-ce qui te plaît dedans?

Abdallah: En fait, je n'utilise pas le français souvent. Donc, et mon clavier est anglais. Donc, quand je veux écrire quelque chose en français, je vais juste écrire en anglais et puis ajouter des accents. Par exemple, je vais dire, je ne sais pas, j'étais dans un live stream et je vais aller ici, je vais faire Accent Menu. Je vais juste changer, sans devoir changer le clavier parce que je ne me rappelle pas l'emplacement des lettres accentuées dans le clavier. Donc je veux juste le faire dans Emacs et je le copie dans un autre navigateur ou bien quelque chose comme ça. J'ai seulement le clavier anglais et le clavier arabe parce que c'est totalement une différente méthode d'écrit. Est-ce que vous utilisez ce package puisque vous apprenez le français?

Prot: Moi non, moi j'utilise l'input method.

Abdallah: Ah d'accord. C'est quelque chose intégré dans Emacs j'imagine, parce que je pense que j'ai entendu parler. Oui, c'est exactement comme ça.

Sacha: Il y a peut-être une vidéo récente sur les méthodes d'input, non?

Abdallah: Je pense que c'est une fonctionnalité intégrée dans Emacs. C'est vrai? Parce que je me rappelle que j'ai une fois, je l'ai activé par accident.

Sacha: C-\.

Abdallah: Oui et je pouvais écrire en arabe dans Emacs. Et je me demandais pourquoi parce que le clavier était en anglais. C'était un peu... Mais c'est une fonctionnalité très intéressante. Je vais voir plus tard.

Sacha: Je pense que tu as répondu à toutes mes questions que j'ai préparées sur ta configuration, donc la conversation est libre. J'ai une question.

44:45 Tes prochaines étapes

Sacha: Quelles sont tes prochaines étapes pour apprendre Emacs? Qu'est-ce qui t'intrigue?

Abdallah: Actuellement, je veux apprendre, un peu me familiariser avec les macros parce que je les ai utilisés précédemment, comme je vous ai dit dans l'exemple. Mais je ne suis pas assez familier et je veux l'intégrer plus dans mon flux de travail pour un peu automatiser des trucs. Et je veux aussi... Je veux aussi apprendre Emacs Lisp. En fait, je connais le langage Lisp, donc c'est un peu facile, mais je trouve qu'il y a des fonctions très spécifiques d'Emacs qu'il faut apprendre pour maîtriser Emacs Lisp. Oui, c'est très intéressant, surtout pour pouvoir contribuer dans la communauté Emacs. J'ai voulu contribuer il y a quelques mois sur un package Emacs, mais j'ai trouvé qu'il fallait faire un peu [de] paperasse, paperwork parce que c'est en relation avec GNU. Et oui, donc c'était un peu difficile. Il fallait demander à mon employeur de signer quelques trucs et tout. Et il pense que je vais travailler ou quelque chose comme ça. Oui, donc c'était un peu difficile, mais je veux bien apprendre Emacs Lisp pour pouvoir contribuer et aussi pour faciliter des flux de travail personnels.

Sacha: Si tu veux aller contribuer à NonGNU ELPA, je pense que la paperasse n'est pas nécessaire.

Abdallah: Je pense que le package que j'allais contribuer à était sous la licence Emacs. Il s'appelle dape. C'est un debugger pour eglot. Il s'intègre avec eglot. Je pense que c'est une exigence dans le package lui-même.

Prot: C'est pour tous les paquets dans le répertoire GNU ELPA.

Abdallah: C'est le cas. Mais je pensais que c'était juste pour les packages qui sont intégrés dans Emacs. Parce que ce package, il était disponible sur GitHub, mais le maintainer, il exige que le code soit sous licence de GNU.

47:39 Org Mode et des tableaux

Abdallah: Aussi, j'aimerais bien manipuler Org Mode De plusieurs façons. Par exemple, récemment, j'ai un peu testé les tableaux. J'ai utilisé les tableaux pour essayer de gérer mes dépenses personnelles. Oui, c'était très intéressant. Parce que des fois, il y a des fonctionnalités dans Org Mode, même dans Emacs, où on ne sait pas vraiment ce qu'on va avoir comme fonctionnalités. Si on ne va pas vers le manuel, donc si tu ne lis pas le manuel, tu ne sais pas que cette fonctionnalité existe. Par exemple, les tableaux, si on ne va pas dans le manuel et on voit que les tableaux, il permet de faire des calculs et automatiser des formules, ça ne serait pas évident de savoir cette information parce que le format Markdown, il n'offre pas cette fonctionnalité. Je peux, je pense, vous montrer un exemple. Je pense que je l'ai quelque part sur cette démonstration. Là, je vais démontrer comment je cherche une note. Donc, je vais chercher « budget ». J'espère que je vais le trouver. D'accord, donc « monthly budget ». Là, j'ai changé les valeurs pour la vidéo. Mais vous pouvez voir par exemple là, j'ai des catégories de dépenses. J'ai par exemple le salaire ici et le salaire du mois. Par exemple, en cas où dans un certain mois, j'ai plus ou moins de ce chiffre. Et là, j'ai le nombre que je peux associer à ces catégories. Et ça diminue à chaque fois que je... J'associe un numéro ici. Par exemple, là, si je vais changer celle-ci en 4 et je vais faire un calcul, ça change toutes les valeurs ici pour savoir combien il nous reste pour la catégorie Savings. Si je vais augmenter, ça va aussi changer, je pense. Oui, vous remarquez que ça va changer ici. Donc, je peux associer parce qu'en fait, j'utilise la méthode Budget... Non.

Sacha: You Need A Budget.

Abdallah: Enveloppe. Enveloppe, budget. Oui, oui, oui.

Sacha: Moi aussi, moi aussi.

Abdallah: Ah, d'accord.

Sacha: Je connais le livre.

Abdallah: J'utilise cette méthode et chaque mois, je vois ce tableau et je commence à diminuer pour chaque catégorie et je mets l'argent dans chaque enveloppe pour m'organiser un peu. Et cela me permet de...

50:40 Ledger, Hledger, Gnucash

Sacha: Dans Emacs, j'utilise Ledger pour faire ça. J'utilise aussi le système d'enveloppe virtuelle pour gérer mes dépenses, mes économies. J'adore le système d'enveloppe virtuelle. Tu connais Ledger?

Abdallah: Ledger, oui. Ledger, le mode sur Emacs.

Sacha: Et aussi un outil, un outil de lien de commande.

Abdallah: Oui, oui, bien sûr. Je pense que c'était écrit par John Wiegley.

01: Oui, oui.

Abdallah: Je l'ai utilisé à un certain moment, j'ai oublié de mentionner. Donc, oui, je l'ai utilisé pendant, je pense, quelques années. C'était très intéressant. Mais mon problème, c'était la visualisation. Il y avait une intégration avec gnuplot, si je me rappelle bien. Mais c'était un peu compliqué à configurer et j'avais besoin de quelque chose de simple. Donc j'ai arrêté d'utiliser Ledger et j'utilise maintenant Gnucash.

Sacha: Je [connais] aussi hledger qui a beaucoup de visualisations sur l'interface web. Donc si tu veux des visualisations...

Abdallah: Il s'appelle comment?

Sacha: Hledger.

Abdallah: hledger, ok. Je vais noter.

Prot: Avec le h.

Sacha: Le format des données est assez pareil, mais il y a une interface web pour explorer des données.

Abdallah: Ah d'accord, je vois. C'est intéressant, je vais voir plus tard. Est-ce que vous avez utilisé Gnucash ?

Sacha: [J'ai utilisé Gnucash] il y a beaucoup d'années pour gérer les finances de mon entreprise, mais je préfère ledger parce que c'est dans Emacs.

02: Oui, oui.

Sacha: Donc, j'ai fait justement un fichier Org avec tous mes reports. Et une fois que j'ai fait les reports, je les laisse. Je les laisse pour juste reexecuter.

Abdallah: Est-ce que vous avez documenté votre méthode de gérer cela ?

Sacha: Oui, j'ai une blog post de mon système d'enveloppe virtuelle avec Ledger. Je vais t'envoyer le lien.

Abdallah: Oui, ce serait intéressant de voir.

Sacha: Tu peux utiliser ta transaction virtuelle et tu peux définir les règles pour ajouter ou [soustraire] des comptes [correspondants] pour chaque transaction.

Abdallah: En fait, je faisais exactement ça. C'est de créer des comptes virtuels et puis l'utiliser pour faire des budgets comme enveloppe budgeting. Oui, j'ai fait exactement ça. Je pense que je l'ai trouvé quelque part dans Reddit.

Sacha: Donc si j'importe les données de ma banque, presque toutes les transactions sont catégorisées automatiquement.

Abdallah: Ah d'accord, c'est très bien. J'ai toujours voulu avoir cette intégration mais les banques ici n'offrent pas des fonctionnalités similaires.

Sacha: J'ai téléchargé chaque [CSV] manuellement.

Abdallah: Ah, le fichier CSV?

Sacha: Oui, le fichier CSV.

Abdallah: Ah d'accord, donc vous allez manipuler le fichier CSV pour le convertir. D'accord, je vois, je vois.

Prot: Et c'est difficile de le convertir?

Sacha: Non, non, j'ai créé une fonction pour utiliser la bibliothèque pcsv pour [extraire] les données [du] fichier CSV et après ça traduire vers le format Ledger.

Abdallah: C'est très intéressant. En fait, ça aurait été très utile dans mon cas parce que je le fais manuellement actuellement. Bon, je n'ai pas tellement de transactions sur mon compte puisque je gère mon compte personnel. Mais quand même, ça prend, je dirais, deux heures par mois. Juste pour synchroniser un peu. Les deux comptes. Le compte de Gnucash et celui de banque.

Sacha: Apparemment, une bonne excuse pour automatiser avec Emacs.

Abdallah: Oui, peut-être. Mais je ne sais pas comment... Est-ce qu'il y a une façon d'importer les données dans Gnucash? Je pense qu'il y a une manière, mais je ne l'ai pas encore explorée. Et aussi, je me demande si... Comment vous... Vous déplacez les dépenses dans les catégories. Comment vous le programmez?

Sacha: Par le nom du payee.

Abdallah: Ah d'accord.

Sacha: Donc si j'achète les aliments au supermarché, le nom du payee est No Frills. J'assigne les catégories par le nom.

Abdallah: Oui, parce que c'est la partie un peu que je trouve un peu difficile à automatiser dans mon cas parce que je dois le faire manuellement et il n'y a pas de nom de payee. Oui, parce que le CSV que je peux exporter dans la banque, il ne contient qu'un numéro que je ne sais pas le numéro de compte, je pense. Oui, les banques ici ne sont pas assez développées. C'est pour ça. Tant que tu peux extraire ton argent et que tu peux faire des transactions en ligne, tu es super.

Sacha: Je pense que je dois réveiller ma fille pour notre grande aventure au parc d'attractions. Donc, un grand merci à tous les deux pour notre conversation et [ta] patience avec nos erreurs techniques. À la prochaine.

Abdallah: Ça m'a fait un grand plaisir de converser avec vous. J'espère que c'est demain pour vous et on se voit dans la prochaine.

Prot: À la prochaine à vous aussi et à bientôt. Merci. À bientôt. Au revoir.

View Org source for this post

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

-1:-- Emacs Chat 29: Abdallah Maouche (en français) (Post Sacha Chua)--L0--C0--2026-08-23T13:53:00.000Z

James Cherti: Emacs Spell Checker: Configuring Flyspell, Ispell, and Aspell to Minimize False Positives in Source Code and Prose

The ispell package serves as the underlying interface in Emacs for communicating with external spell checking programs. Building upon this, the flyspell package is a built-in minor mode that provides on-the-fly spell checking. It highlights misspelled words as you type and offers interactive corrections. This article presents a practical configuration for Emacs that sets up ispell to use Aspell for both programming and standard text editing.

Additionally, it addresses a common issue for developers. While writing code, variables such as filepath or buffername in comments or docstrings are often flagged as errors, generating visual noise in your programming buffers. This article contains a specific configuration to reduce these false positives.

Setting the backend and dictionary

The first step is to configure Emacs to use Aspell as the spell-checking engine, set the default dictionary, and reduce unnecessary messages when checking individual words:

;; Set the ispell program name to aspell
;; (switching to aspell will generally offer better performance than ispell.)
(setq ispell-program-name "aspell")

;; Set the global default dictionary for the Ispell process.
(setq ispell-dictionary "en_US")

;; Reduce unnecessary messages when checking individual words.
(setq ispell-quietly t)

Note that this configuration explicitly targets the US English dictionary "en_US". If you write in a different language or require a different regional dialect, such as "en_GB" or "fr_FR", replace "en_US" with your preferred language code.

Configuring aspell flags

The following configures Aspell's suggestion mode to "ultra", which favors very close spelling and phonetic matches when generating suggestions:

;; Configure Aspell's suggestion mode to "ultra", which favors very close
;; spelling and phonetic matches when generating suggestions.
(setq ispell-extra-args '("--sug-mode=ultra"))

Handling compound words in source code

Comments and string literals in source code are heavily populated with compound variable names and technical terms, such as filepath, buffername, or checkbox. Aspell may flag these as errors, generating visual noise in programming buffers.

To resolve this, we pass the --run-together flag to Aspell. This flag allows Aspell to recognize words formed by combining valid dictionary words without spaces. (It only affects words in the comments and strings that Flyspell checks; it does not cause Flyspell to check arbitrary identifiers elsewhere in the source buffer.)

To apply this cleanly, we can define a custom wrapper function that we will later attach to our programming modes using add-hook. This function activates flyspell-prog-mode and then safely appends the required flags buffer-locally using add-to-list, which inherently prevents duplicate arguments if the mode is toggled off and on:

(defun my-flyspell-prog-mode (&rest _args)
  "Enable `flyspell-prog-mode' with buffer-local Aspell arguments."
  ;; The --run-together flag instructs Aspell to accept words formed by
  ;; combining two or more valid dictionary words without spaces, treating the
  ;; resulting string as valid.
  ;;
  ;; This is excellent for source code. Code is heavily populated with
  ;; compound variable names and technical terms (e.g., filepath, buffername,
  ;; checkbox).
  ;; URL: https://www.jamescherti.com/emacs-spell-checker-flyspell-ispell-aspell/
  (make-local-variable 'ispell-extra-args)
  (dolist (item '("--run-together"
                  ;; "--ignore=2"
                  ;; "--run-together-min=3"
                  ;; "--run-together-limit=4"
                  ;; "--camel-case"
                  ))
    (add-to-list 'ispell-extra-args item))
  (flyspell-prog-mode))

Here are four additional flags worth uncommenting in the my-flyspell-prog-mode function above. Each provides specific advantages for programming workflows, along with some tradeoffs to consider:

  • --run-together-limit=4: Increases the maximum number of combined words allowed in a single string from the default limit of two up to 4. (A higher limit increases the probability that a genuine typo will be silently ignored because Aspell manages to break the misspelling down into a sequence of several shorter valid words. It also adds a slight processing overhead.)
  • --run-together-min=3 sets the minimum length of the individual words that Aspell is allowed to use when recognizing a run-together compound. For example, with a value of 3, Aspell can recognize filepath as file + path, but it is less likely to accept a compound that depends on very short components such as a or in. Increasing this value can therefore reduce false positives in source code by preventing Aspell from accepting suspicious compounds made up of short, common words.
  • --ignore=2: Configures Aspell to completely skip words containing two characters or fewer. Tradeoff: You lose spell checking for actual two-letter English words (such as "is", "an", "to", "of").
  • --camel-case: Enables Aspell's camel-case word handling, allowing camelCase identifiers to be checked more appropriately. It can reduce false positives for identifiers such as myHttpRequest that appear in docstrings and comments. Tradeoff: It can occasionally mask a typo if its components are individually recognized as valid words.

Initializing the Packages

Finally, we use add-hook to bind the spell checking functions to the appropriate Emacs hooks:

;; The flyspell package is a built-in Emacs minor mode that provides on-the-fly
;; spell checking. It highlights misspelled words as you type, offering
;; interactive corrections.
;; URL: https://www.jamescherti.com/emacs-spell-checker-flyspell-ispell-aspell/
(defun my-flyspell-enable-appropriate-mode ()
  "Enable the appropriate Flyspell mode based on the current major mode."
  (if (or (derived-mode-p 'conf-mode)
          (derived-mode-p 'yaml-mode)
          (derived-mode-p 'yaml-ts-mode)
          (derived-mode-p 'ansible-mode)
          (derived-mode-p 'nxml-mode)
          (derived-mode-p 'sgml-mode))
      (my-flyspell-prog-mode)
    (flyspell-mode 1)))

(add-hook 'prog-mode-hook #'my-flyspell-prog-mode)
(add-hook 'conf-mode-hook #'my-flyspell-enable-appropriate-mode)
(add-hook 'text-mode-hook #'my-flyspell-enable-appropriate-mode)

Note: Configuration files such as YAML and Ansible playbooks can present an edge case because some source-like modes are based on text-mode rather than prog-mode. Without an explicit override, such modes may receive ordinary Flyspell behavior rather than programming-mode behavior. Since these files behave more like source code, we use a custom dispatcher function to apply my-flyspell-prog-mode to them instead.

A few additional interesting options

Several lesser-known variables can be adjusted to change Flyspell's behavior and, in some cases, reduce the amount of work it performs. These options involve tradeoffs: reducing spell-checking activity can improve responsiveness, but may also reduce the amount of text that is checked or the feedback that Flyspell provides.

  • Setting flyspell-issue-message-flag and flyspell-issue-welcome-flag to nil suppresses Flyspell's checking messages and its startup welcome message. This removes minibuffer messages generated by Flyspell without changing the actual spell-checking operation.
  • Setting flyspell-check-changes to a non-nil value causes Flyspell to check only words that have been typed or edited, instead of also checking words that point moves across. This can reduce spell-checking activity when navigating through existing text. The tradeoff is that existing misspellings are not checked because point moves across them, so they may remain undetected until the text is edited or checked explicitly.
  • Setting flyspell-delay-use-timer to a non-nil value makes Flyspell use a timer instead of sit-for when waiting after delayed commands. This allows idle timers and other Emacs code to run during the delay. The option therefore changes how Flyspell waits before performing a check rather than disabling the delay itself.
  • Setting flyspell-mark-duplications-flag to nil prevents Flyspell from reporting repeated words as errors. This eliminates duplicate-word detection, so accidental repetitions such as "the the" are no longer reported.
  • Setting ispell-silently-savep to a non-nil value causes new words added to the personal dictionary to be saved without an interactive confirmation prompt. This avoids the prompt when saving personal-dictionary additions, but also removes that opportunity to review each addition before it is saved.
  • Setting flyspell-highlight-properties to nil prevents Flyspell from highlighting incorrect words when the relevant text already has text properties. The spelling check can still identify the word as incorrect, but Flyspell will not add its normal misspelling highlight when an applicable text property is present. This can be useful when existing text properties should take precedence over Flyspell's visual indication.
  • Lowering flyspell-large-region tells Flyspell to use its faster bulk-checking method for smaller sections of text. Instead of checking each word individually, Flyspell sends the larger section to the external Ispell/Aspell program to check it in one operation. For example, changing the value from 1000 to 200 makes Flyspell use this method for regions containing 200 or more characters. This can improve performance when checking larger sections, but repeated words such as "the the" are not detected when this bulk-checking method is used.
  • Increasing flyspell-delay increases the number of seconds Flyspell waits before checking after a command classified as delayed. This can reduce how frequently spell checking is triggered during rapid editing, at the cost of delaying the appearance of misspelling highlights.
-1:-- Emacs Spell Checker: Configuring Flyspell, Ispell, and Aspell to Minimize False Positives in Source Code and Prose (Post James Cherti)--L0--C0--2026-08-23T13:00:26.000Z

Matt Maguire: AI Agents and Local LLMs

In my last article on AI agents I talked about using Claude Code AI agent. This is an easy-to-set-up commercial solution that gives excellent results. The risk is that the pricing may not remain as affordable as it is today, and as a result I wanted to find an alternative solution that insulates me from changing commercials.

There are a number of open-weight models that one may download and run locally on your home computer. Some of the benefits are:

-1:-- AI Agents and Local LLMs (Post Matt Maguire)--L0--C0--2026-08-23T00:00:00.000Z

Monadic Sheep: An Introduction to Canvas in GNU Emacs

by Divya
2026 August 23

The canvas patch has been finally merged to upstream GNU Emacs. If you have been following my Mastodon, this was a journey that took us ~8 months. But finally we have, in core Emacs, a feature that I desperately desired in early 2025 when I was starting the Emacs Reader project: a way to update and manipulate images in Emacs without choking Emacs by putting the image data in strings. I went through a series of different hacks to overcome these limitations, some more successful than others, but thanks to the magnificent Daniel (aka minad) who suggested the idea of exposing a pixel buffer from within Emacs via the dynamic module API. That idea is now a feature in Emacs, and will be part of Emacs 32 release cycle. A more detailed history about this will be written later, for now this article is an introduction to the new feature, its API and what you can do using it. I will try to keep it as self-contained as I can, so that beginners are able to understand it. But first, we need to clarify certain misconceptions that people might have about this feature.

1. Frequently Asked Questions

  1. “Is this related to HTML5 Canvas?”

    No. It has nothing to do with any web framework or technology. We call it “canvas” because it allows the user to draw arbitrarily on to a surface in an Emacs buffer. So at a high-level, both the HTML5 and Emacs canvas do the same thing, but they have zero relation in their implementation or how they work. HTML5 Canvas also has a more rich API with drawing functions and such, right now Emacs’ canvas API integrates with the existing image API and just provides access to the underlying pixel buffer via dynamic module API. See Canvas via Dynamic Modules.

  2. “How is this different from SVG that Emacs has support for since a long time?”

    Good question! So, at the level of an Emacs buffer the canvas looks and behaves just like any other Emacs image, including SVGs. Where it differs from SVG and other images is:

    • a canvas’ :data is just ARGB32 pixel data (in strings or vectors), so no specific compressed format.
    • the ability to access an Emacs Lisp image object from a lower level and be able to manipulate it
    • update the image without having to call a full redisplay
  3. “Isn’t this still limited by Emacs’ redisplay engine?”

    Not entirely. We have a function called canvas-refresh that is to be called every time you wish to update the canvas, and this function doesn’t use/call Emacs’ redisplay, it has its own redrawing path that only updates the specific glyphs in the buffer that pertain to that canvas. Thus, whereas a full redisplay would cost more because it will try to update all the glyphs that changed, canvas-refresh only updates what needs to be. Since Emacs 25 we have double-buffering so if that’s enabled in the frame you’d still need to call redisplay to flip the buffers, but that won’t slow down your canvas. If you’re not convinced, see the demos below that run smooth at 25, 30, and 60FPS without causing any lag to the rest of the Emacs session. Or, look at this where I display a 2K60 FPS and a 720p 30FPS video side by side without Emacs missing a beat!

  4. “Is this hardware-accelerated? If not, why are you not using the GPU for this?”

    Because we simply don’t need it :D! A canvas simply exposes a pixel buffer, and Emacs simply redisplays it as it changes. You can get your pixels hardware accelerated outside of Emacs if you wish to, and then put them in canvas’ pixel buffer. This will give you hardware acceleration where you need it the most, pixel calculation, texture math, etc. Ideally, an introduction of a GPU powered redisplay within Emacs itself would be great and certainly help but one can still reap much of the benefits of HW acceleration from current Emacs via canvas.

2. What Is a Canvas?

Before answering that question, we should ask: “what is an image in GNU Emacs?” Everybody has seen Emacs display dazzling images since decades, and of varying formats. How does Emacs do that? Well, at the level of an Emacs buffer we have something called Text Properties (also see Overlay Properties), as its name suggests it is a property list (aka plist) for a character position or a string within a buffer. Among the many properties a text can have, there’s a special one called the display property. This special property is responsible for how the text gets displayed. This is exactly what we use to display images! You use create-image to create an image object, which is exactly a plist, and then you set this image object (called an image specification) to be the display property of some text or overlay (which are like text properties but without the "text", it’s an object that belongs to a particular buffer with specific beginning and end and along with properties just like for usual text).

This is briefly how the API of how images work in GNU Emacs. The new canvas feature works in full compatibility with this API. In short, at the level of an Emacs buffer, a canvas is just another image type! Indeed, compiling the latest GNU Emacs from source and evaluating: (image-type-available-p 'canvas) should return a t.

The immediate question then is: if Emacs already supported images, and if canvas just follows the same API, then what was the point of it? Why have a new image type, when we have XBM, PPM, PNG, GIF, SVG, etc.? Well, the two-fold short answer is:

  1. You need to pass image data via strings to create these objects and for most of the above formats it’s expensive to do so (except SVGs)
  2. Any arbitrary external image data cannot be directly displayed without throttling Emacs strings or objects, etc. (i.e., there’s no low-level access to the image data)

With SVGs you can mostly do really good things as long as they are simple enough, the moment you lead to have complex graphics at high refresh rate, Emacs starts throttling. I wanted in 2025 was exactly a way around this, a way to access the image Emacs is displaying at pixel-level so that I can update it in-place by fiddling with the pixels without having to pass strings or Emacs Lisp objects through the garbage collector. And this is what a canvas provides.

So, there are two levels at which a canvas can be viewed and used:

  1. purely from Emacs Lisp as an image object
  2. as an ARGB32 pixel buffer via dynamic module API

This allows for full integration with the existing Image API of Emacs while not sacrificing on low-level efficiency of being able to manipulate the pixel buffer directly. Now I will demonstrate how one can use the canvas in these two ways separately and/or simultaneously.

3. Creating and Using A Canvas

3.1. Canvas via Emacs Lisp Only

Like usual Emacs images, one can create images either by using create-image or manually building the image spec plist. For a canvas, the latter approach would look like this:

(setq test-canvas `(image :type canvas
                          :id test
                          :data-width 10
                          :data-height 10
                          :data nil))

This is what a canvas image’s specification looks like, the only difference between this and other images (other than the obvious :type canvas) is that canvas images need an :id property, this is used to uniquely identify each canvas.

If you used instead: (setq test-canvas (create-image nil 'canvas t)) to create the canvas, it would look almost the same:

(image :type canvas :data nil :scale default :id g138)

Since, of course, we didn’t provide any data in either of the cases, they are not technically valid canvas images, but that is how it looks like. To make things more interesting, remember that the "data" that a canvas accepts is ARGB32 pixel arrays. This can be done via Emacs Lisp through either unibyte strings or vectors. The latter is a bit easier to showcase, so I will go continue with that.

First, let’s create our array of pixels. We use make-vector:

;; A red square of 10x0 size
(setq rect-vec (make-vector (* 10 10) #xFFFF0000))

This creates a vector of 10x10 size containing each element as #xFFFF0000 (red) 32-bit ARGB pixel. It’s literally an pixel by pixel array vector that contains the color red. This is valid data for our canvas, we can now create it manually:

(setq rect-canvas `(image :type canvas
                          :id rect
                          :data-width 10
                          :data-height 10
                          :data ,rect-vec))

Now we can simply display this:

(insert (propertize "#" 'display rect-canvas))

After evaluating the above, you’ll see a tiny red square in your buffer! Voila, we now have a small canvas!

3.1.1. Note on Canvas Refresh

This might not seem too useful, because you can make a small rectangle via SVGs much quickly. The real value of canvas arises with the use of canvas-refresh function using which you can update the canvas without requiring a full redisplay. It only touches the glyphs which cover the particular canvas and updates them immediately. This is significantly cheaper than calling a full redisplay that will try to update all the changes.

Also, canvas-refresh has an additional optional argument RELOAD-DATA which if non-nil will reload any new updated data (either via the :data property or change in :file) from the canvas. It will be demonstrated later how to use this function properly.

3.2. Canvas via Dynamic Modules

As suggested in the introduction, canvases can be used either via Emacs Lisp and/or via dynamic modules. To know more about dynamic modules in Emacs and how to use them effectively, please consult my previous article on it. Emacs 32 provides a simple API for dealing with canvases from dynamic modules. The idea is simple, once a canvas has been created you can simply call the canavs_data function on it and it will provide you with the pixel buffer associated with the canvas. Once again, this buffer is only valid for ARGB32 pixel data.

static emacs_value
Fcanvas_update(emacs_env* env, ptrdiff_t nargs, emacs_value args[], void* data) {
  emacs_value canvas = env->args[0];
  uint32_t *canvas_pixel = env->canvas_data(env, canvas); // accessing canvas' pixel buffer
  if (canvas_pixel)
    {
      // do some pixel manipulation to the canvas pixel
    }
}

env->funcall(env, env->intern(env, "canvas-refresh"), (emacs_value[])[rect_canvas, Qnil]);

The above dynamic module function, when embedded in a legitimate dynamic module, compiled and loaded via module-load, would result in updating the canvas repeatedly by changing the colors (or whatever the pixel manipulation code does). This is how we can surpass Emacs Lisp’s limitations by accessing a low-level representation of the canvas we can manipulate directly. And since canvas-refresh avoids throttling Emacs’ full redisplay we do not encounter any slowness at all! And since we don’t use :data via Emacs Lisp image spec, whenever canvas is used via dynamic modules the RELOAD-DATA argument of canvas-refresh is to be nil.

4. Demonstrations

Here we showcase a few neat graphical animations you can do with canvas, both in Emacs Lisp and dynamic modules. For our previous demonstrations, consult Tushar’s blog post where he collects all of the experiments I did with and without Emacs. Also, look at the Minad's emacs-canvas-patch which has some basic demos (including a fancy mode-line one!). The first two of the demos below would be purely in Emacs Lisp and the last one would be in C via a dynamic module.

4.1. A Bouncing Ball

Let’s start with creating and setting up our canvas. We’ll go with dark violet (#xFF8B00FF) for the background:

(setq W 250
      H 250
      BG #xFF8B00FF)
(setq ball-canvas-vec (make-vector (* W H) BG))
(setq ball-canvas `(image :type canvas
                          :id rect
                          :data-width ,W
                          :data-height ,H
                          :data ,ball-canvas-vec))
(insert (propertize "#" 'display ball-canvas))

This is exactly what we did in the previous section, just in violet! Now is the interesting part, we want this 250x250 pixel canvas to have a ball bouncing. Well, first we prepare the ball, it will have some radius, an initial position and some initial velocity (and color!):

(setq ball-radius 10)
(setq bx (/ W 2)
      by (/ H 2)) ; start from the center
(setq dx 4
      dy 3) ; initial velocity
(setq ball-color #xFFFFFF00) ; yellow

Now we need to actually draw the ball, now I will ask you to remind yourself of some analytical geometry for this! The way we draw the ball is we take a point to be the center of the circle and check if we can build a circle around a certain bounding box. If ball-radius is 15, thus the diameter is 30, so we need at least a 30x30 square to fit the circle. Now to draw the actual circle, we’ll use the equation for a circle:

x 2 + y 2 ≤ r 2 superscript 𝑥 2 superscript 𝑦 2 superscript 𝑟 2 x^{2}+y^{2}\leq r^{2}

Putting this in Emacs Lisp, we get:

(defun draw-ball (cx cy color)
  (let ((r2 (* ball-radius ball-radius))) ; Radius squared
    ;; the nested loop checks for the circle within the square
    (dotimes (y-off (1+ (* 2 ball-radius)))
      (dotimes (x-off (1+ (* 2 ball-radius)))
        (let* ((px (+ cx (- x-off ball-radius)))
               (py (+ cy (- y-off ball-radius)))
               (dx-local (- px cx)) ; Horizontal distance from center
               (dy-local (- py cy)) ; Vertical distance from center
               (dist2 (+ (* dx-local dx-local) (* dy-local dy-local)))) ; squared distance using pythagorean theorem
          ;; check if pixel is inside the circle AND inside the canvas bounds
          (when (and (>= px 0) (< px W) (>= py 0) (< py H) (<= dist2 r2))
            (aset ball-canvas-vec (+ (* py W) px) color)))))))

The above function should make sense, while it is not the most efficient way to draw a ball, it’s relatively simple enough for the demonstration. Now we simply need to check the physics for making sure it stays within the canvas:

(defun ball-bounce ()
  (setq bx (+ bx dx) by (+ by dy))
  ;; compare with ball-radius so the *edge* of the circle bounces, not the center.
  (when (<= bx ball-radius) (setq dx (abs dx)) (setq bx ball-radius))
  (when (>= bx (- W ball-radius)) (setq dx (- (abs dx))) (setq bx (- W ball-radius)))
  (when (<= by ball-radius) (setq dy (abs dy)) (setq by ball-radius))
  (when (>= by (- H ball-radius)) (setq dy (- (abs dy))) (setq by (- H ball-radius))))

And now we simply do it in a loop!

(setq ball-frame 0)
(setq max-ball-frames 2000)
(defun ball-loop ()
  (if (>= ball-frame max-ball-frames)
      (cancel-timer ball-timer)
    (setq ball-frame (1+ ball-frame))
    (draw-ball bx by BG) ; make old ball invisible (turn it into background color)
    (ball-bounce)
    (draw-ball bx by ball-color)
    (canvas-refresh ball-canvas t)))
(setq ball-timer (run-with-timer 0 0.02 'ball-loop))

Evaluate it all and you would have a yellow ball bouncing inside a violet square. It automatically stops after 2000 frames, but you can have it go on indefinitely as well! Here’s what it will look like:

4.2. Chaotic Double Pendulum

One of the things that fascinated me when I was studying classical mechanics for the first time was the double pendulum. The math underlying this: the Euler-Lagrange equation and variational calculus in general was also very fascinating to me. If you think it’s not, I suggest you look into the history and math behind the Brachistochrone curve. It’s evident from all this, that I want to put this into visualization using canvas!

The two equations in Lagrangians which describes the chaotic system:

θ ¨ 1 = − m 2 ⁢ cos ⁡ ( θ 1 − θ 2 ) ⁢ l 1 ⁢ θ ˙ 1 2 ⁢ sin ⁡ ( θ 1 − θ 2 ) + m 2 ⁢ cos ⁡ ( θ 1 − θ 2 ) ⁢ g ⁢ sin ⁡ ( θ 2 ) − m 2 ⁢ l 2 ⁢ θ ˙ 2 2 ⁢ sin ⁡ ( θ 1 − θ 2 ) − ( m 1 + m 2 ) ⁢ g ⁢ sin ⁡ ( θ 1 ) l 1 ⁢ ( m 1 + m 2 − m 2 ⁢ cos 2 ⁡ ( θ 1 − θ 2 ) ) subscript ¨ 𝜃 1 subscript 𝑚 2 subscript 𝜃 1 subscript 𝜃 2 subscript 𝑙 1 superscript subscript ˙ 𝜃 1 2 subscript 𝜃 1 subscript 𝜃 2 subscript 𝑚 2 subscript 𝜃 1 subscript 𝜃 2 𝑔 subscript 𝜃 2 subscript 𝑚 2 subscript 𝑙 2 superscript subscript ˙ 𝜃 2 2 subscript 𝜃 1 subscript 𝜃 2 subscript 𝑚 1 subscript 𝑚 2 𝑔 subscript 𝜃 1 subscript 𝑙 1 subscript 𝑚 1 subscript 𝑚 2 subscript 𝑚 2 superscript 2 subscript 𝜃 1 subscript 𝜃 2 \displaystyle\ddot{\theta}_{1}=\frac{-m_{2}\cos(\theta_{1}-\theta_{2})\,l_{1}% \dot{\theta}_{1}^{2}\sin(\theta_{1}-\theta_{2})+m_{2}\cos(\theta_{1}-\theta_{2% })\,g\sin(\theta_{2})-m_{2}l_{2}\dot{\theta}_{2}^{2}\sin(\theta_{1}-\theta_{2}% )-(m_{1}+m_{2})g\sin(\theta_{1})}{l_{1}\left(m_{1}+m_{2}-m_{2}\cos^{2}(\theta_% {1}-\theta_{2})\right)}
θ ¨ 2 = ( m 1 + m 2 ) ⁢ ( l 1 ⁢ θ ˙ 1 2 ⁢ sin ⁡ ( θ 1 − θ 2 ) + θ ˙ 2 2 ⁢ sin ⁡ ( θ 1 − θ 2 ) ⁢ cos ⁡ ( θ 1 − θ 2 ) ⁢ m 2 ⁢ l 2 m 1 + m 2 + cos ⁡ ( θ 1 − θ 2 ) ⁢ g ⁢ sin ⁡ ( θ 1 ) − g ⁢ sin ⁡ ( θ 2 ) ) l 2 ⁢ ( m 1 + m 2 ⁢ sin 2 ⁡ ( θ 1 − θ 2 ) ) subscript ¨ 𝜃 2 subscript 𝑚 1 subscript 𝑚 2 subscript 𝑙 1 superscript subscript ˙ 𝜃 1 2 subscript 𝜃 1 subscript 𝜃 2 superscript subscript ˙ 𝜃 2 2 subscript 𝜃 1 subscript 𝜃 2 subscript 𝜃 1 subscript 𝜃 2 subscript 𝑚 2 subscript 𝑙 2 subscript 𝑚 1 subscript 𝑚 2 subscript 𝜃 1 subscript 𝜃 2 𝑔 subscript 𝜃 1 𝑔 subscript 𝜃 2 subscript 𝑙 2 subscript 𝑚 1 subscript 𝑚 2 superscript 2 subscript 𝜃 1 subscript 𝜃 2 \displaystyle\ddot{\theta}_{2}=\frac{(m_{1}+m_{2})\left(l_{1}\dot{\theta}_{1}^% {2}\sin(\theta_{1}-\theta_{2})+\dfrac{\dot{\theta}_{2}^{2}\sin(\theta_{1}-% \theta_{2})\cos(\theta_{1}-\theta_{2})\,m_{2}l_{2}}{m_{1}+m_{2}}+\cos(\theta_{% 1}-\theta_{2})\,g\sin(\theta_{1})-g\sin(\theta_{2})\right)}{l_{2}\left(m_{1}+m% _{2}\sin^{2}(\theta_{1}-\theta_{2})\right)}

Now let’s create the canvas as usual with black background and display it like before:

(setq W 250
      H 250
      BG #xFF101010)
(setq canvas-vec (make-vector (* W H) BG))
(setq canvas `(image :type canvas :id dp :data-width ,W :data-height ,H :data ,canvas-vec))
(insert (propertize "#" 'display canvas))

We set some constants: gravity, length of rods, and delta time:

(setq g 9.8
      L 60.0
      dt 0.05)

We set some initial conditions for the angles θ 1 subscript 𝜃 1 \theta_{1} and θ 2 subscript 𝜃 2 \theta_{2} ; angular velocities w 1 subscript 𝑤 1 w_{1} and w 2 subscript 𝑤 2 w_{2} , and a fixedd anchor point:

(setq th1 2.5
      th2 2.5
      w1 0.0
      w2 0.0)
(setq pivot-x 125
      pivot-y 80)

We need some helpers, we already have the ball one from before and one for drawing a line/rod and to set a pixel in the vector:

(defun set-px (x y color)
  (when (and (>= x 0) (< x W) (>= y 0) (< y H))
    (aset canvas-vec (+ (* (truncate y) W) (truncate x)) color)))

(defun draw-line (x0 y0 x1 y1 color)
  (let* ((x0 (truncate x0)) (y0 (truncate y0))
         (x1 (truncate x1)) (y1 (truncate y1))
         (dx (abs (- x1 x0))) (dy (abs (- y1 y0)))
         (sx (if (< x0 x1) 1 -1)) (sy (if (< y0 y1) 1 -1))
         (err (- dx dy)) e2)
    (while (not (and (= x0 x1) (= y0 y1)))
      (set-px x0 y0 color)
      (setq e2 (* 2 err))
      (when (> e2 (- dy))
        (setq err (- err dy))
        (setq x0 (+ x0 sx)))
      (when (< e2 dx)
        (setq err (+ err dx))
        (setq y0 (+ y0 sy))))
    (set-px x0 y0 color)))

(defun draw-circ (cx cy r color)
  (let ((r2 (* r r)))
    (dotimes (y (* 2 r)) (dotimes (x (* 2 r))
                           (let* ((px (+ (- cx r) x)) (py (+ (- cy r) y))
                                  (dx (- px cx)) (dy (- py cy)))
                             (when (<= (+ (* dx dx) (* dy dy)) r2)
                               (set-px px py color)))))))

And now the main procedure that takes care of the pendulum movment:

(defun pendulum-physics ()
  (let* ((dth (- th1 th2))
         (den (- 3.0 (cos (* 2.0 dth))))
         (num1 (+ (* (- g) 3.0 (sin th1))
                  (* (- g) (sin (- th1 (* 2.0 th2))))
                  (* (- 2.0) (sin dth) (+ (* w2 w2 L) (* w1 w1 L (cos dth))))))
         (a1 (/ num1 (* L den)))
         (num2 (* 2.0 (sin dth)
                  (+ (* 2.0 w1 w1 L)
                     (* 2.0 g (cos th1))
                     (* w2 w2 L (cos dth)))))
         (a2 (/ num2 (* L den))))
    (setq w1 (+ w1 (* a1 dt)) w2 (+ w2 (* a2 dt)))
    (setq th1 (+ th1 (* w1 dt)) th2 (+ th2 (* w2 dt)))))

The exercise is left to the reader to make sure the above code follows the math! Specifically compare the two equations we introduced initially with the above procedure. And now the main game loop:

(defun pendulum-loop ()
  (dotimes (i 12) (pendulum-physics))
  (fillarray canvas-vec BG)
  (let* ((x1 (+ pivot-x (* L (sin th1))))
         (y1 (+ pivot-y (* L (cos th1))))
         (x2 (+ x1 (* L (sin th2))))
         (y2 (+ y1 (* L (cos th2)))))
    (draw-line pivot-x pivot-y x1 y1 #xFF888888)
    (draw-line x1 y1 x2 y2 #xFF888888)
    (draw-circ x1 y1 8 #xFF00FF00)
    (draw-circ x2 y2 8 #xFFFF0000)
    (set-px pivot-x pivot-y #xFFFFFFFF))
  (canvas-refresh canvas t))
(setq my-timer (run-with-timer 0 0.05 'pendulum-loop))

And once evaluated, it will look something like this:

One can enable interactivity here by making the mouse be able to drag the pendulum and set the initial positions, this is very much doable by using Emacs’ track-mouse functionality. I am not including it here because it’ll be too complex.

4.3. Points in 3D Space

This demo is borrowed from Alexey Kutepov, aka tsoding. They built a graphics library called olive.c. The following demo is Dots3D example from olive.c. Since this is a dynamic module, all the pixel manipulation math would be happening in C and on the Emacs Lisp side we will take care of creating and displaying the canvas and just calling the dynamic module function in a timer to update the canvas. While the original demo didn’t have any mouse interactivity, we can include it in ours quite easily.

After doing the mandatory int plugin_is_GPL_compatible we define some constants in the dynamic module:

#define WIDTH 960
#define HEIGHT 720
#define BACKGROUND_COLOR 0xFF181818
#define GRID_COUNT 10
#define GRID_PAD (0.5f/GRID_COUNT)
#define GRID_SIZE ((GRID_COUNT - 1)*GRID_PAD)
#define CIRCLE_RADIUS 5
#define Z_START 0.25f

We’ve just inherited them from the original demo’s code. Now we need one helper to draw the actual circles/points in this grid/space. We can just convert into C the code we wrote earlier for drawing circles:

void draw_circle(uint32_t *pixels, int cx, int cy, int r, uint32_t color) {
  int r2 = r * r;
  for (int y = -r; y <= r; ++y) {
    for (int x = -r; x <= r; ++x) {
      if (x*x + y*y <= r2) {
        int px = cx + x; int py = cy + y;
        if (px >= 0 && px < WIDTH && py >= 0 && py < HEIGHT)
          pixels[py * WIDTH + px] = color;
      }
    }
  }
}

Now we need to write the actual module function that will be called from Emacs. Firstly, what should this function’s signature be? Since it’s a module function, it has to be like this:

static emacs_value render(emacs_env* env, ptrdiff_t nargs, emacs_value args[], void* data)

But what should be its arguments when called? Well, firstly it needs the canvas where it will render to. It will also need as arguments the angles from which to render the whole grid, because the grid must be rotating, so the angles will be changing continuously. So let’s get those arguments, and get access to the canvas’ pixel buffer as well:

static emacs_value render(emacs_env* env, ptrdiff_t nargs, emacs_value args[], void* data) {
    emacs_value canvas = args[0];
    float angle_x = env->extract_float(env, args[1]);
    float angle_y = env->extract_float(env, args[2]);

    uint32_t* pixels = env->canvas_data(env, canvas);
    if (!pixels) return Qnil;
}

Now the background must be painted, for which we’ll just loop through all the pixels and set them to BACKGROUND_COLOR. We’ll also get some float valuees for the upcoming math. We clearly need some camera math to make it move as well, and since we are in 3D we’ll need a 3-level nested for loop. So here’s what the final function looks like:

static emacs_value render(emacs_env* env, ptrdiff_t nargs, emacs_value args[], void* data) {
    emacs_value canvas = args[0];
    float angle_x = env->extract_float(env, args[1]);
    float angle_y = env->extract_float(env, args[2]);

    uint32_t* pixels = env->canvas_data(env, canvas);
    if (!pixels) return Qnil;

    for(int i = 0; i < WIDTH * HEIGHT; ++i) pixels[i] = BACKGROUND_COLOR;

    float cos_x = cosf(angle_x), sin_x = sinf(angle_x);
    float cos_y = cosf(angle_y), sin_y = sinf(angle_y);

    float camera_distance = 0.8f;  // How far the camera is from the center (Lower = closer)
    float focal_length = 800.0f;   // The "zoom" multiplier (Higher = more zoomed in)

    for (int ix = 0; ix < GRID_COUNT; ++ix) {
        for (int iy = 0; iy < GRID_COUNT; ++iy) {
            for (int iz = 0; iz < GRID_COUNT; ++iz) {
                float x = ix*GRID_PAD - GRID_SIZE/2.0f;
                float y = iy*GRID_PAD - GRID_SIZE/2.0f;
                float z = Z_START + iz*GRID_PAD;

                float px = x;
                float py = y;
                float pz = z - (Z_START + GRID_SIZE/2.0f);

                // 1. Rotate around Y axis (Yaw)
                float x1 = px * cos_y + pz * sin_y;
                float z1 = -px * sin_y + pz * cos_y;
                float y1 = py;

                // 2. Rotate around X axis (Pitch)
                float y2 = y1 * cos_x - z1 * sin_x;
                float z2 = y1 * sin_x + z1 * cos_x;
                float x2 = x1;

                // 3. Apply camera distance
                float z_cam = z2 + camera_distance;

                // 4. Perspective projection using focal length
                float screen_x = (x2 / z_cam) * focal_length + WIDTH / 2.0f;
                float screen_y = (y2 / z_cam) * focal_length + HEIGHT / 2.0f;

                uint32_t r = ix*255/GRID_COUNT;
                uint32_t g = iy*255/GRID_COUNT;
                uint32_t b = iz*255/GRID_COUNT;
                uint32_t color = 0xFF000000 | (r << 16) | (g << 8) | b;

                draw_circle(pixels, (int)screen_x, (int)screen_y, CIRCLE_RADIUS, color);
            }
        }
    }

    return Qnil;
}

After this we can just initialize the module:

int emacs_module_init(struct emacs_runtime *rt) {
    if ((size_t)rt->size < sizeof (*rt)) return 1;
    emacs_env* env = rt->get_environment(rt);
    if ((size_t)env->size < sizeof (*env)) return 2;
    Qnil = env->make_global_ref(env, env->intern(env, "nil"));

    env->funcall(env, env->intern(env, "defalias"), 2,
                 (emacs_value[]){
                     env->intern(env, "dots3d-render"),
                     env->make_function(env, 3, 3, render, "Render dots3d", 0)
                 });
    return 0;
}

This now needs to be compiled into a shared object, do not forget to add emacs-module.h wherever gcc looks for includes:

gcc -O2 -I%ssrc dots3d.c -o /tmp/dots3d.so -fPIC -shared -lm

Now we can use this from Emacs Lisp, some preliminary stuff:

(module-load "/tmp/dots3d.so")
(declare-function dots3d-render "ext:dots3d.c")

(switch-to-buffer (get-buffer-create "*dots3d*"))

(defvar dots3d-canvas)
(defvar dots3d-frame 0)
(defvar dots3d-time 0.0)
(defvar dots3d-last-time 0.0)

(defvar dots3d-angle-x 0.0)
(defvar dots3d-angle-y 0.0)
(defvar dots3d-auto-rotate t)

(setq dots3d-time (float-time))
(setq dots3d-last-time (float-time))

We setup the main canvas:

(setq dots3d-canvas '(image :type canvas
                            :data-width 960
                            :data-height 720
                            :margin (20 . 20)
                            :scale 1
                            :id dots3d))

We need to disable the mode-line and cursor:

(setq-local cursor-type nil
            mode-line-position nil
            mode-line-modified nil
            mode-line-mule-info nil
            mode-line-remote nil)

And we display the canvas:

(insert (propertize "#" 'display dots3d-canvas))

Now the main function that takes care of interacting with the space. We basically use track-mouse to update the canvas by calling the render function with new angles, every time we drag on the canvas. To be noted, we need to stop the auto-rotate while we are dragging.

(defun dots3d-start-drag (event)
  (interactive "e")
  (setq dots3d-auto-rotate nil)
  (let* ((start-pos (posn-object-x-y (event-start event)))
         (last-x (car start-pos))
         (last-y (cdr start-pos)))
    (when (and last-x last-y)
      (track-mouse
        (let (evt pos mx my)
          (while (progn
                   (setq evt (read-event))
                   (mouse-movement-p evt))
            (setq pos (posn-object-x-y (event-start evt)))
            (when (and (car pos) (cdr pos))
              (setq mx (car pos) my (cdr pos))
              (setq dots3d-angle-y (+ dots3d-angle-y (* (- mx last-x) 0.01)))
              (setq dots3d-angle-x (+ dots3d-angle-x (* (- my last-y) 0.01)))
              (setq last-x mx last-y my)
              (dots3d-render dots3d-canvas dots3d-angle-x dots3d-angle-y)
              (canvas-refresh dots3d-canvas))))))
    (setq dots3d-auto-rotate t)))
(local-set-key [down-mouse-1] 'dots3d-start-drag)

And the final render function in Emacs Lisp can be extremely simple, you just call the render function with slightly adjusted angles and we call it in a timer so that it keeps rotating:

(defun dots3d-update ()
    (let* ((time (float-time))
           (dt (- time dots3d-last-time)))
      (switch-to-buffer (get-buffer-create "*dots3d*"))
      (setq dots3d-last-time time)
      (when dots3d-auto-rotate
        (setq dots3d-angle-y (+ dots3d-angle-y (* dt 0.5))))
      (dots3d-render dots3d-canvas dots3d-angle-x dots3d-angle-y)
      (canvas-refresh dots3d-canvas)))
(run-with-timer nil (/ 1 60.0) 'dots3d-update)

And after evaluation, you should see as below and be able to move the grid by dragging it:

5. Conclusion & Further Work

As is evident, this feature has enabled a new universe of graphical capabilities within Emacs. Of such possibilities, we have only scratched the surface in a few places. We are very excited to see what kinds of applications hackers can build using this. Here are some possible applications that can be built on top of canvas in Emacs (some of them we might work on ourselves):

  • A full-fledged document reader in Emacs. (Emacs Reader, the project which started it all.)
  • A full-fledged video player
  • A drawing pad (screw M$ Paint)
  • A framework to make video games
  • Scientific plotting (integration with Calc)
  • Org babel integrations
  • Rendering LaTeX snippets directly.
  • A nice color picker like HTML’s color attribute
  • 2D & 3D Graph viewer
  • Image & Video editors
  • A visual programming environment

And more… we’ve opened a discussion on such graphical applications within Emacs and how to make them composable, feel free to contribute there.

-1:-- An Introduction to Canvas in GNU Emacs (Post Monadic Sheep)--L0--C0--2026-08-23T00:00:00.000Z

James Cherti: Emacs startup: Why setq beats setopt, customize-set-variable, and use-package :custom?

Every millisecond counts during Emacs initialization. A potential performance cost in modern Emacs configurations is defaulting to setopt, customize-set-variable, or the use-package :custom keyword when a direct assignment with setq is sufficient.

Disclaimer: This article assumes you have a solid understanding of Emacs Lisp and know what you are doing when switching to setq. For users who prefer a hands-off experience, defaulting to setopt or the built-in customization interface is often better for convenience, even though these options are not the fastest. Using setq is mainly intended for users who want to optimize startup performance. Always check the Elisp definition of each defcustom to ensure that switching to setq does not introduce unintended side effects.

What is the difference between setq and other ways to assign values to variables?

setq (and setq-default)

The setq and setq-default functions directly assigns a value to a variable, making it the fastest way to set a variable. (FYI: The following projects prioritize using setq and setq-default: minimal-emacs.d, Doom Emacs, Protesilaos Emacs config, and Spacemacs.)

(The setq function updates a variable's value within the current scope; if the target variable is buffer-local, the assignment only affects the current active buffer while the global state remains unchanged. Conversely, setq-default explicitly modifies the global default value of a variable. Although both functions produce the same result for purely global variables, setq-default must be used to apply system-wide changes to buffer-local variables like tab-width or indent-tabs-mode.)

setopt, customize-set-variable, and use-package :custom

The setopt, customize-set-variable, and use-package :custom functions can be slower than setq. Depending on the variable, they can:

  • Call the variable's defcustom :set function.
  • Validate the value against the variable's declared type.
  • In some cases, load the library defining an autoloaded customizable variable, potentially causing a package to load earlier during startup.

Example of an expansive defcustom :set property

To understand the latency penalty, look at how a package author might write a defcustom with an expensive :set property:

(defcustom my-global-visual-indicator t
  "Toggle a heavy visual indicator across all open buffers."
  :type 'boolean
  :group 'my-ui-package
  :set (lambda (symbol value)
         ;; Update the variable's value
         (set-default symbol value)
         
         ;; The slow part: Iterate through every open buffer
         ;; and trigger a costly visual update or cache rebuild.
         (dolist (buffer (buffer-list))
           (with-current-buffer buffer
             ;; This simulated function might parse the buffer,
             ;; apply text properties, or query a language server.
             (my-heavy-visual-update-function value)))
             
         ;; Force Emacs to immediately redraw all frames
         (redraw-display)))

Using setopt, customize-set-variable, or the use-package :custom to configure my-global-visual-indicator uses executes the associated defcustom :set function during startup:

;; Example 1: SLOW: This triggers the expensive :set function during startup
(setopt my-global-visual-indicator nil)

;; Example 2: This triggers the expensive :set function
(use-package my-ui-package
  :commands (my-ai-package-cmd1 my-ai-package-cmd2) ; defer
  :custom
  (my-global-visual-indicator nil))

In many cases, the Emacs startup phase is simply not the appropriate time to execute those :set functions.

On the other hand, if the variable does not require its Custom setter to establish associated state, using setq inside a use-package :init block bypasses the Emacs Customization setter entirely:

;; Example 1: FAST: This bypasses the :set function for a faster startup
(setq my-global-visual-indicator nil)

;; Example 2: FAST: This bypasses the :set function
(use-package my-ui-package
  :commands (my-ai-package-cmd1 my-ai-package-cmd2) ; defer
  :init
  (setq my-global-visual-indicator nil))

Note: Code placed in the :init block executes before the package loads into memory. Setting variables here ensures your customized values are already established in the global environment before the package evaluates its internal definitions, bypassing the need for a defcustom setter to update its internal state.

Trade-offs when using setq

Here are the trade-offs to consider when using setq:

  • During startup, a direct assignment can avoid UI updates or other side effects performed by a Custom setter. However, setq should only be used when the package does not require that setter to establish associated state.
  • setq does not validate types. If you pass a string where a boolean is expected, Emacs will not warn you at evaluation time.

Rule of thumb

Use setopt, customize-set-variable, or use-package :custom when a variable's Custom :set setter or type validation is required, or if you notice that the option is not being applied as intended. Otherwise, use setq by default.

(To determine whether a specific variable requires a custom :set setter for correct state initialization, inspect its properties directly within Emacs. Execute M-x describe-variable or C-h v and provide the variable's name to view its documentation. In the resulting help buffer, look for a :set attribute. While the presence of a :set function indicates that the package defines initialization logic for when the variable is modified, it does not necessarily mean that using setopt or use-package :custom is required. The necessity depends on the specific implementation, as the majority of major and minor modes automatically execute these initialization functions by default when the mode is activated.)

-1:-- Emacs startup: Why setq beats setopt, customize-set-variable, and use-package :custom? (Post James Cherti)--L0--C0--2026-08-22T16:32:15.000Z

Irreal: HTML Preview For Org Mode

Randy Ridenour has an interesting post about previewing an Org file in your browser. If you’re like me your first thought is probably, “That’s easy. Just export the Org file to HTML and display it right from the Org export dispatcher.” I do exactly that to preview my blog posts. I simply type Ctrl+c Ctrl+e h o and my post appears in my browser. Of course, it doesn’t look exactly like the final product because it uses the default Org CSS instead of the WordPress theme that you see on the Irreal site.

And that’s exactly the point. Ridenour has his own opinion on how his markup should be formatted. Five years ago, I wrote about how to to add your own CSS formatting to an Org file when you export it to HTML but those methods all required adding some text to the Org file. Ridenour’s method does all that in a function that he can call to export his Org file to the browser. The process is pretty much the same as using the export dispatcher except that he gets to have his preferred CSS applied to the output.

The other advantage of his method is the he arranges for the automatic removal of the generated HTML file. The export dispatcher method requires that you delete the files manually. I do that periodically using Embark, which makes it easy but I still have to remember to do it. If you’re looking for a fast and easy way to preview an Org file in your browser, take a look at Ridenour’s post.

-1:-- HTML Preview For Org Mode (Post Irreal)--L0--C0--2026-08-22T16:02:37.000Z

Emacs APAC: Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, August 22, 2026

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

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

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

-1:-- Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, August 22, 2026 (Post Emacs APAC)--L0--C0--2026-08-22T05:06:06.000Z

Matt Maguire: AI Agents Take Over Lesson Prep

It has been a while since I have written about how I use Emacs/LaTeX to generate lesson notes for my students. Quite a lot has happened during that time. One of the main game changers has been the advancements in “AI” technology. Over the next few articles I intend to describe my journey as I navigate this technology and apply it to my teaching practice.

Chat Bots

Having decided to dip my toe in the water, I signed up to two of the prevalent players in this space, being Google Gemini and Anthropic Claude. My early experiments with ChatGPT had led to some disappointing results, where it emphatically tried to convince me that the value of Euler’s constant is 1.5. Since then, the technology has advanced significantly, and I thought it worthwhile to try again.

-1:-- AI Agents Take Over Lesson Prep (Post Matt Maguire)--L0--C0--2026-08-22T00:00:00.000Z

Donovan R.: 💭 I Smell Emacs and Smalltalk on DSH (DeepSeek Harness)

Last week, Deepseek released DSH, an agent harness with very interesting approach.
The same pitch used by VS Code and Eclipse RPC (2024): “everything is a plugin” but this time it’s a bit different. Even the agent loop itself is a plugin.

In practice, you extend the tool by just prompting. (Reminds me of an old post of mine where I said that Emacs is like a modern “Magic Lamp”)
It allows maximum customizability and also opens many possibilities.
In the official article, they showcased adding snake game to the main interface. Pretty cool!

Around the same time, I spent a little time playing with the pi-coding-agent on Emacs and was thinking that now if I want, I probably could extend Emacs indefinitely by just prompting.

Weird synchronicity.

Anyway, here are some useful links if you want to know more:

-1:-- 💭 I Smell Emacs and Smalltalk on DSH (DeepSeek Harness) (Post Donovan R.)--L0--C0--2026-08-20T17:37:36.000Z

Irreal: Setup For The Casual Suite

Charles Choi has announced the release of Casual 3.0. There are now 26 modules in the Casual suite but this release is about providing an easier setup for the suite. Prior to this release, each module had to be set up separately. As Choi says, that made sense when there were just a few modules but it has become increasingly burdensome as the number of modules grew.

The new procedure is simply to call casual-init and all the module’s you want to use are initialized. The modules you’re using is determined by a set of hook functions that can configured with the Emacs Customize utility. You simple check off which modules you want to use and call casual-init. That’s all there is to it. You can learn more about this in the Casual User Manual.

If you’ve been following along on Choi’s blog—or to a lessor extent on Irreal—you know that there are a lot of different types of modules, not all of which you might want installed. The Customize interface is an easy way to specify which ones you want.

The latest version is available on MELPA and NonGNU ELPA. The nice thing about the modules is that they are all invoked with the same key sequence, Ctrl+o by default, so you don’t have to remember a bunch of different bindings. Once you invoke it, Casual brings up a Transient menu appropriate for the current major mode

Casual is a nice suite that make it easier to navigate Emacs applications that you don’t use often and for which you haven’t internalized all the bindings. Take a look at the user manual if you want to know more.

-1:-- Setup For The Casual Suite (Post Irreal)--L0--C0--2026-08-20T14:46:27.000Z

Thanos Apollo: My Emacs Agentic Workflow

Two years ago I was a skeptic about GenAI. Now Emacs has become my agent cockpit.

Harness

My favorite harness right now is hermes-agent, which is probably the “emacs of agent harnesses” with how easy it is to configure and extend, while not being “just an agent harness.” Hermes supports messaging gateways, so you have first-class support for mobile as well. All you have to do is /handoff <platform> to continue your session via a mobile messaging app.

To interact with hermes via emacs, I’m using emacs-hermes, which is available via NonGNU ELPA.

Sometimes I also use codex via emacs-codex-ide. But it’s not as token-efficient as my hermes setup, and it’s not where I am most productive. Unlike most harnesses, hermes supports codex native compaction, which is the “killer” feature of codex.

Models

I currently use gpt-5.6-sol for most tasks through my codex subscription. It is exceptionally good at long-running goals. It can easily keep going for 20h+ trying to complete its task, and I have found the quality of its work more than adequate.

TIP: to save tokens with hermes, adjust the auxiliary.approval model to a cheap open-weight one, such as deepseek-v4-flash, which is practically free on nous portal. This has cut my usage by more than 35%.

I’m also surprised by the recent leap x.ai made with Grok, which is currently the best value-for-money model by far. The benchmarks place it close to gpt-5.6-sol and fable.

Figure 1: Artificial Analysis Intelligence Index

Figure 1: Artificial Analysis Intelligence Index

It’s currently my fallback model when I reach my usage limit on codex. It’s the fastest “frontier” model I’ve tried.

I avoid using models from Anthropic. Models like opus-5 and fable are good for coding tasks, but I found them subpar for my studies even compared to grok-4.6, plus overpriced. The API prices are insane, and their subscription only supports their proprietary harness.

What about open models?

A friend and I tried to self-host our “AI Homelab”, repurposing an old ethereum mining rig plus a couple of new Nvidia GPUs. The performance was not ideal with models like GLM-5.2 compared to subscriptions by a long shot. Forget subagent workflows if you ever attempt it. Not to mention that my electricity bill was the same as I’d pay for a subscription plan.

A lot of people in the “GNU/Linux” sphere are looking for “free” models. Even if there were a 6T+ “free” model that outperforms frontiers, there is no “free hardware” that could run it, and even if there were a FOSS Nvidia alternative, would it be financially worth it for most people as an investment? Would your average hobbyist rather pay $20/month or buy the next gen “free” DGX Spark for $5,000?

Extra goodies & Skills

Memory

No matter your setup, the biggest upgrade for me has been hindsight. It’s an agent memory system that you can share across harnesses, and it helps your agents remember your preferences and workflow details.

It’s free and easy to self-host.

Skills

Skills are where my setup stops being generic. Instead of explaining the same preferences and workflow every session, I put them in SKILL.md files that Hermes loads depending on the task. I have skills for how I want elisp written and reviewed, and even for drafts like this post.

Everyone takes a different approach, from GSD/BMAD/Spec-driven workflows that automate most of the work to “real engineering” skills that want you to babysit the agent. I prefer smaller skills I can mix together instead of one framework owning my whole workflow.

If you are just starting out, browse skills.sh for anything that “looks cool”, experiment with it, then start writing your own. The two I recommend to most people are unslop and grill-me.

Other tools and environments

As of now, the only other tools I’d recommend are web tools like firecrawl and searxng.

Current state of my “AI Psychosis”

The past year the value I have gained from GenAI tools is insane. I had not dreamed, even in my wildest dreams, that I would have such tools in my lifetime.

Tasks that used to bottleneck my workflow, such as adjusting a software tool to help me study, are now cheap to solve. Software is more of a commodity now, no longer a moat. Anything you like can be built easily. You can see that even in projects like hermes, which copied the “Grok Bot” “killer-feature” in less than 24h and made it open source in their harness.

Grunt work like notes for my syllabus based on my university lecture slides used to take me weeks. Now it’s just a /goal prompt, go for a walk for 2h, and come back and they are ready in the same exact format the rest of my notes already have. All that is left is for me to study them.

We live in exciting times.

-1:-- My Emacs Agentic Workflow (Post Thanos Apollo)--L0--C0--2026-08-20T00:00:00.000Z

Irreal: Switching To A Window In Another Frame

Álvaro Ramírez, author of Journelly and many other useful applications—a lot of them Emacs based—has a new post about a basic Emacs operation: changing focus to another window. Ramírez and I basically agree about things like this although he stubbornly refuses to admit the superiority of my method of indicating the window that has focus.

Now he has a slightly different problem. For technical reasons that you can read about in his post, he wants to be able to switch to a window in another frame. The other-window command, bound to Ctrl+x o by default, doesn’t consider windows in other frames but, of course, there’s a command for that: next-window-any-frame. That turned out to be exactly what Ramírez needed and he simply bound it to Meta+o, which is where he had other-window bound.

Like Ramírez, I seldom use more than one frame so I didn’t know about this command. If you often have more than one frame going, you may find it useful if you’re not already using it. If you’re an ace-window user, it will consider all frames by default but you can adjust this with aw-scope.

-1:-- Switching To A Window In Another Frame (Post Irreal)--L0--C0--2026-08-19T16:11:32.000Z

Charles Choi: Simplifying Setup for Casual

Since its inception, Casual required individual setup for each module it supported. This seemed fine at the start when the module count was low. Now weighing in at 26 modules, there is admittedly a lot of setup.

Not anymore. Starting with the latest Casual v3.0 update, there is now a convenience command to setup all Casual modules: casual-init.

Use the following Elisp to replace all the previous setup code:

1
2
(require 'casual)
(casual-init)

You can configure which modules to setup via the customizable hook variable casual-init-hook, a list of hook functions each associated with a module. Having a module hook function present in casual-init-hook tells casual-init to setup that module.

Editing casual-init-hook is straightforward using the customize checkbox interface shown below.

img

More information about installation and setup of Casual can be found in the Casual User Guide.

Bumping to 3.0

Because the recommended setup has significantly changed, I’ve decided to bump the major version to 3.0.

That said, there are no functional changes to Casual and all the prior individual setup still should work without change. For existing users of Casual, I highly recommend migrating to casual-init as it allows for removing individual setup code.

A significant amount of manual testing was required to verify lazy-loading behavior in casual-init. While best effort was made to avoid unpleasant surprise, if something shows up, please file an issue to let me know.

Casual v3.0 update is now available on MELPA and NonGNU ELPA.

It costs money to make, enhance, and maintain Casual as ideologically free software. If you enjoy using Casual, please consider buying me a coffee to help support its development and maintenance. Thank you!

-1:-- Simplifying Setup for Casual (Post Charles Choi)--L0--C0--2026-08-18T16:20:00.000Z

Irreal: Emacs 31.1 RC1

Great news! Sean Whitton writes to announce the release of Emacs 31.1 RC1. This is the final step before the formal release of the Emacs 31 series. Whitton remarks that the developers are hoping that RC1 will be the only release candidate. I take that as a measure of their confidence in this trial release. Unless any breaking problems pop up, we can expect the official release of 31.1 imminently.

As always, we should offer up a word of thanks for the hard work of the maintainers and all the developers who have toiled so hard to bring us this latest release. If we find ourselves in the same bar as one of them, the drinks are on us.

-1:-- Emacs 31.1 RC1 (Post Irreal)--L0--C0--2026-08-18T15:05:43.000Z

Randy Ridenour: HTML Preview for Org Files

I’ve long been a fan of Brett Terpstra’s Marked for easily reading Markdown files. Over the years, he’s added more and more features. He’s recently released Marked 3 which, if you need those features, is well worth the purchase price of $2.99/month subscription or a one-time purchase of $69.99. There are only two small things keeping me from upgrading:

  1. I don’t need any of those features — in fact, since everything I write begins as an Org mode file, I don’t really need a Markdown previewer.
  2. I teach philosophy at a small liberal arts college in a rapidly collapsing market.

So, I don’t need a Markdown previewer, but an Org mode previewer would be nice. I guess I could have Claude code a Mac app like Marked for Org files, but since Org exports to HTML, it shouldn’t be that difficult to export the HTML to a temporary file, style it with some CSS, open it in a browser, then have it automatically deleted when no longer needed.

Here is the export function and the CSS for styling the output.

(defun rlr/org-export-html-to-browser ()
  "Export the current Org buffer to a temporary HTML file in the system temp directory, open it, and then delete it when Emacs is killed."
  (interactive)
  (unless (derived-mode-p 'org-mode)
    (user-error "Not in an Org buffer"))
  (let* ((tmp (make-temp-file "org-preview-" nil ".html"))
         (org-export-in-background nil)
         (org-html-head-include-default-style nil)
         (org-html-head-include-scripts nil)
         (org-html-head rlr/org-preview-css)
         (org-html-validation-link nil))
    (org-export-to-file 'html tmp)
    (add-hook 'kill-emacs-hook
              (lambda () (ignore-errors (delete-file tmp))))
    (browse-url-of-file tmp)))
(defvar rlr/org-preview-css "
<style>
  :root { color-scheme: light dark; }
  body {
    max-width: 40rem;
    margin: 3rem auto;
    padding: 0 1.25rem;
    font-family: -apple-system, BlinkMacSystemFont, 'Iowan Old Style','Palatino Linotype', Georgia, serif;
    font-size: 1.125rem;
    line-height: 1.65;
    color: #24292f;
    background: #fdfdfc;
    text-rendering: optimizeLegibility;
    hyphens: auto;
  }
  h1, h2, h3, h4 {
    font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
    line-height: 1.25;
    margin-top: 2.25em;
    color: #14181c;
  }
  h1 { font-size: 1.9rem; margin-bottom: 0.2em; }
  h2 { font-size: 1.45rem; }
  h3 { font-size: 1.2rem; }
  .title { text-align: left; margin-bottom: 2rem; }
  #table-of-contents { font-size: 0.95rem; }
  a { color: #0b5cad; text-underline-offset: 2px; }
  blockquote {
    margin: 1.5em 0; padding: 0.25em 0 0.25em 1.25em;
    border-left: 3px solid #d0d7de; color: #4a5058; font-style: italic;
  }
  pre, code { font-family: 'SF Mono', Menlo, Consolas, monospace; font-size: 0.9rem; }
  pre {
    background: #f4f4f2; padding: 0.9em 1.1em;
    border-radius: 6px; overflow-x: auto; line-height: 1.45;
  }
  code { background: #f0f0ee; padding: 0.1em 0.3em; border-radius: 3px; }
  pre code { background: none; padding: 0; }
  table { border-collapse: collapse; margin: 1.5em 0; font-size: 0.95rem; }
  th, td { border: 1px solid #d0d7de; padding: 0.4em 0.7em; text-align: left; }
  th { background: #f4f4f2; }
  hr { border: none; border-top: 1px solid #d8d8d4; margin: 2.5em 0; }
  .footpara { display: inline; }
  #postamble { margin-top: 3rem; font-size: 0.85rem; color: #6a707a; }
  @media (prefers-color-scheme: dark) {
    body { color: #d6d3cd; background: #16181a; }
    h1, h2, h3, h4 { color: #f0ede8; }
    a { color: #6cb3f5; }
    blockquote { border-left-color: #3a3f45; color: #a8a49d; }
    pre { background: #1e2124; }
    code { background: #24282c; }
    th, td { border-color: #3a3f45; }
    th { background: #1e2124; }
    hr { border-top-color: #2e3236; }
  }
</style>"
  "Stylesheet injected into throwaway HTML previews of Org buffers.")

Tagged: Emacs Org

-1:-- HTML Preview for Org Files (Post Randy Ridenour)--L0--C0--2026-08-17T23:49:00.000Z

Sacha Chua: 2026-08-17 Emacs news

The canvas patch has been merged upstream, very cool! I'm looking forward to seeing the kinds of experiments and tools this enables. Also, there's a release candidate for the next version of Emacs, please go try it out!

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-08-17 Emacs news (Post Sacha Chua)--L0--C0--2026-08-17T20:41:53.000Z

TAONAW - Emacs and Org Mode: RSI and Emacs: When Your Pinky Says Enough is Enough

I’ve been doing so much Emacs writing lately, so I guess it’s time I get my Emacs pinky (which is a form of RSI). I made a quick reference to it three days ago and got a lot of responses.

What happened?

I started to feel a strain last week, but at first I didn’t think much about it. Sometimes my pinky rests awkwardly on the keyboard because I’m used to using the Caps Lock key, but it’s usually a hand-position issue. After I readjust and let my pinky rest, it goes away.

This time the strain didn’t go away, and my pinky kept rebelling.

My keyboard, Kinesis Freestyle Edge (the one here is the RGB+, the original RGB is discontinued), has a prominent Caps Lock key that just screams “use me as control!” For years, that’s what I did. As you can see in the picture, with my pinky resting on it.

A hand is positioned on the left side of a  Kinesis Gaming mechanical keyboard with blue backlit keys.

The last week has been busy. I didn’t leave my small Manhattan apartment for two days, working straight from morning to evening, then gaming and sleeping. This isn’t normal for me and has taken a toll on my hands, and the RSI got worse.

I think my new screen also exacerbated the problem somehow. It’s much bigger and has higher resolution, which I’m still adjusting to by how I sit and how I move my head and neck. I also have a standing desk I usually use in its standing position in the morning, when I write these posts or work on projects — these are easily my longest writing sessions, sometimes going for two hours straight without a break, especially in the last couple of weeks, and that’s all Emacs and pinky work.

When I thought about Emacs pinky in the past, I mostly thought of it with a smirk. I mean, it’s your pinky. How can you not read a comment about someone having their little finger hurt and not chuckle a bit? But folks, let me tell you, once you have it, you realize how much you depend on that one small finger, especially as an Emacs user.

My work, my organization, even my gaming in the evening (as it turns out), it’s all going through that little finger. Sure, it’s my “fault” for using Emacs, but if you’ve been reading this blog for a while you’d know that I owe many things to Emacs (including, among other things, my current job).

What am I doing about it?

For now, I’ve started using the keyboard’s dedicated control key again and changed how I use my hands. I try to use my right thumb, all the way to the side from the home row (my keyboard is huge, I will get to that in a minute) for when I need to use the left side of the keyboard, and then fold my pinky and use my knuckle to press it when I need the left control, for when I use the right side of the keyboard, as you can see in this picture:

A hand is resting the keys X and A with the little finger curled over the Ctrl key on a backlit keyboard with blue lighting.

This adjustment is the easiest, since I know where these Control keys are by muscle memory. I turned off my Caps as control on my Mac to make sure I don’t get tempted to go back to my old habit. It’s been working OK, and that’s what I’m doing right now for writing this post.

Other Emacs users have suggested several things, and I’ve been considering and trying a few of them.

god-mode came up a couple of times. It creates a chord (two keys pressed at once) key binding that triggers a control press. For example, sd, and then xs (separately) to save a file in Emacs — same as C-xC-s. This can be useful, but it has two problems.

The obvious one is that this shortcut works only in Emacs. When I don’t use Emacs, which happens more often than I’d like for work, I have to use the OS shortcuts. Caps is so ingrained in my fingers’ memory that I just use it all the time, but now I need to start thinking of other options. I could probably program the keyboard’s firmware to do something similar, but that’s a topic in itself — and if I do that, I don’t need god-mode anyway. Another odd issue is that it doesn’t work on my Mac. It throws a GCC error, which from what I gather has to do with Emacs internals and how it works (or doesn’t work in this case) with its internal compiling mechanisms. Yes, I tried the fix, which doesn’t seem to work well with Homebrew, and I gave up on it.

There are other packages like god-mode, but I think the next one I’m going to try is Xah Fly Keys, because what he’s after seems to make sense to me. Which brings me to the next point: a new keyboard.

New Keyboards

I’ve been considering a new keyboard for a while, and the RSI has sped up the process. I’ve looked at a couple reviews and YouTube videos in the last couple of days. Kinesis, my current keyboard brand, is a known name in the ergonomic keyboard world, but after checking out their Advantage 360 I’m not sure I’m sold. It’s an excellent premium-looking keyboard, but it’s having a huge and heavy keyboard again. My hands are small, and I’m not sure I want to commit to another expensive giant, especially when I already struggle with room for the mouse, especially when the two halves are spread apart for the shoulders. I already compromise on this, and my chest is closer than it should be. I also think that for the price of these keyboards (over $400), I might want to take them with me when I go to the office, and with a Kinesis keyboard, this is not going to happen. Another thing I’m considering is going wireless, because of my USB switch setup. It can take my Linux Desktop or the Mac up to 30 seconds to detect the keyboard when I switch, which is small, but still annoying. The mouse, connected by a Bluetooth dongle, takes a second to switch over.

The above reasons, and Xah’s reviews, landed me on Glove80, which is even a weirder-looking keyboard. This keyboard takes ergonomics further (or just looks crazier, if you’re not into those). It’s wireless and lighter, which means taking it with me makes more sense, and will hopefully fix the USB lag issue. It has keywells for the fingers, which is basically a “dip” or a bowl (hence the name) that curves downwards so your fingers, which are naturally curled on a keyboard, are resting downwards. It has its own super-light switches, which, while I don’t think I need them, are another example of thinking of RSI and finger pain.

Going further with light keyboards, there’s the Voyager, which is tiny, lighter, and minimal. It’s not wireless, but it’s meant to be mobile, and it’s probably the best one for small hands. However, the Voyager only has 52 keys. It’s meant to be used with layers — meaning, there’s a modifier key that creates another “layer” of keys, which makes your Alt act as Ctrl, for example. I’m not sure I’m sold on this idea, especially when we’re talking Emacs. If I’m already challenged by my lack of Caps Lock, I can only imagine how much time I will have to sink into this keyboard with its layers and grid layout (which the Glove80 has too, by the way) to become efficient with it. I also think the Keywell is something I could use, so I have a rough idea of what I want to try next.

The problem with the Glove80 is that you can’t really try it out. They have a 30-day return policy, shipping not included, and they’re in New Zealand. A keyboard like that takes time to get used to, and 30 days for a $ 400-plus keyboard requires a level of confidence in the product I don’t have yet.

My conclusion so far

All in all, I think what I’m looking at is a combination of adjustments rather than one solution. I need to get used to the idea of different key bindings, be it something like god-mode or Xah Fly Keys, which will make it easier to use with a new keyboard and would also be beneficial now, until I get it. The idea of layering is a bit daunting, but I can see the wisdom in it, especially after I got an email back from BSAG, who was kind enough to share her experience with me (she’s the one who told me about the Voyager keyboard).

It will be a while before I buy a new keyboard, then it will take some time to get here, and then it will take me even longer to get used to one. I’m not looking forward to having my typing hindered even further, especially because I’m so dependent on writing notes all the time, but it’s also because of it that I have to do it. This is one thing I know I need to do.

-1:-- RSI and Emacs: When Your Pinky Says Enough is Enough (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-08-17T01:54:19.000Z

Corwin Brust: Emacs Card Games on MELPA

First melpa release evah \o/

Thanks to many for patience and coaching, and card-games.el 1.0.92 (pre-release #2, MELPA) is available from package-install for users of MELPA. Release announcement after the cut..

https://code.bru.st/corwin/card-game.el/media/branch/master/doc/images/klondike.png

Grown from experimentation within Dungeon, thank you to contribs there, and for review comments from ricsy putting toward MELPA got rid of some advices forcing very interesting better ways. The hooks approach makes the registered card-games-bid-submit a generic semaphore, for example to interlink with a process, CRDT via an adaptor, or … in present v1.0.92 (unstable): https://melpa.org/#/card-games

-1:-- Emacs Card Games on MELPA (Post Corwin Brust)--L0--C0--2026-08-16T23:09:44.000Z

Amit Patel: Emacs Tree-sitter custom highlighting, part 3

In the part 2 I described how, for a particular project with certain naming conventions, I wanted to use emacs tree-sitter font-locking to highlight variables that might be used in the wrong context:

Screenshot showing two subscript expressions, one that seems suspect because it's an array indexed by t values that has an index that is named r

I found a way to make it work for simple expressions but it was relying on regular expression matching rather than tree-sitter parse trees. I set a goal of handling these types of expressions:

elevation_t[t]           // good
elevation_t[r]           // bug
elevation_t[t_from_r[r]] // good
elevation_t[r_from_t[t]] // bug
elevation_t[obj.t]       // good
elevation_t[obj.r]       // bug
elevation_t[t_from_r(r)] // good
elevation_t[r_from_t(t)] // bug
elevation_t[x.t_fn(x)]   // good
elevation_t[x.r_fn(x)]   // bug
elevation_t[x.t_arr[i]]  // good
elevation_t[x.r_arr[i]]  // bug
elevation_t[(t)]         // good
elevation_t[(r)]         // bug

What are my options for using tree-sitter to handle more cases? I decided to try the :pred feature of tree-sitter font lock. Using M-x treesit-explore-mode, I learned that the subscript expression looks like:

(subscript_expression 
   object: (_) 
   [
   index: (_)
   ]
)

That means I can match it with a rule that captures both the array and index as separate nodes:

(defun amitp-check-subscript (array index)
  (message "debug: %S %S" array index))

(defvar amitp/treesit-font-lock-typescript-indexing
  (treesit-font-lock-rules
   :language 'typescript
   :feature 'semantic-identifier
   :override t
   `(((subscript_expression
       object: (identifier) @array 
       index: (expression_statement) @index 
       (:pred amitp-check-subscript @array @index))))))

Those two arguments are treesit parse nodes. What can I do with them? The emacs manual has a page about how to navigate the parse tree and another page about querying the parse tree. In the docs found a function treesit-query-capture that can pattern match on a node and extract the parts.

I used that function to look for the identifier that most likely tells me what geometry type is being returned (r for region, s for side, t for triangle). Examples:

  • in t_foo_r(r), the identifier t_foo_r tells me this is likely a t (triangle)
  • in t_obj[i].r_elevation, the identifier r_elevation tells me this is likely an r (region)
  • in map[i].s_inner_t(t_queue[0]), the identifier s_inner_t tells me this is likely an s (side)

I think this is where tree-sitter can do better than regular expression based matching. I wrote a function to find the key identifier:

(defun amitp/treesit-top-typescript-node (node)
  "Try to find the identifier that represents a node's return value"
  (let ((results
         (treesit-query-capture
          node
          '(((identifier) @identifier)
            ((member_expression property: (property_identifier) @property))
            ((subscript_expression object: (_) @expr))
            ((call_expression function: (_) @expr))
            ((parenthesized_expression (_) @expr))))))
    (if (assoc 'expr results)
        (amitp/treesit-top-typescript-node (cdr (assoc 'expr results)))
      ;; prefer the property if tree-sitter matches both
      (or (cdr (assoc 'property results)) (cdr (assoc 'identifier results))))))

I wrote a function detect a mismatch:

(defun amitp-check-subscript-mismatch (array index)
  ;; note: my usual naming convention is amitp/foo but tree-sitter won't accept that
  (let* ((array-name (buffer-substring (treesit-node-start array) (treesit-node-end array)))
         (top (amitp/treesit-top-typescript-node index))
         (index-name (buffer-substring (treesit-node-start top) (treesit-node-end top))))
    (and
     (or (= (length array-name) 1) (equal "_" (substring array-name -2 -1)))
     (or (= (length index-name) 1) (equal "_" (substring index-name 1 2)))
     (not (equal (substring array-name -1) (substring index-name 0 1))))))

Then I used the tree-sitter :pred feature to only highlight if there's a mismatch:

(defface array-error)
(defface index-error)

(defvar amitp/treesit-font-lock-typescript-indexing
  (treesit-font-lock-rules
   :language 'typescript
   :feature 'semantic-identifier
   :override t
   '(((subscript_expression
       object: (_) @array-error
       index: (_) @index-error
       (:pred amitp-check-subscript-mismatch @array-error @index-error))))))
     ;; todo: add call_expression

Does it work? Yes!

Screenshot showing subscript expressions flagged if they seem like they might have a bug

But there's something a little weird here. Why do r+1 and 1+r get flagged even though I never actually handle binary operators in my amitp/treesit-top-typescript-node function? Something to investigate later.

The next feature I wanted was to highlight only the "type" character instead of the entire identifier. I couldn't figure out how, so I asked on Reddit, where /u/eleven_cupfuls gave me a solution: the @face-name can be a @function-name instead. Thank you!

Instead of using :pred to tell font-lock whether to highlight something, I need to highlight it myself. I changed the treesit font lock rules to call my function instead of applying a face:

(defvar amitp/treesit-font-lock-typescript-indexing
  (treesit-font-lock-rules
   :language 'typescript
   :feature 'semantic-identifier
   :override t
   '(((subscript_expression) @amitp-highlight-subscript-mismatch)
     ((call_expression) @amitp-highlight-subscript-mismatch))))

My function now receives the entire expression instead of receiving the array and index parts separately like the :pred version. So I need to find a way to extract the right information. I can use treesit-query-capture like before, right?

It was at this point that I learned treesit-query-capture pattern matches against the entire tree and not only the top node. That explains why it was finding r+1 and 1+r. It was looking for any identifier anywhere in the subtree. Oops.

I rewrote amitp/treesit-top-typescript-node to use treesit-node-type instead of treesit-query-capture:

(defun amitp/treesit-top-typescript-node (node)
  "Try to find the identifier that represents a node's return value"
  (pcase (treesit-node-type node)
    ("identifier" node)
    ("member_expression" (treesit-node-child-by-field-name node "property"))
    ("subscript_expression" (amitp/treesit-top-typescript-node (treesit-node-child-by-field-name node "object")))
    ("call_expression" (amitp/treesit-top-typescript-node (treesit-node-child-by-field-name node "function")))
    ("parenthesized_expression" (amitp/treesit-top-typescript-node (treesit-node-child node 1)))
    (_ nil)))

It would've been cleaner if pcase could pattern match against these nodes, but it's not too bad.

And then I wrote a new amitp-highlight-subscript-mismatch function to analyze subscript nodes (and function calls too) to make sure the names match:

(defun amitp-highlight-subscript-mismatch (node override start end &rest _)
  "Highlight mismatching variable names in amitp's geometry mesh code"
  (let* ((parts (pcase (treesit-node-type node)
                  ("subscript_expression" (list (treesit-node-child-by-field-name node "object")
                                               (treesit-node-child-by-field-name node "index")))
                  ("call_expression" (list (treesit-node-child-by-field-name node "function")
                                           (treesit-node-child
                                            (treesit-node-child-by-field-name node "arguments")
                                            1))))))
    (when parts
      (let* ((array (nth 0 parts))
             (index (nth 1 parts))
             (array-name (treesit-node-text array))
             (top (amitp/treesit-top-typescript-node index))
             (index-name (or (treesit-node-text array) ""))
             (face (if (and ;; todo also check (>= (length index-name) 1)
                        (or (= (length array-name) 1) (equal "_" (substring array-name -2 -1)))
                        (or (= (length index-name) 1) (s-match "[_0-9]" (substring index-name 1 2))))
                       (if (equal (substring array-name -1) (substring index-name 0 1))
                           'index-good
                         'index-bad)
                     nil)))
        (when face
          (treesit-fontify-with-override (- (treesit-node-end array) 2) (treesit-node-end array) face override)
          (treesit-fontify-with-override (treesit-node-start top) (min (treesit-node-end top) (+ 2 (treesit-node-start top))) face override))))))

It's a little bit long but it seems to work:

Screenshot showing subscript expressions in green or red depending on whether the names match

However, there are things I haven't figured out, like why certain expressions don't trigger the (subscript_expression) font lock rule:

r => mesh.foo_r[r]);     // matches
(r => mesh.foo_r[r]);    // matches
foo(mesh.foo_r[r]);      // matches
foo(r => mesh.foo_r[r]); // doesn't match

That's a problem for another day. I also want to:

  1. apply these rules only to files in which I use that naming convention
  2. tighten the matching to apply only to the letters r, s, t
  3. remove the green "it's ok" color and only leave the red "warning" color

This was a fun adventure. I'm not sure this is the best approach but it was a nice excuse to play with emacs tree-sitter.

-1:-- Emacs Tree-sitter custom highlighting, part 3 (Post Amit Patel)--L0--C0--2026-08-16T21:53:52.812Z

Amit Patel: Emacs Tree-sitter custom highlighting, part 2

In the part 1 I described my initial attempt at customizing font-lock using Emacs 29 tree-sitter. Regular font locking uses regular expression matching. Tree sitter allows combining regular expression matching with parse tree matching, like "variable declaration" or "function call". The ideas I came up with were mostly things that I could have implemented with regular expressions alone, but I wanted to experiment with the tree sitter approach. Here's the effect of conventional highlighting, using color for keywords and other syntax:

Screenshot showing no use of color Screenshot showing color for syntax
Screenshots showing the use of color for syntax highlighting

I wanted to instead mark variable names. In some projects I'm working with geometry, and I sometimes make mistakes putting the wrong variables together. I decided to mark horizontal and vertical variables differently. First, I defined a regular expression to guess that a variable represents something "horizontal":

(defvar amitp/regexp-horizontal-identifiers
  (rx
   (seq
    string-start
    (or "x" "dx" "width" "left" "right" "q" "col" "cols" "columns")
    (zero-or-more digit)
    string-end)))

(defvar amitp/regexp-vertical-identifiers
  (rx
   (seq
    string-start
    (or "y" "dy" "height" "top" "bottom" "up" "down" "r" "row" "rows")
    (zero-or-more digit)
    string-end)))

I had started out with a string regexp but switched to the rx syntax. Over time I added more patterns to this, including all caps and camelcase patterns.

Unlike regular expression font locking, I can limit these regular expressions to match within certain types of parse tree nodes:

(defvar amitp/treesit-font-lock-typescript-axis-variables
  (treesit-font-lock-rules
   :language 'typescript
   :feature 'semantic-identifier
   :override t
   `(
     ([(identifier) (property_identifier) (shorthand_property_identifier_pattern)]
      @variable-horizontal-face
      (:match ,amitp/regexp-horizontal-identifiers @variable-horizontal-face))
     ([(identifier) (property_identifier) (shorthand_property_identifier_pattern)]
      @variable-vertical-face
      (:match ,amitp/regexp-vertical-identifiers @variable-vertical-face))
     )))

(add-hook
 'typescript-ts-mode-hook
 (lambda ()
   (setq-local treesit-font-lock-settings
               (append treesit-font-lock-settings
                       amitp/treesit-font-lock-typescript-axis-variables))))

I think the restriction doesn't help much in this case, but it was fun learning how to use treesit-font-lock-rules for this. Here's the result:

Screenshot showing color for variables

Do you notice the bug?

This was a fun experiment. But it's a bit too noisy, especially if I have both variable and syntax highlighting. I think it'd be better if I could highlight only the problematic subexpression (the y2-dx). I decided to try it with a simpler problem.

In some of my projects, I use geometry meshes that have integer IDs for regions (r), sides (s), and triangles (t). I want to avoid bug where an array is indexed by a triangle ID, but I accidentally index it by a triangle ID. Some languages have a feature called "newtype" to make these separate types, but I don't have them in Javascript. Instead, I use a naming convention to help me catch these:

  • x_foo is a value storing type x
  • x_foo_y is a function or array indexed by type y and stores values of type x

I was hoping to use tree-sitter to find subscript expressions, and then check if the array name ends with the same type _x as the index expression x_ starts with. I implemented it for simple cases:

(defvar amitp/treesit-font-lock-typescript-indexing
  (treesit-font-lock-rules
   :language 'typescript
   :feature 'semantic-identifier
   :override t
   `(((subscript_expression) @error
      (:match "_r\\[_?[st]\\|_s\\[_?[rt]\\|_t\\[_?[sr]" @error)))
   ))

And it does work:

Screenshot
Highlighting potential indexing error

However, tree-sitter plays almost no role in this. The real work is done by the regular expression. It doesn't catch these cases:

elevation_t[obj.t]       // good
elevation_t[obj.r]       // bug
elevation_t[t_from_r(r)] // good
elevation_t[r_from_t(t)] // bug
elevation_t[x.t_fn(x)]   // good
elevation_t[x.r_fn(x)]   // bug
elevation_t[x.t_arr[i]]  // good
elevation_t[x.r_arr[i]]  // bug

How would I also handle these cases? I attempted it, but it took more time than I had originally planned for, so that'll be in part 3.

-1:-- Emacs Tree-sitter custom highlighting, part 2 (Post Amit Patel)--L0--C0--2026-08-16T21:53:41.759Z

Amit Patel: Emacs Tree-sitter custom highlighting, part 1

A few years ago I blogged about custom tree-sitter-based syntax highlighting in emacs. I started out with highlighting certain keywords in red. I wanted to show the keywords that interrupt control flow:

Screenshot showing syntax highlighting of keywords in black or red

I highlight regular keywords (while, if) in bold. I highlight control flow interrupting keywords (return, continue) in red.

That's syntax highlighting.

But I also wanted to highlight names based on their meaning.

In some projects I'm working with horizontal and vertical measurements, and I thought it would be nice to highlight those variable names differently. In some projects I'm working with Delaunay+Voronoi meshes, and I wanted to highlight the triangles, edges, and polygons differently. In some projects I'm working with hexagonal grids, and I wanted to highlight the three axes differently, like I do in my guide to hexagonal grids. In some projects I am working with html+js mixed code, and I wanted to highlight the html vs js differently, like I do in my tutorial on how I write interactive tutorials.

In that blog post from a few years ago, I had used emacs tree-sitter mode to implement this type of highlighting. Since then, emacs has incorporated its own tree-sitter mode, which works differently from the third party package. I decided to update my code to work with the new tree-sitter functions.

Let's start with highlighting keywords. This doesn't really need tree sitter mode, but I'm using it because I want to highlight more in part 2 of this post. The treesit-font-lock-rules function compiles tree sitter patterns into some internal format. Since the set of keywords is different per language, I'm going to make one variable per language. Here's the one for Python:

(defface amitp-control-keyword-face)
(defvar amitp/python-treesit-settings
  (treesit-font-lock-rules
   :language 'python
   :feature 'keyword
   :override t
   '(
     (["assert" "await" "break" "continue" "finally" "raise" "return" "yield"]
      @amitp-control-keyword-face)
     )
   ))

This will highlight those keywords in my amitp-control-keyword-face instead of the default font-lock-keyword-face.

A minor note — I had tried reusing my previous tree-sitter face names, which were named with periods (variable.horizontal) and colons (amitp:keyword), but I ran into some errors, so I switched to only using dashes (variable-horizontal) for the new tree sitter modes.

I then have to install these highlighting rules into treesit-font-lock-settings, which is a buffer-local variable. So I install it from a hook:

(add-hook 'python-ts-mode-hook
          (lambda ()
            (setq-local treesit-font-lock-settings
                        (append treesit-font-lock-settings
                                amitp/python-treesit-settings))))

That's all it took to get my keywords highlighting the way I wanted. But wait, there's more!

I had noticed that type aliases didn't highlight the way I wanted. Here's the screenshot:

Screenshot shows that the word 'type' is not highlighted as a keyword

The keyword type isn't being highlighted here. I could add it as a keyword like the others:

(defvar amitp/python-treesit-settings
  (treesit-font-lock-rules
   :language 'python
   :feature 'keyword
   :override t
   '(
     (["assert" "await" "break" "continue" "finally" "raise" "return" "yield"]
      @amitp-control-keyword-face)
     (["type"] @font-lock-keyword-face) ;; <-- added
     )
   ))

Alternatively, I could modify python.el's python--treesit-keywords to include "type".

However, unlike other keywords, type is a soft keyword in Python, not a full keyword. That means it can be used in other contexts such a function call:

Screenshot shows that the word 'type' can occur as both a keyword and an identifier

I don't want to highlight those uses. I used M-x treesit-inspect-mode to learn the tree-sitter parse tree context. And then I made the rules highlight the word type only when it occurred in the context of defining a type alias:

(defvar amitp/python-treesit-settings
  (treesit-font-lock-rules
   :language 'python
   :feature 'keyword
   :override t
   '(
     (["assert" "await" "break" "continue" "finally" "raise" "return" "yield"]
      @amitp-control-keyword-face)
     ((type_alias_statement "type" @font-lock-keyword-face)) ;; <-- added
     )
   ))

Here's the result:

Screenshot shows that the word 'type' when used as a keyword is highlighted differently than when used as a function
"type" keyword vs "type" function

This is one of the nice things I get by using tree-sitter highlighting instead of regex highlighting.

I also changed import statements to be highlighted the way I want, by marking import NAME and from name import NAME in a different face:

  (treesit-font-lock-rules
   :language 'python
   :feature 'definition
   :override t
   '(
     ((import_statement name: (dotted_name (identifier) @font-lock-variable-name-face)))
     ((import_from_statement module_name: (dotted_name (identifier))
                             name: (dotted_name (identifier) @font-lock-variable-name-face)))
     )

I'm still experimenting to figure out what rules I want to have. I try things and then delete some of them. I've found M-x treesit-inspect-mode to be quite useful.

In part 2 I'll explore heuristics to highlight based on variable names I commonly use.

-1:-- Emacs Tree-sitter custom highlighting, part 1 (Post Amit Patel)--L0--C0--2026-08-16T21:53:12.412Z

Irreal: Emacs Backups

Raymond Zeitler over at Ray on Emacs doesn’t use version control for his “non-public” files so when he had a problem with his .emacs file, he didn’t have any backups except for the single last version of a file that Emacs will make automatically if you have backups enabled. That, apparently, was enough to recover but Zeitler resolved to do better.

What he did was to replace .emacs~ with .emacs.~1~, which causes Emacs to create a numbered sequence of backups rather than simply overwriting the last one. Reading his post reminded me of my backup scheme, which I haven’t thought about in years because all my files—except autogenerated ones—go into Git.

My backup scheme is a little different from Zeitler’s because I’ve set mine to keep only the last two backups. That way, my file system doesn’t fill up with long forgotten and unneeded backups. Even so, there were a lot of backups—some for files that don’t even exist anymore—taking up a lot of space. I deleted them all and started over.

I don’t claim that 2 is the right number for everyone or even for me. It just seemed like a safe number in case something happened with Git. Happily, I’ve never had to use the backups, which is why I’d mostly forgotten about them.

One thing for sure, you need to be religious about either saving your files to a version control system or configuring the Emacs backup system to automatically save some number of backups for you.

-1:-- Emacs Backups (Post Irreal)--L0--C0--2026-08-16T15:04:42.000Z

Alvaro Ramirez: Focus windows across Emacs frames

Historically, I've only ever known two great ways of focusing Emacs windows: the built-in other-window command, which cycles through all the available windows, and the ace-window package, offering random window access. It's not that there aren't more or better ways, I just didn't look any further as these were enough for my needs.

While I really wanted to make ace-window my default choice, for whatever reason, it never stuck. other-window with a custom M-o binding always felt like the smoother fit for my limited needs. You see, I hardly ever have more than two visible windows, so whenever ace-window kicked into action (for 3 or more windows), it often took me by surprise. The one area other-window didn't fit my needs revolved around focus requiring visual feedback, but I eventually solved that with winpulse (a little package I wrote).

As you can see, I'm a simple man using few Emacs windows, and when it comes to frames, I almost never use more than one. That is until somewhat recently, when I built ytr, a tiny YouTube radio player that sits in the corner of your frame. While it all feels fairly integrated into your frame, under the hood, ytr renders in a separate frame. This broke my trusty other-window flow. I couldn't just focus my radio window using my well-internalized M-o binding.

Turns out, other-window actually caters for focusing windows across frames, but only when invoked programmatically. Sure, I can wrap it with my own custom command, but Emacs already had me covered. I found the built-in next-window-any-frame command. All I had to do was bind it to M-o and Bob's your uncle.

I can now switch between my current window and my YouTube radio, with my dear M-o binding. Balance restored.

-1:-- Focus windows across Emacs frames (Post Alvaro Ramirez)--L0--C0--2026-08-16T00:00:00.000Z

Irreal: Emacs Users

Chris Maiorana has a lovely blog post in which he makes the case that Emacs users are a different breed. He starts with the quotidian observation that you can be pretty sure that a Vim or VSCode user is either a software developer or in a closely related such field such as System Administration or DevOps. Emacs users, on the other hand, could come from almost anywhere.

This is a point that Irreal is fond of making. I’m fascinated by people from outside the software field who use Emacs as an essential part of their workflow. Their stories always illustrate the power and flexibility of Emacs to solve problems even if they aren’t about software and engineering.

What distinguishes Emacs users, Maiorana claims, is their love of tinkering and the blurring of work and play when they’re using Emacs. As he puts it

Emacs could be more than a text editor. It could be an organizer, a scratchpad, a dictionary, a canvas, a database, a calendar, an email client, and much more. What should have been work started to feel like fun.

Maiorana says that Emacs users don’t care primarily about the usefulness or user friendliness of Emacs; it’s the opportunity for tinkering that attracts them. I think he has this part backwards. Emacs users surely do love to tinker but the attractive thing about Emacs is that your tinkering can turn it into the exact tool that you need. That’s true even if the tool you need has nothing to do with software or its siblings.

Emacs is, in fact, the perfect piece of software. Not only does it indulge our urge to tinker and have fun but also allows us to turn that tinkering into the precise tool we need. Not just once but as often as we desire as our needs change. With Emacs, the fun never ends.

-1:-- Emacs Users (Post Irreal)--L0--C0--2026-08-15T15:29:25.000Z

Andros Fenollosa: Elpher: my tweaks for better Gemini browsing in Emacs

Elpher is the Gemini browser for Emacs. It works wonderfully and its integration with GNU's editor is sublime. You don't just move between capsules: you can also write your own scripts or build automated flows. It's Emacs, after all. Everything bends to your will.

If you still don't know what Gemini is, it's a minimalist protocol, halfway between the web and Gopher, with no JavaScript and no trackers. I cover it in depth in Introduction to the Gemini protocol.

Today I want to share a minimal configuration I use to browse more comfortably. Nothing exotic, just a few shortcuts and a couple of tweaks that make a difference day to day.

Elpher is on MELPA. I declare it with use-package, so everything stays in a single block: :ensure t installs it the first time, :config defines the function and sets the protocol, and :bind keeps the shortcuts encapsulated. Paste this into your init.el.

;; ===
;; elpher - Gemini and Gopher client
;; https://thelambdalab.xyz/elpher/
;; ===
(use-package elpher
  :ensure t
  :config
  ;; Smart link following that handles emojis
  (defun elpher-smart-follow ()
    "Smart link following that handles emojis."
    (interactive)
    (let ((current-pos (point)))
      (beginning-of-line)
      (if (re-search-forward "=>" (line-end-position) t)
          (elpher-follow-current-link)
        (goto-char current-pos)
        (elpher-follow-current-link))))

  ;; Gemini as the default protocol instead of Gopher
  (setq elpher-default-url-type "gemini")

  :bind
  (:map elpher-mode-map
        ("RET" . elpher-smart-follow)      ; follow link, emoji-aware
        ("l" . elpher-back)                ; back, same as eww
        ("n" . elpher-next-link)           ; next link
        ("p" . elpher-prev-link)           ; previous link
        ("b" . elpher-bookmark-current)))  ; bookmark, same as eww

What does all this give you? A few improvements you notice right away:

  • n to jump to the next link.
  • p to go back to the previous one.
  • l to return to the previous page, just like in eww.
  • b to save the current page as a bookmark, just like in eww.
  • It widens the clickable area of each link, so you can click on the emojis and not only on the text.
  • It sets Gemini as the default protocol instead of Gopher.

Watch out with elpher-smart-follow: it reassigns RET. If you already had that key mapped to something else inside elpher-mode, this overrides it.

That last note about emojis seems trivial, but it changes the experience a lot. Many capsules decorate their links with an emoji in front, and if you try to follow them from there, Elpher just sits still. With elpher-smart-follow it no longer matters where the cursor is on the line: if there is a =>, it takes you there.

And this is only the beginning. Elpher is Lisp, and Lisp bends to whatever you need: today it is four shortcuts, tomorrow a flow that opens your favorite capsules when Emacs starts. Tinker with it, break it, make it yours.


Help me keep writing Every coffee gives me a push toward the next article. Sure, it's on me!

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

-1:-- Elpher: my tweaks for better Gemini browsing in Emacs (Post Andros Fenollosa)--L0--C0--2026-08-15T14:07:40.000Z

Mike Hostetler: No Graph Needed

This month's Emacs Carnival theme is The Search For Knowledge, which is kinda something near and dear to my heart. I wrote an Obsidian to Emacs post from a little over a year ago and one of the things that I mentioned is that Obsidian has a Knowledge Graph that I didn't use. denote-explore does have one and I set graphing up once but I don't use it at all. I work just fine without a Knowledge Graph. Using Denote, ripgrep, and consult, things work great for me. It's smooth and not a lot of fuss.

I literally do all my knowledge management now within Emacs in a Denote system. I dump all of my notes into one folder in org format. Denote puts a time stamp on the file name as well as the title and the tags. Searching by tag is really just a filename search. See examples from my own denote folder:

20260714T130657--org-agenda-views__orgmode.org
20260714T100032--filtering-org-ql-results__orgmode_productivity.org
20250605T091129--escape-character-in-orgmode__orgmode.org

For me the file name is the index. It's simple. I don't need a SQLite database or anything else on the backend. As soon as I create a file I know that I can search by the file name or search its contents. This keeps things simple and, for me, simple is good. When I want to search for all of my notes about orgmode, I just run my mh/consult-denote-find-files , type orgmode and all of those files appear. If I want to add something else to that search, I just type that in. Here is my simple function that does that, using consult-find

(defun mh/consult-denote-find-files ()
  "Find files in the Denote notes directory by name via `consult-find'."
  (interactive)
  (consult-find (car (denote-directories))))

So that brings up the files using the wonderful consult-find format. I just keep typing to narrow down my search. Easy and effective.

And inside the files, the denote tags are the same as org mode tags when the document is open since it uses the TAGS property on the top of the file. I generally use just ripgrep for that – see the function I use below:

  (defun mh/rg-org-wiki ()
  "Search ~/org-wiki with ripgrep via `consult-ripgrep'.
Follows symlinks and ignores JSON files."
  (interactive)
  (let ((wiki-dir (expand-file-name "~/org-wiki")))
    (if (file-directory-p wiki-dir)
        (let ((consult-ripgrep-args
               (concat consult-ripgrep-args " --follow --glob=!*.json")))
          (consult-ripgrep wiki-dir))
      (message "Directory ~/org-wiki does not exist!"))))

(yes, my denote files are in a folder called org-wiki. That actually dates back to a pre-Obsidian setup I had once. This function actually dates back to that but it's easy to reuse here. Again, another win for Emacs and simplicity)

I do have denote-explore installed and I do use denote-explore-random-note to, well, explore my notes. I find things that I forgot, and find things to update. I use that generally when I just want to work in my notes for a while.

But that is the exception and not the rule. I set up links to explore my notes. Using the example files above, I use denote-link to link to a top-level OrgMode note (since they're all about OrgMode). That note is kind of a Map Of Content about OrgMode. That is how I want to discover notes… the individual notes link to top-level notes and that note is linked to all else about OrgMode. Well that is the theory anyway… I set it up but I don't do it near as much as I thought I would. This goes back to how I used Obsidian. I tried really hard to use a very rigid and disciplined link structure and at the end I find I didn't use it. It took more time than writing a lot of my notes. So really my flow is – I find and read notes that my past self used and update them to what I currently think but I don't necessarily follow a web of links. While a graph is useful for a lot of people, it just doesn't seem as useful for me. The filename is a good enough index for me.

-1:-- No Graph Needed (Post Mike Hostetler)--L0--C0--2026-08-15T05:00:00.000Z

Alvaro Ramirez: agent-shell 0.73 updates

Another month, another agent-shell update. If you missed the last one, have a look at the 0.63 update. While this post showcases the latest highlights, the full list of changes is far chunkier than what we'll cover.

What's agent-shell?

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

agent-shell enters the chat

Since inception in September last year (yikes nearly a year), agent-shell has featured a shell-like experience, powered by comint mode. There's also viewport mode (via agent-shell-prefer-viewport-interaction), if you prefer a more focused experience, and now we have chat mode.

Chat mode fuses comint mode with a more traditional chat-like labelling experience.

We're living on the edge here, so chat mode is now enabled by default. Ok not really that edgy, it's fairly safe (powered overlays) and can be disabled entirely via (setq agent-shell-chat-mode-enabled nil). Chat mode itself is a minor mode, so you can always toggle it on and off via M-x agent-shell-chat-mode.

A little more chatter

In the last post, we talked about making agent-shell less chatty with more grouping for the likes of tool calls and agent thinking, all collapsed by default. If that's far too quiet, you can expand by default via (setq agent-shell-activity-group-expand-by-default t). If you found these two settings either too quiet or too chatty, we now have a third alternative via (setq agent-shell-activity-group-expand-by-default 'latest). When set, only the latest grouped activity is expanded by default, and automatically collapsed when the agent moves on to something else.

I've become quite fond of this feature (thank you @nhojb for the PR), so yes. It's also enabled by default.

Prompt queueing

Queuing received some improvements. The related commands have been consolidated under agent-shell-prompt-queue.el. You can queue prompts while the agent is busy, then view, resume, or drop pending prompts via M-x agent-shell-prompt-queue, M-x agent-shell-prompt-queue-resume, and M-x agent-shell-prompt-queue-remove. The pending queue is now shown after each new submission.

Compose from anywhere

M-x agent-shell-prompt-compose opens a dedicated buffer for crafting a prompt, and it's now more independent of the shell. You can invoke it from any buffer (it resolves to the right shell), C-c C-c sends and returns you to whatever you were doing (fire and forget), and C-u C-c C-c submits the prompt and immediately lets you craft another queued prompt.

Start typing sooner

Shell initialization may take a second or two, depending on what agent you're using, which meant you had to wait for initialization before you could start typing into your new shell. Unnecessary, so that's no longer the case. Shell prompts are now offered as soon as possible, so one can get typing.

Markdown lists

While Markdown lists are easily digestible without special rendering, we can do better than that, so we now give them a better treatment with normalized padding, indentation, and of course, civilized bullets.

TAB navigation improvements + hints

TAB navigation made it into agent-shell fairly early on. I love being able to TAB my way into any section in the buffer and press RET to toggle folding. That's great and all, but we can make the navigation experience richer by welcoming the likes of Markdown source blocks, links, and images to the navigation party. Why these in particular? They are all actionable by RET too, of course.

While you'd rightly assume RET opens links to local text files in Emacs and delegates to browsers when needed, Markdown source blocks and images get a similar treatment. While their respective RET actions may not be as obvious, we can certainly make them much more discoverable, so we now add hints. Landing point on an actionable item now echoes a hint of what you can do and which key does it (say, "Press RET to copy" on a source block, "Press + to enlarge" on an image, and so on). Hints are also shown on mouse over events.

Image size hints

While agent-shell offers agent-shell-markdown-image-max-width for customizing image sizes, it's fairly restrictive. Not all images are the same, so why force them all to fit within the same constraint? agent-shell-markdown-image-max-width continues to offer a preferred default, but you can now scale images differently by getting the agent to annotate Markdown images with Pandoc-style link attributes.

The attribute block goes right after the image, taking a width and/or height in pixels or percentages:

![alt](image.png){width=300}
![alt](image.png){width=50% height=200}

Auto resize

I may be sweating the small stuff here, but this was really grinding my gears. We have lovely Markdown table rendering, which I'm glad we do as LLMs aren't always great at producing perfectly aligned tables. In the best of cases, the LLMs align the table perfectly, but it's just too wide for our Emacs window. Luckily, our lovely rendering also wraps cells to make them fit into our window. The thing is, all that lovely rendering goes out the door the moment you either resize your Emacs frame or merely split your window, resulting in a monstrosity like this:

I know. I'm sweating the small stuff here, but hey we don't have to live like this. Emacs has all the hooks in the world, so let's track window changes and rejoin the civilized world.

On a much smaller scale, I also wanted auto resize for images, so here you have it…

Manual resize

While we can influence image size at render time, this can still generate undesirable image dimensions, so we can now rescale all images in buffer on demand.

Sometimes a hammer really isn't the right tool, so we can now also rescale an image at point.

Open file links as you please

Opening a local file (from a link, image, or @file mention) now routes through agent-shell-file-display-action, a standard display-buffer action you can customize. The default reuses a window already showing the file, or takes over the current one.

If you'd like a different window arrangement, you could do something like:

(setq agent-shell-file-display-action
      '(display-buffer-pop-up-window))

Xref support

Following a local file link now pushes your origin onto xref's marker stack, so xref-go-back (M-,) brings you right back to where you were in your agent-shell, just like other Emacs jumps.

Use your favorite folding key binding

Foldable fragments now use agent-shell-ui-fragment-map and bind agent-shell-ui-toggle-fragment. If you're not a fan of the RET binding to toggle folding, you can now use your preferred binding.

If TAB is your jam, you can do something like:

(with-eval-after-load 'agent-shell-ui
  (define-key agent-shell-ui-fragment-map
              (kbd "TAB") #'agent-shell-ui-toggle-fragment))

Styling thoughts

If you prefer styling agent thoughts differently, a new agent-shell-thought-body face lets you do just that.

Streaming performance

Streaming performance also received some love. Thanks to @suhail-singh for the profiling and improvements, and to @Scott-Guest and @claytharrison for the trace analysis and benchmarking in #757.

Richer display of options

The "Available config options" section now displays possible values.

Public functions

Dynamic agent configs

agent-shell-agent-configs can now be set to a function, letting you compute the available agent configurations dynamically rather than hard-coding a static list. The function is called on every access, so it stays current across code reloads.

Maybe you'd like to list only available agents. Here's a rough snippet.

(setq agent-shell-agent-configs
      (lambda ()
        (seq-filter
         (lambda (maker)
           (when-let* ((client (ignore-errors
                                 (funcall (map-elt (funcall maker) :client-maker)
                                          (current-buffer))))
                       (command (map-elt client :command)))
             (executable-find command)))
         (agent-shell-default-agent-config-makers))))

Subscribe to agent message chunks

agent-shell-subscribe-to now broadcasts an agent-message-chunk event, handy for external integrations that want to observe streamed output.

Remote agents

executable-find now works correctly on remote hosts (#742 by @CeleritasCelery), smoothing out TRAMP-driven remote agents.

New third-party packages

agent-shell-hq joins the family, offering an interface for managing multiple agent-shell sessions.

Maintenance and housekeeping

If you peeked at the commit logs, you'll notice I've been working daily on agent-shell, keeping up with project inflow. Since last month, 27 issues have been closed and 13 pull requests merged. As of today, the backlog sits at 11 open issues and 5 open PRs (versus 13 and 4 last time around). If there's something you'd like me to prioritize, feel free to ping.

agent-shell needs your support

Vendor-neutral tooling matters more than ever, 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. Time spent on agent-shell is time away from work that pays the bills, so if it's 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 exposure

GitHub stars help with exposure, attracting new users and potential sponsors. Starring agent-shell costs nothing and can potentially help bring in more funding, 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 agent-shell

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

-1:-- agent-shell 0.73 updates (Post Alvaro Ramirez)--L0--C0--2026-08-15T00:00:00.000Z

Irreal: 🥩 Red Meat Friday: The Perils Of Rewriting

The Emacs Cat, of whom I’ve written many times, has a post on a broken change in Ubuntu that affected Emacs. The TL;DR is that the Cat upgraded to Ubuntu 26.4 but when he tried to use Emacs he discovered a problem with Dired. At first he thought the problem was with Emacs 30.2, which is the default Emacs with that version of Ubuntu. But the actual problem turned out to be with Ubuntu’s implementation of ls. The Cat solved his problem by using the “old” version of ls from GNU coreutils. Ubuntu had decided that it was a good idea to rewrite the core Unix utilities—such as ls​—in Rust.

Maybe I’m just old and grumpy but this seems to me to be a terrible idea. Utilities like ls have been around and working more or less flawlessly for close to 50 years. What, really, was the reasoning behind the decision to rewrite ls? Twenty six years ago, Joe Spolsky answered that question and explained why the rewriting is almost always a bad idea.

It may have been written a quarter of century ago but Spolsky’s explanation remains true. There really is no reason—other than fashion—to rewrite core utilities that have been working perfectly well for longer than many of us have been alive. When you do, you can expect exactly the sort of problem that the Cat stumbled on.

The problem with the new ls was very specific involving an obscure parameter but it reached out to disable a major Emacs functionality and probably affected other applications as well. The annoying thing is that there really wasn’t any reason for this to happen: just someone who decided it would be a good idea to rewrite an application that was working fine.

-1:-- 🥩 Red Meat Friday: The Perils Of Rewriting (Post Irreal)--L0--C0--2026-08-14T15:32:50.000Z

Chris Maiorana: The Emacs pastoral tradition

You’ve probably noticed, Emacs users tend to be a different breed than other computer users. While you can be reasonably sure that anyone who uses vim or VSCode is probably a developer, or someone who works in software engineering, devops, or systems administration, Emacs users can be from virtually anywhere else on the map.

While conventional software has the basics—like users, functions, documentation, and best practices—Emacs has a tradition.

In this article, we’ll look at:

Table of Contents

Let’s get nutty.

The Emacs pastoral tradition

Emacs users may come from career backgrounds or intellectual disciplines that have almost nothing to do with software or computers in general, but they have simply found their way into the “cult” of Emacs from pursuits like:

  • Academics
  • Writing and literature
  • Civics
  • Philosophy
  • Law
  • Medicine
  • Music

(And more, I’m sure.)

I’ll use myself as an example. In my technical writing career I was searching for a more optimized text editor. I started learning vim, but switched to Emacs because, as it turns out, I wasn’t looking for a text editor so much as an “operating system” for writing.

Emacs could be more than a text editor. It could be an organizer, a scratchpad, a dictionary, a canvas, a database, a calendar, an email client, and much more. What should have been work started to feel like fun.

This is what I’ve started calling the Emacs pastoral tradition.

Pastoral themes in literature evoke images of shepherds ostensibly tending to a flock, but really engaging in more leisurely pursuits like writing poetry and music. The point being that it’s the leisurely pursuits that uplift the soul and are thus more important than mere toil, even if toil is useful and productive.

Emacs is a kind of playground where we can bring our unique ideas and favorite hobbyhorses and have fun building systems around them.

The pastoral also applies to the church: the pastor as the shepherd, and the ones being sermonized to as the flock. Emacs is already jokingly referred to as a cult, so I need not belabor this point.

Doing “useless” work can be useful

What we do in Emacs can look a lot like work, and sometimes it is, but often it’s leisure disguised as work.

This might include:

  • Writing a blog post in Org Mode (as I’m doing now).
  • Tweaking your init.el file to approach that ever-illusive perfect configuration.
  • Auditioning new packages out of curiosity.
  • Trying to play Doom or surf the web in Emacs.

I’d go so far as to say that there are some entire projects out there, such as EXWM, that can be described as leisure.

Does anyone really need an Emacs window manager? No, but that’s the point. It’s an aesthetic choice rather than a purely rational one. An Emacs window manager is no more useful than any other.

I’ll paraphrase Roger Scruton in saying:

There’s nothing quite as useful as the truly useless.

But even useless pursuits can be useful, even if the only useful aspect is the fun of leisurely non-productivity.

The tool doesn’t always matter

Emacs pursuits can be productive endeavors, but they need not be in order to justify our time.

There are lots of productivity tools out there that are probably better than Org Mode. Or, at the very least, an intelligent critic could make an impassioned case for why Org Mode is lame, with multiple bullet points and peer-reviewed references; but it would not be persuasive at all to a sufficiently dedicated Org user.

For Emacs users, the user-friendliness or usefulness of the tool is the not the key point. The promise of endless tinkering is more tantalizing than a tool that simply works all the time. That’s why we’re always tweaking but never really landing on a static configuration. Emacs itself is a running Lisp image you can manipulate on the fly. At this level of interaction, the margins between work, experimentation, and play become invisible, or at least quite porous.

Some people believe we may on the cusp on a new era of non-production. You know as well as I do that there’s more hype than substance around AI at the moment. However, in the brave new world, there’s a non-zero chance that the machines will take over all the toil and leave us free to indulge in a new age of pastoral leisure. Sitting under shady trees and composing romantic sonnets in iambic pentameter sounds pretty good to me.

For people who derive value and validation from productivity, this is a threat. But for Emacs users it might be what we’ve always wanted.


If you enjoyed this ramble, here are some next steps for your consideration:

Thanks for reading. See you next time!

The post The Emacs pastoral tradition appeared first on Chris Maiorana.

-1:-- The Emacs pastoral tradition (Post Chris Maiorana)--L0--C0--2026-08-14T04:00:24.000Z

Sacha Chua: Emacs Chat 28: Fabrice Niessen (en français)

Nous avons parlé en direct avec Fabrice Niessen (Formation Emacs Boost) d'Emacs, de ses flux de travail, de sa configuration, et d'autres sujets en français.

J'ai essayé de corriger la transcription, mais je crois qu'il y a encore beaucoup d'erreurs, donc n'hésitez pas à m'envoyer un message.

  • 0:00 Introduction
  • 1:46 Le parcours
  • 4:15 Emacs sur Windows : Cygwin et WSL
  • 8:26 Les automatisations
  • 12:15 Emacs Boost
  • 21:15 Emacsclient
  • 28:49 org-html-themes
  • 35:02 Review
  • 37:25 PDF
  • 40:03 Key-chord
  • 45:41 Copier sans sélectionner
  • 48:29 fill-paragraph
  • 50:02 Les macros
  • 51:02 Volatile highlight
  • 52:47 Les tableaux Org
  • 55:43 Les formations et les cours particuliers

La transcription un peu corrigée

Details

0:00 Introduction

Sacha: Ça marche!

Prot: Maintenant, nous sommes en direct.

Sacha: Succès! Bonjour à toutes et à tous et bienvenue dans le deuxième épisode d'Emacs Chat en français. C'est le 28e épisode de la série. J'espère que j'ai lancé la diffusion correctement cette fois. Aujourd'hui, notre invité est Fabrice Niessen, un consultant informatique qui habite en Belgique et qui a beaucoup de dépôts dédiés à Emacs et à Org Mode, tels que le kit de configuration Leuven et le thème de publication d'Org Mode org-html-themes. Il propose aussi des formations à Emacs en présentiel à Paris, à Rotterdam et dans d'autres villes. Merci d'être là, Fabrice.

Fabrice: Merci pour l'invitation. C'est très sympa de votre part.

Sacha: Merci aussi à Prot de m'aider pour la conversation. En fait, je pense que mon niveau de français est un peu trop faible pour ce genre de conversation, même avec le sous-titrage automatique de mon navigateur. Donc, je vais étudier le sous-titre après la conversation pour mieux en tirer parti. Nous faisons de notre mieux ensemble.

Prot: Merci. Je t'en prie, je t'en prie. Non, de fait, très bien, très bien.

Sacha: C'est parti. Fabrice, tu utilises Emacs depuis très longtemps. 1999, c'est plus logique en Belgique.

Fabrice: Absolument. La Suisse est encore mieux avec le 8 ans.

Sacha: Oui.

1:46 Le parcours

Sacha: Comment t'as-tu découvert Emacs?

Fabrice: En fait, j'ai découvert Emacs en 95, lors de mon stage de fin d'études. J'ai été dans une entreprise pendant six mois et là, ils utilisaient Linux. Enfin, Unix plutôt, un serveur Unix. Et donc, j'ai dû apprendre à développer sous Emacs. Alors, j'ai appris les keybindings, j'avais la refcard imprimée avec un stabilo pour souligner tous les trucs que j'apprenais au fur et à mesure. Ça m'a beaucoup aidé d'ailleurs, d'en apprendre comme ça, C-x C-s, et puis tous les suivants. Et après les six mois, c'était donc en 95, c'est l'époque de la sortie de Windows 3.11, je pense. J'avais un ordinateur avec Windows 3.11. Après, j'ai repris les habitudes point and click, donc des interfaces graphiques. Jusqu'en 99, à peu près 99, où un de mes collègues, Dominique, utilise Emacs. Et en fait, à l'époque, on avait déjà du versionning au boulot et c'était PCL CVS, c'était CVS le versionneur, donc un ancêtre de Subversion et un ancêtre de Git. Et il y avait un mode PCL CVS d'utiliser dans Emacs. Et quand j'ai vu ça une fois, c'est en fait un peu ça qui m'a fait sauter le pas de me dire il faut que je retourne parce que retourne regarder Emacs. Parce que c'était beaucoup plus simple pour lui, il avait modifié ses fichiers dans Emacs, il avait son petit truc où il sélectionnait les quelques fichiers qu'il voulait committer, il les sélectionnait, il faisait je ne sais plus quel key binding, et puis il mettait son message de commit, C-c C-c, et voilà, c'était fait. Et donc quand j'ai vu ça, alors que moi, il fallait que je passe dans le shell, il fallait que je fasse sans doute des CVS add ou des choses comme ça, des quelques fichiers, c'était beaucoup plus long. Beaucoup plus compliqué. Donc, j'ai recommencé à me réintéresser à Emacs. J'ai utilisé son .emacs et puis j'ai retiré un petit peu de trucs et j'en ai rajouté énormément. Et c'est comme ça que ça a commencé. Et voilà, c'est fantastique. Évidemment, une fois qu'on rentre dans Emacs, c’est-à-dire le C-s, rien que ça, la puissance du C-s, l’incrémental search, c’est dingue. Donc, c'est comme ça que j'ai commencé et puis je n'ai pas pu m'arrêter.

4:15 Emacs sur Windows : Cygwin et WSL

Sacha: Tu utilises Emacs sur Windows.

Fabrice: Exactement. Donc, effectivement, à l'époque, j'ai fait plusieurs tentatives. J'ai utilisé Cygwin, donc j'ai utilisé Emacs ou Cygwin. Je vais aussi utiliser le Emacs natif. Ce qui était compliqué, parce que j'écrivais quand même aussi beaucoup de shell scripts, etc. Donc là, c'était dans mon Cygwin. Avec les Emacs natifs, il y avait les montages, le /mnt, etc. Donc c'était surtout le Emacs Cygwin que j'utilisais. Et depuis maintenant 3-4 ans, 4-5 ans, Windows a eu la géniale idée de mettre le WSL, donc Windows Subsystem Linux, et donc dans mon Windows, j'ai un vrai Linux, encore mieux, qui est plus puissant que Cygwin encore. Et tout est prévu, tout est interconnecté. Donc je peux passer, je peux être dans le monde Linux et voir mes fichiers qui sont sur le c: et vice versa, ce qui était plus compliqué avant. Donc voilà, c'est fantastique et donc j'utilise maintenant WSL. Je n'ai plus que ça et ça marche extrêmement bien. Et c'est mon environnement de travail.

Prot: A-t-il des difficultés d'utiliser Emacs sur Cygwin ? Ou c'est comme WSL ?

Fabrice: C'était un émulateur. Il y avait à peu près tout ce que je voulais quand même. Mais il n'y avait pas tous les outils. Peut-être des outils style... Non, et encore, je dirais des bêtises. Top, je pensais à top, etc. Mais je pense que non, ça, c'était aussi implémenté. Mais bon, il y avait un certain souci. Il y avait un certain nombre de commandes implémentées. Ici, c'est un vrai Linux. Et alors avec sudo apt install, tandis que sous Cygwin, il fallait installer des packages séparément. Donc on a une petite interface, on sélectionne les packages. Ici, je recopie ce que je peux trouver sur le web. Si je veux installer R, j'utilise le langage R, sudo apt install R et des choses comme ça. Donc c'est beaucoup plus facile. C'est vraiment typiquement tout ce que les gens font qui n'ont que Linux. C'est exactement les mêmes commandes. Je trouve ça beaucoup mieux, c'est beaucoup plus intégré.

Sacha: Je pense que c'est aussi plus vite qu'utiliser Emacs sur Windows directement parce que Magit, par exemple, Magit est trop lent, je pense, si tu utilises Magit sur Windows directement.

Fabrice: Ça c'est un truc, c'était déjà pareil avant avec Cygwin aussi. Au début, j'avais installé mon WSL Linux, donc il est monté, et j'avais mis Emacs, je pense... Attends, comment ça allait ? Non, je mettais mes repos à l'extérieur, dans le C:. Et en fait, là, il y a quand même un passage entre les deux mondes qui est extrêmement lent. Rien que pour lancer Emacs, mon Emacs se lance en une à deux secondes. Là, dans ce cas-là, ça se lançait en une ou deux minutes. Donc, il y a une différence énorme. Donc, en fait, il faut vraiment tout mettre, vivre uniquement dans le monde WSL, dans le monde Linux. Donc, mes repos sont dans Linux. Mon Emacs, il est dans mon Linux. Mais en fait, les fenêtres sont partagées. C'est ça aussi qui est bien. Je peux avoir une fenêtre, ma fenêtre Emacs, elle est à côté d'une fenêtre Outlook, par exemple. Et les presse-papiers, etc. sont complètement interconnectés aussi. Donc, c'est vraiment comme si c'était une application Windows. Sauf que réellement, elle est dans le sous-système Linux. Mais il faut vraiment mettre tout dans le sous-système Linux pour que les performances soient potables. Sinon, il y a quand même des lenteurs pour les fichiers qui sont lus d'un domaine à l'autre, d'un espace à l'autre. Les shared files, quoi. Tout ce qui passe par le point de montage est quand même beaucoup plus long.

8:26 Les automatisations

Sacha: Maintenant, tu as un environnement plus pratique pour tes automatisations. Parle-moi de tes automatisations préférées pour les tâches répétitives.

Fabrice: Volontiers. En fait, j'ai préparé un fichier Org, évidemment, pour préparer cet entretien. Et donc, vous voyez mon écran ou je dois partager ? Oui, vous le voyez, c'est ça ? Toujours ?

Prot: Oui, oui, oui, oui, je peux voir ton écran.

Fabrice: Donc, voilà. Voilà, ici, j'ai mon Emacs en grand. Et vous voyez dans le titre ici ? Le titre, je le... Oui, toutes les 20 secondes, il y a un petit truc comme ça. Toutes les 20 minutes, il faut poser les yeux pendant 20 secondes. Toutes les 20 minutes, j'ai un programme qui m'interrompt pendant 20 secondes. Ça va encore arriver deux fois tout à l'heure. Et donc ici, dans le titre, vous voyez GNU Linux Emacs. Donc, on voit vraiment que c'est l'Emacs du GNU Linux. Et avec ici, j'ai la date dans laquelle il avait été compilé. Et puis j'ai le PID aussi. Parfois, dans le temps, j'avais plusieurs Emacs, donc j'ai le PID qui est aussi indiqué. Comme ça, quand je devais faire un kill, ça arrivait parfois de devoir faire des kills, je savais au moins lequel il fallait que je kill, parce que j'avais l'information directement dans le titre. Donc ici, j'ai une petite présentation Org. Avec un agenda. On a parlé de mon parcours, de comment j'ai fait la connaissance d'Emacs. Tu posais des questions sur différents repositories que j'ai rendus publics sur YouTube. Il y a le fameux thème de couleur Leuven. Leuven, pour information, c'est la ville où j'habite.

Prot: Une belle ville.

Fabrice: Très belle ville, absolument. Et voilà, j'ai un peu copié Till Tantau, je pense, le créateur de Beamer, qui avait donné des noms pour ses thèmes, Madrid, etc., Hanover, là où il était passé faire des séminaires. Donc c'est comme ça que j'ai pris le vœu au début. D'ailleurs, ici, vous voyez ce thème Leuven Theme qui est appliqué avec des titres plus gros pour les sections de premier niveau et puis des couleurs un peu différentes pour les to-do keywords, pour les titres, pour les sections, pour les tags, etc. Il y a des morceaux qui sont dans Leuven theme, il y a des morceaux qui sont dans ma configuration Emacs. Le thème de couleurs s'appelle Leuven Theme et la configuration Emacs. Pour l'instant, le repository sur GitHub s'appelle Emacs Leuven. J'ai mis ici un lien. D'ailleurs, pour information, cette note que je partage, c'est aussi sur GitHub. Vous pouvez très bien la chercher sur https://github.com. Et alors ici, donc, Emacs Chat Sacha Chua. Donc, vous pourrez le trouver là. Donc ici, il n'y a qu'un fichier, il y a juste l'heure et demie.

Prot: Oui, bonne idée de...

12:15 Emacs Boost

Prot: Et c'est quoi Emacs Boost ?

Fabrice: Donc en fait Emacs Boost, je fais des formations à Emacs depuis 2024 et j'ai appelé ça Emacs Boost, l'idée étant de booster l'utilisation d'Emacs, d'être plus efficace, d'être plus rapide, d'avoir des automatisations qui soient incluses dedans. Mon projet s'appelle Emacs Leuven depuis longtemps, mais pour être cohérent entre la formation que j'ai appelée Emacs Boost et mes configurations qui s'appellent pour l'instant Emacs Leuven, je suis en train de renommer tout doucement des fonctions, des variables, etc. vers Emacs Boost. L'idée, ce sera que tout mon fichier de configuration, que je peux vous montrer, Emacs Leuven, Je fais du literate programming. Donc ça c'est mon fichier de configuration d'Emacs. C'est le fichier à partir duquel est anglé ma configuration Emacs. Donc ici si je vais montrer de l'autre côté le fichier liste. Donc, ça, c'est mon fichier de configuration avec toutes mes configs à moi. Il fait, je pense, 7500 lignes. Je pensais qu'on était à 10 000 déjà. C'est un petit peu de nettoyage. Bon, alors évidemment, c'est facile parce que j'ai quasiment toujours, devant cette queue, j'ai toujours un commentaire. Donc, si on regarde ici, si je remonte un petit peu, voilà, tous mes sets de pubs. Je vais agrandir un peu. J'ai systématiquement une ligne de commentaire au-dessus. Voilà, define. Donc, j'ai une ligne blanche, une ligne de commentaire et puis une ligne de commande. Donc, en fait, si on retirait tous les blancs ou tous les commentaires, je serais à 2500 lignes. C'est déjà pas mal. Voilà, mais donc ça, c'est le fichier que j'ai commencé en 99. Et donc qui est aussi public avec toutes les configurations que j'ai et tous les packages que j'utilise. J'utilise un certain nombre de packages. Donc si je regarde, par exemple, un dont je vais vous parler tout à l'heure, je vais retourner au début du fichier. Je vais retomber sur ma liste, voilà. Donc ça c'est les packages que je charge ou en tout cas que je customise. Donc voilà, ça c'est toute la liste. Je crois qu'il y en a à peu près 70. Oui, un petit peu moins peut-être. Deux packages que j'utilise. Alors certains, je les ai commentés parce que je n'utilise plus tellement. Et sinon, ça c'est les packages que j'utilise couramment et pour lesquels il y a des configurations. Et donc d'ailleurs, le but d'Emacs Leven, c'était de dire c'est un fichier de configuration qui est pour moi, il y a mes configurations, la façon dont je veux travailler, mes automatisations, mes settings par défaut, ou des fonctions supplémentaires, des commandes supplémentaires. Mais l'idée, c'est que n'importe qui pourrait faire require Emacs Leven, donc évidemment cloner le repo, faire require de ce fichier-là, et ça ne devrait pas perturber son système. Ça devrait rajouter toutes les configs que j'ai faites, mais sans perturber, sans imposer, par exemple, je ne sais pas, fancy narrow. Donc, en fait, au démarrage, ça regarde, est-ce que ça va demander, ça va regarder les packages qui sont installés dans, si je prends l'exemple de Sacha, si Sacha faisait ça, ça regarderait les packages qu'elle a installés, qui sont appelables, qui sont loadables, qu'elle a installés dans son système. Et ça dirait, tiens, voilà, il manque tel et tel package, voulez-vous les installer ? Donc, ça va proposer d'installer tous ces packages-là. Mais on peut dire non. Et dans tous les cas, toutes mes configurations, je vérifie chaque fois si je modifie web-mode. Par exemple, je vérifie, est-ce que web-mode est installé ? Si oui, alors voilà les configs. S'il n'est pas installé, il n'y a aucune config qui se passe. Donc, j'ai toujours un... Si je vais regarder web-mode, par exemple, pour cet exemple-ci. with-eval-after-load, voilà. Donc ici j'ai toujours with-eval-after-load de web-mode. Et donc ma configuration de web-mode ne sera active que si web-mode a été trouvé et est utilisé. Donc je ne vais pas, en théorie, je ne vais pas rajouter des raccourcis clavier, etc. pour des modes que vous n'auriez pas. Et en théorie, le fichier, il n'y a pas de truc qui soit vraiment privé. Donc il n'y a pas mon nom, mon prénom, mon username, par exemple, il n'est pas dans ce fichier là. Mon adresse email n'est pas dans ce fichier-là. Donc j'ai un petit fichier init-local.el qui est vraiment spécifique à moi. Tout ce qui est là, en théorie, c'est relativement générique et universel. Très bien. Donc maintenant, si je repasse sur la... C'est ce que je considère être les meilleurs packages, ceux que j'ai testés. J'ai lu des centaines, si pas des milliers, sur une vingtaine d'années, si pas des milliers de fichiers de config. J'ai vu beaucoup de choses chez plein de gens et c'est comme ça que j'ai trouvé beaucoup de packages au fur et à mesure. Et quand c'était des choses qui me plaisaient, j'ai rajouté des configurations de ces packages. C'est pour ça que c'est aussi gros, mais ça fait beaucoup de choses. Et malgré tout, ça se charge quasiment instantanément. Donc aussi, les packages un peu plus longs, je fais des idle require. Donc j'attends qu'il y ait 5 secondes d'inactivité dans la session Emacs pour aller charger certains packages. Mon adresse email n'est pas dans ce fichier-là. Donc j'ai un petit fichier init local.el qui est vraiment spécifique à moi. Tout ce qui est là, en théorie, c'est relativement générique et universel.

Prot: Très bien.

Fabrice: Donc maintenant, si je repasse sur...

Sacha: Même si ta configuration inclut beaucoup de package, parce que tu délais le démarrage du package, c'est vite, c'est rapide.

Fabrice: Exactement. Je dis que c'est une à deux secondes maximum. Et en fait, l'idée, et on va en parler encore tout à l'heure, on verra, j'ai beaucoup de choses que j'aimerais bien dire, on va voir le temps qu'on a. Mais l'idée, c'est qu'en plus, une fois qu'il est démarré, il passe en mode serveur. Et donc, je peux lancer, ouvrir un fichier directement dans Emacs sans lancer une nouvelle instance d'Emacs. Donc je n'ai qu'une seule instance d'Emacs et c'est celle-là qui est utilisée tout le temps. Donc après, chaque fois que j'appelle Emacs pour écrire un message de commit ou des choses comme ça, c'est instantané. Il n'y a vraiment même plus de temps d'attente du tout. Il n'y a même pas les une à deux secondes de démarrage. Et par exemple, un petit truc que je voulais montrer éventuellement, deux petites automatisations que je trouve extrêmement utiles, qui font le pont. En fait, j'ai trois applications que j'utilise tout le temps chez Emacs. J'ai le Shell, ici, le terminal. Donc j'utilise pas, j'ai pas l'habitude, et ça c'est plutôt d'ailleurs à cause, entre guillemets, à cause de l'époque Cygwin, où le shell dans Cygwin, un shell dans Cygwin, dans l'Emacs Cygwin, ça marchait pas bien, il y avait des problèmes de C-m qui apparaissaient, etc. Donc j'ai pas pris l'habitude d'utiliser le shell dans Emacs. Donc j'ai toujours, voilà, mon petit shell sur le côté, j'ai Emacs et j'ai le browser. En gros c'est mes trois, et puis alors... Pour le boulot, évidemment, Outlook. Mais sinon, c'est les trois applications que j'utilise vraiment le plus. C'est Emacs, le Shell et le Browser. Et j'ai envie d'avoir des ponts entre ces deux mondes. Donc ici, mon Shell, mon terminal et Emacs. Du style, je veux donc voir si je vais ouvrir un fichier.

21:15 Emacsclient

Fabrice: Par exemple, je vais ouvrir le fichier Emacs... Leuven, tout doux. Donc ici, j’ai un petit alias e qui en fait va faire emacsclient. Quand je fais ça, si tout se passe bien, voilà. Donc ici, il m'a ouvert ce fichier tout doux dans Emacs. Donc ça, c'est pour ouvrir un fichier. Je peux aussi ouvrir Le répertoire dans lequel je suis, ça ouvre le répertoire et je passe en direct. Par exemple, si j'étais dans ce repository Emacs Leuven, imaginons que je veux faire un guide status. J'ai aussi des alias évidemment pour... Si je veux faire un Git status, je vois qu'il y a ces fichiers-là. Je pourrais éventuellement vouloir garder les modifications. Je vais les faire avec Emacs. Je pourrais faire simplement up point pour me retrouver directement ici, dans le bon répertoire. Et comme ça, là, je vais faire, chez moi, je vais faire C-<f9>, qui va me lancer vc-dir sans me poser la question de savoir où je veux lancer vc-dir. Donc, mon C-<f9> ne prompte pas, il lance vc-dir sur le répertoire ou le fichier dans lequel je suis. Et donc, c'est avec ça que je commencerai. Ici, je regarderai les différences avec un égal. Je vais voir si c'est bon ou pas, puis je vais committer. Donc ça, c'est pour passer du shell vers Emacs. J’ai le petit e. Beaucoup de gens connaissent emacsclient, donc ce n'est pas très nouveau. J'ai juste abrévié en e. Deux choses qui sont plus marrantes ou plus utiles. Il y en a une qui est marrante et une qui est utile. Celle qui est marrante, c'est cate, qui est une petite fonction dans le terminal, un petit shell script, et qui fait un cate.

Sacha: Cat plus Emacs.

Fabrice: cat de ce qu'il y a dans Emacs. Donc, si je fais cate, il va en fait m'afficher le contenu du buffer working-dir. C'est génial. Si j'ai ce todo-là, par exemple, je peux faire ici si je fais cate, ou si j'en avais plusieurs, on peut imaginer que j'ai plusieurs fenêtres. Donc, si je vais ici, voilà. Pour l'instant, la fenêtre active, c'est celle-là. Donc, si je fais Cat, Cat, Ça va me remontrer ça. Si je mets mon curseur là et que je refais cate, ça va m'afficher l'autre fichier. Alors ça c'est plus... Et alors c'est emacsclient aussi. Alors ça c'est plus marrant, entre guillemets, parce que ça n'a pas beaucoup d'intérêt, sauf éventuellement, parfois. Imaginons que je ne sache pas faire un grep. Il y a des opérations que je connaîtrais mieux dans le terminal. Par exemple, compter le nombre de lignes. J'ai un petit... Je suis sous et j’ai un global w qui va me faire le compte. Donc ici, je vais savoir directement, voilà, j'ai 1397 lignes dans ce fichier-là. Ce qui revient à faire, juste pour expliquer, ça revient à faire pipe wc -l. Donc voilà, je pourrais faire ici aussi un grep, éventuellement, grep, je ne sais pas, test, je ne sais pas si j'ai... Voilà. Donc il y a toutes les lignes qui sont dans ce buffer là avec le mot test. Donc ça c'est plus marrant entre guillemets, c'est pas tout le temps utile. Par contre un qui est extrêmement utile, c'est imaginons que je sois, je vais ici me redéplacer dans mon route, dans mon home directory. Je suis dans le home directory et en fait ici dans Emacs, j'ai ouvert ce fichier là. Et j'aimerais bien faire le status, par exemple, le guide status, dans ce repo-là. Mais donc, il faut que j'aille dans le repo. Donc ici, si je fais C-x C-k, avec direct plus, je sais passer à la vue directe et je vois que le chemin d'accès, donc le chemin, c'est ~/.dotfiles/emacs-leuven. Donc, je pourrais faire ici cd slash... En fait, ça, ce qui est juste au-dessus. Mais si je veux l'automatiser, en fait, je peux faire cde. Donc cde, comme cate, c’était cat de ce qu’il y a dans Emacs, cde, c’est cd de ce qu’il y a dans Emacs. Donc cde va me conduire là où je suis dans Emacs. Donc dans le même répertoire, voilà.

Sacha: Je vais voler toutes les idées.

Fabrice: C'est le but, parce que j'ai trouvé quelque part cette idée, et je l'ai peut-être un peu modifiée, mais c'est génial. Donc ici, maintenant, si je vais sur n'importe quel fichier, je ne sais pas si je prends de nouveau notre exemple ici, Readme. Est-ce que vous voyez assez grand ? Est-ce que je peux augmenter la taille aussi ? De la fonte. Donc ici, je suis dans le fichier que j'ai préparé pour cette présentation. C'est un autre repository. Imaginons que dans mon shell, je veuille retourner dans ce repository, cde. Et je suis dans le repository Emacs, Sacha Chua. Je peux faire mon guide status. Je suis directement bien placé. Je n'ai pas besoin de devoir taper des chemins d'accès.

Sacha: Tu as aussi un script pour récupérer ou retrouver le nombre de fichiers qui est courant en Emacs dans la command line?

Fabrice: Je ne l'ai pas, mais ce serait facile à faire puisqu'en fait, il y a ici ce que je... Imaginons que je veuille le faire. Je vais prendre quelques touches en plus. Je vais faire C-x C-j. Et puis... Je ne sais pas si vous savez, w, le petit w, ça me copie. Donc, le contrôler, il me place dans direct sur le fichier en question. Et w, dans le presse-papier, le nom du fichier. Je pourrais faire cat. C'est facile d'implementer. Ce serait facile, parce qu'ici, je l'ai récupéré. cat, ceci. Donc j'ai directement le fichier comme ça. Donc ce serait extrêmement facile. Plus dur, ce sera de lui trouver un nom. À cette commande.

Sacha: Génial, génial.

28:49 org-html-themes

Fabrice: Non, vas-y, j'allais éventuellement, maintenant on était dans, j'allais éventuellement parler d'export HTML, etc.

Sacha: Oui, oui, oui.

Fabrice: Donc vous voyez ici, on est dans le buffer Emacs, alors c'est déjà agréable à lire parce qu'il y a un certain nombre de couleurs, etc. J'aimerais bien d'ailleurs ici que les notes soient colorisées avec une certaine couleur, mais bon, ce sera pour une prochaine fois. Mais il y a évidemment un moyen de lire qui est quand même beaucoup plus agréable, c'est de lire ce même document. qui est assez long, ce serait de le lire en HTML ou en PDF. Et donc, en rajoutant un setup file, donc ici simplement une ligne comme ça. Donc, il faut avoir cloné le repository à org-html-themes. Et alors, après, il faut taper le chemin d'accès vers le HTML thème readtheorg.setup. readtheorg est un des deux thèmes HTML que j'ai écrit. Et donc, simplement en mettant une ligne comme ça, quand on va faire C-c C-e h h, ça va générer. Voilà, c'est déjà fait. On voit ici dans l'écho area, ça va générer la version HTML. Et donc, oui, pour aller l'ouvrir, il y aurait moyen d'aller encore plus vite, etc. Ce que je fais encore toujours pour l'instant, je fais C-x C-j, je passe dans Dired. Et dans Dired, j'ai un petit raccourci qui est extrêmement pratique, que j'ai nommé pour l’instant o pour Open. Et en fait, ça ouvre le fichier sur lequel je suis en utilisant l'application standard Windows. Donc, c'est ça aussi l'intérêt ici. Donc, ça montre bien qu'avec WSL, je suis intégré avec mon Windows. Donc, en faisant O sur ce fichier-là, Ça va m'ouvrir Edge. Si je fais O sur le fichier PDF, ça va m'ouvrir sous Matra PDF. Parce que j'ai configuré dans Windows que mon lecteur, c'est sous Sumatra PDF. Et donc ici, je vais faire O. Et ça doit m'ouvrir ici. Alors voilà, ça m'ouvre un fichier. Vous voyez ? Et donc ça m'ouvre le fichier avec... Le fichier Org, mais avec une présentation beaucoup plus sympathique, beaucoup plus jolie, beaucoup plus agréable à lire.

Sacha: Je l'adore.

Fabrice: Elle est géniale, elle est magnifique. Et en fait, il y en a une deuxième, et je vais vous montrer des petits trucs de la deuxième, parce que je n'ai pas encore tout mis dans celle-ci. J'aurais bien porté des choses que j'avais créées dans l'autre, mais que je n'ai pas encore portées, donc j'aurais bien les partager dans tous les thèmes. Si je retourne ici dans mon fichier Org et que je mets Big Blow à la place, c'est le nom de l'autre thème. Je vais refaire un export. Cette fois-ci, je n'ai plus qu'à faire F5 ici pour faire un refresh. On a une autre vue où là, chaque section devient un tab. On a tous des tabs. Donc ça peut être aussi intéressant en fonction de la taille du fichier. Ça peut être aussi intéressant de voir une vue plus monolithique où on a tout en une seule page ou une vue avec des tables ici où on voit section par section ou chapitre par chapitre. Mais alors cette vue-ci, elle a des trucs un peu spéciaux. Donc j'ai notamment la possibilité de faire un collapse. On va aller à un endroit où j'ai ici... Quelques astuces, voilà. Donc j'ai collapse all ou extend all. Donc je peux collapser toutes mes sections. En fait, j'ai voulu faire comme dans Org. Et j'ai mes petites flèches ici. Donc je peux, en cliquant sur n'importe quel item, je peux l'expander ou le collapser. Donc si j'expand ou je collapse.

Sacha: Pardon, je vais apporter des tissus à ma fille.

Prot: C'est effectif comme ça, oui. Et tu as d'autres thèmes aussi ?

Fabrice: Non, pas encore publics. J'en ai en deux, mais ils ne sont pas encore publics. Et donc voilà, ça peut être intéressant quand on a beaucoup de tâches, etc., pour pouvoir de nouveau, un peu comme dans le fichier Org, de collapser tout sauf un certain truc. Et alors, j'avais mis exprès ici, donc là j'ai deux tâches, j'ai fait deux tâches, en todo et en startit, avec des petites tags. Et en fait, j'ai même... Ici, un petit dashboard sur le côté. Il y a un petit truc dashboard qui s'est mis quand on l'ouvre. On voit le nombre de tâches en todo, le nombre de tâches en started. Donc, dynamiquement, ça va questionner, ça va afficher tous les statuts qui existent. Si j'en ai que deux dans tout mon fichier, c'était pour un petit exemple. Là, pareil, j'ai tous les tags et on peut même highlighter carrément tous les trucs qui sont en todo. C'est des flags, en fait. On peut les highlighter. Je peux highlighter et ça prend la couleur du todo. Comme ça, si je veux scanner tous les trucs qui seraient en todo, je peux les mettre en todo et je peux scanner, quand j'ai un long fichier, je peux scanner tout ce qu'il y a dedans. Et ça marche aussi, si je me souviens bien, avec les petits tags. Je peux highlighter toutes les tâches qui ont le tag personnel ou toutes les tâches qui ont le tag work d'un coup. Ça me permet de scanner, de regarder ce qui est important. C'est un peu un fonctionnement à la Org, mais dans le browser.

35:02 Review

Prot: Qu’est-ce que ça veut dire r ? r, or click here to review. Exactement.

Fabrice: C'est ce que je voulais te montrer. J'ai un petit truc en plus pour faire un vrai review. Si je clique sur r, ça va faire comme un narrowing. Tout le reste de l'écran va devenir foncer. Sauf la tâche, une tâche à la fois. Et donc ça va aller chaque fois sur la next tâche. Donc ici, quand je vais cliquer sur R, il va passer à la tâche suivante, qui est active, c'est-à-dire ici la tâche qui sera en start-it. Et donc ça collapse tout le reste. Et si je refais un r, ici c’est bloqué, donc je peux faire q pour quitter. Donc r, il va à la première tâche, et donc je vois cette première tâche ouverte, et puis il va à la deuxième. Donc on pourrait passer, scanner une par une toutes les tâches. Et ça va chaque fois mettre en évidence uniquement cette tâche-là. Et donc, je peux faire grand R, donc c’est pour faire le reverse, donc aller en arrière. Donc r, ça va vers le suivant, la tâche suivante. Si j'en ai que deux, on pourrait imaginer que j'en ai 100 dans un fichier. Et donc, je peux faire mon review comme ça et relire le truc et puis éventuellement changer. Ce qui serait génial, c'est de pouvoir changer le statut ici, etc. aussi. Et que ce soit répercuté vers le fichier Org, mais ce n'est pas le cas. C'est un petit ensemble de Org ici. J'ai aussi là un petit mini-talk, comme la mini-talk dans les chapitres LaTeX. Et donc vous voyez que simplement en mettant une ligne ici, BigBlow ou ReadTheDocs, On a accès à l'une ou l'autre. Je génère l'autre version. F5, voilà. Ici, il y a un petit truc de recherche en plus que je n'ai pas dans l'autre. J'ai des petites différences dans les deux thèmes. Et donc, ça, c'est l'export vers HTML. L'idée, c'est d'avoir... J'ai mis une référence ici, vers le repo. Et l'idée, c'est d'avoir 0 CSS à écrire. Une navigation confortable, une lisibilité plus grande, un rendu professionnel.

37:25 PDF

Fabrice: On peut carrément imprimer ça. Voilà, une personnalisation qui est assez simple. J'ai fait aussi un petit repository pour l'export vers la tech. Donc ici, si j'exporte, je l'ai déjà fait, mais je peux directement l'ouvrir. Oh, donc je vais faire de nouveau... Oh, et ça va ouvrir l'application dans Windows qui est liée pour les PDF. Chez moi, c'est sous Sumatra PDF. Voilà. Et donc, ça m'ouvre ici mon fichier. Cette fois-ci, comme un PDF, avec une table des contenus, une table des matières. Et voilà, j'ai essayé de nouveau de faire les notes, les types, etc. Donc, de les faire un rendu qui soit assez joli. Tout à l'heure, on était ici dans Emacs Leuven Boost. Et donc ça, c'est aussi public. C'est une classe qui s'appelle mcreport. Je l'ai appelé mcreport pour l'instant. Donc, il faut juste charger la première fois. Il faut charger, donc la première fois que je lance Emacs, je devrais le faire automatiquement, mais pour l'instant je ne l'ai pas encore fait. Je dois le faire une fois à chaque session. Je vais évaluer ce buffer-ci dans lequel j'ai les définitions de mes différents... de mcarticles, de mcreports, de mcbooks, etc. Donc je dois juste évaluer une fois C'est un package. C'est des accords sur une guitare où on peut gratter plusieurs cordes sur une guitare. Ici, l'idée, c'est qu'on peut appeler une commande avec deux touches simultanées. Donc, par exemple, eval-buffer. Donc ici, quand je dois évaluer ceci, je ferais, si je faisais comme avant, je ferais M-x, eval-buffer, Enter.

40:03 Key-chord

Fabrice: Mais en fait, avec key-chord, je peux simplement faire vb en même temps. Donc, si je fais vb, il y a genre 100 ou 200 millisecondes. Donc ça doit être fait quasiment simultanément. Et le b peut être avant le v ou le v avant le b. Et donc quand je fais les deux en même temps, vb, ça fait eval-buffer. Si vous voulez, alors ici on voit rien, c'est pas très visuel. Si vous voulez voir un truc, hh par exemple, quand je tape hh rapidement, ça fait la sélection d'abord du mot. Si je retape rapidement, ça grandit chaque fois la région. Donc ça fait un expand region. Et grand H, ça fait l’inverse, je retourne vers du plus petit au plus petit. xk par exemple, si je tape xk, ça va faire kill-buffer. Donc c'est chaque fois juste, si vous voulez voir dans ma configuration, où ça se trouve, Emacs Levenel, XK par exemple, voilà. Donc c’est là que j’ai mis other window, ww, xj, ça fait le C-x C-k, donc le direct jump. Et le eval-buffer dont j’ai parlé, où est-ce qu'il est ? On va mettre un peu… Donc eval-buffer, voilà, vb, ça fait eval-buffer. Donc pratique ça pour écrire en français. Quand j'écris plus petit, plus petit, deux fois rapidement, donc il faut que ce soit chaque fois rapidement, ça va faire un insert de caractères guillemets français. Donc ici, je vais faire si je le fais lentement, il ne se passe rien. Si je le fais vite, il insère les guillemets. D'accord. Tant que j'y suis, vous avez vu aussi, si je fais plus petit, plus petit ici, et puis je fais un save, vous voyez que j'ai à ce moment-là ici une ligne bleue dans le gutter, dans le fringe. Donc j'utilise git-gutter, si je me souviens bien, de Dmitry, j'ai oublié son nom de famille. C'est diff-hl, de Dmitry Gutov.

Sacha: Moi aussi.

Fabrice: Ouais, ça doit être ça. Et donc ça me met en fonction, si j'efface ici des lignes, ça va mettre du rouge. Et si je fais une insertion, et si je fais des insertions, ça va mettre du vert. Donc je vois directement dans mon buffer, systématiquement, où est-ce que j'ai des choses qui ne sont pas encore commitées. Et alors pour, ça c'est la première customisation que j'ai indiquée, si je retourne dans mon... Je peux en même temps parler de F5 et F6. J'ai quelques trucs assez sympathiques. F5, ça va me faire... Si j'ai un seul window, quand je fais F5, ça me crée un deuxième window. Ça fait l’équivalent de C-x 2 ou de C-x 3. En fonction de la place que j'ai. Donc si j'ai assez de place pour avoir 80 caractères présentés, il va passer à un mode vertical, côte à côte. Sinon, si j'ai moins d'espace, il va passer sur un mode dessus-dessous. Et si je suis à l'inverse, dans une situation avec plusieurs buffers, avec plusieurs windows, donc ici j'en avais encore une troisième, quand je fais F5, en fait, c'est l'équivalent de faire C-x 1, donc ça va faire Ça va garder uniquement la fenêtre qui était active. Et donc souvent, quand je dois passer d'un buffer à l'autre, quand je veux ouvrir autre chose, je vais faire F5, ça me crée un deuxième buffer. Et puis je vais mettre le contenu ici d'autres choses. Je vais retourner sur ma présentation. Et je voulais parler, par exemple, du C-w et du M-w sans sélectionner de région. Ça, pour moi, c'est un des trucs les plus... Les plus importants, que j'utilise tout le temps, tout le temps, tout le temps. Donc, imaginons que je veuille... Si je veux copier cette région-là, je dois commencer à sélectionner une région, et puis je vais pouvoir faire un copie. Si je veux, en fait, si je me rends compte... Imaginons que je veuille faire une ligne qui ressemble à celle-là, celle-ci, avec le kk. Imaginons que je veuille aussi faire qq, que qq ait la même... Appel la même commande. Donc je pourrais dupliquer la ligne. J'ai aussi une petite commande pour dupliquer les lignes. Et sinon, en fait, il faut que je copie cette ligne-là. Dans un Emacs standard, pour copier la ligne, je dois aller au début de la ligne, faire la sélection, aller à la fin de la ligne et puis faire M-w. Et à ce moment-là, je peux la copier. En fait, c'est trop long pour moi. Donc j'étais là au milieu et donc je veux copier la ligne. Simplement, je vais prendre un autre exemple, je vais prendre celle-là. Je fais M-w. Et M-w, donc j’ai un advice au-dessus de M-w. M-w, s’il n’y a pas de région, il considère que la région, c'est la ligne courante. Donc ici, si j’insère, je fais C-y, voilà. J'ai la ligne que j'avais mise dans le presse-papier.

45:41 Copier sans sélectionner

Fabrice: Et ça, ça sauve en temps fou de pouvoir... C'est quand je veux copier une ligne ou à l'inverse couper une ligne parfois. C'est le même principe. Donc le C-w, donc le cut, si on parle en Windows. Donc si je veux faire un cut de cette ligne-là, de nouveau, si je n'ai pas cette customisation-là, je vais devoir, au début de la ligne, sélectionner la région et puis je peux faire C-w. Mais avec la petite customisation, avec l'advice que j'ai, sur n'importe quelle ligne, je suis au milieu, C-w, ça fait le cut. Et donc ça m'arrive très souvent, si je veux supprimer, par exemple, ces trois lignes-là, alors je pourrais me remettre au début, puis sélectionner ma région, faire C-w. Ça ne me dérange pas que ça aille dans le presse-papier. Ou encore plus rapidement, je suis là-dessus et ces trois lignes qui ne m'intéressent plus, je fais C-w C-w C-w. Et les trois lignes ont disparu. C'est plus facile. C'est beaucoup plus rapide, c'est beaucoup plus facile. Le fait de pouvoir faire un copier ou un couper sans sélection. De nouveau, je reviens ici, je fais un M-w, et puis je vais là, C-y. Heureusement que j'ai Git, parce que là, je suis en train de casser mon fichier. Ce n'est pas grave parce que je vais faire un revert après. Mais bon, voilà, ça, c'est un truc extrêmement utile pour moi, ce C-w, M-w. Donc, j’ai mis un lien. Ceux qui veulent aller chercher les notes de cette présentation, j'ai mis un lien directement vers L'endroit, ça se trouve dans mon fichier de config Emacs qui est sur GitHub. Ça va ouvrir directement la section deletion and killing. Vous pourrez avoir le code. Ici, voilà le code qu'il faut copier si vous le voulez. Ça, c'est pour le cut. C'est un slick cut, ce que ça s'appelle. Et puis ici, pareil pour le copier. Donc le M-w, il faut copier ça. Et donc ça regarde, en fait, est-ce qu'il y a une région ou pas. Et s'il n'y a pas de région active, ça sélectionne la ligne courante.

Sacha: J'adore ce genre de fonction, do what I mean.

Fabrice: Oui, j'en ai plusieurs autres, mais exactement.

48:29 fill-paragraph

Fabrice: Par exemple, un autre do what I mean, je l'ai pour le M-q. Imaginons ici que je vais rajouter... Tiens, j'ai des trucs dans le presse-papiers, je peux les mettre là. J'avais upcase, la region. Donc maintenant, si je veux remettre en forme le paragraphe, je vais faire metacue. Et en fait, imaginons par exemple que je veuille... En fait, ça, c'était un paragraphe qui pourrait être beaucoup plus long. Je vais faire loremipsum. J'ai un yasnippet qui va me faire un message un peu plus long. Et imaginons que je veuille remettre ça en une seule ligne, parce que je dois copier ça, par exemple, dans Outlook. Et dans Outlook, ce sera moche si j'ai des retours dans la ligne physique à chaque fois. Ou si je veux le copier dans WhatsApp, un message. Donc, ce que je vais faire, en fait, je vais faire M-q. Il va faire l'inverse, il va toggler. Donc, mon M-q à moi, il tog… Donc, la première fois, il fait un M-q normal et la deuxième fois, il passe à la version sur une seule ligne. Quand je refais M-q, je repasse en version multiligne. Une seule ligne, plusieurs lignes, une seule ligne, plusieurs lignes.

Sacha: Donc, ça fait un toggle.

Fabrice: Donc, ça regarde la dernière commande. Si la dernière commande est déjà le M-q, alors je refais le M-q, mais dans l’autre sens.

50:02 Les macros

Fabrice: J'ai la même chose pour les macros. Maintenant, il y a F3 et F4 qui, depuis une dizaine d'années, ont été bindés pour les macros. J’utilisais Shift F8 pour définir une macro et F8 pour l'exécuter. Donc Shift F8. Imaginons que je veuille supprimer les deux premiers caractères de la ligne. Ça n'a aucun sens, mais c'est pour l'exemple. Je vais faire Shift F8. Voilà. Non, je me suis trompé. Shift F8. Et donc on voit Define Keyboard Macro. Je vais supprimer les deux premiers caractères. Je passe à une suivante. Et je refais Shift F8. Et parce que j'étais en mode définition de macro, il sait qu'il doit arrêter la macro. Et puis pour l'exécution, ce sera juste F8 tout seul.

51:02 Volatile highlight

Fabrice: Un truc aussi extrêmement utile, je trouve, visuellement, c'est si je vais faire un undo, maintenant, les deux premières lignes de chaque phrase vont se remettre, de chaque ligne vont se remettre. Mais en fait, j'utilise Volatile Highlight qui va me montrer ce qui vient de changer. Quand je ne fais pas de mouvement dans le buffer, je vois qu'il vient de rajouter gr. Et si je refais undo, undo, undo, undo. Je vois chaque fois la modification qu'il a faite dans le buffer. Donc quand on fait des yanks, des copies, etc., des insertions ou des replays, on va voir, volatile-highlight va montrer juste comment j'ai déplacé mon caractère. Et ça, c'est très utile pour voir un peu visuellement qu'est-ce qui s'est passé. C'était la même chose avec mon metacue. Si je fais un undo, si je refais le metacue ici et que je fais un undo, voilà. On voit les insertions d'un retour en ligne à chaque fin de ligne. Il ne reste que quelques minutes. Je n'aurais pas le temps de montrer tout ce que j'ai imaginé. Je ne sais pas si vous avez des questions. Sinon, j'ai encore un truc que je voudrais montrer qui est assez utile. Et puis le reste vous pourrez lire.

Prot: Oui, je crois que c'est OK. Pardon ? Oui, je crois que c'est OK pour toi de démontrer. Parce que si on a des questions, on peut voir le Elisp que tu as écrit.

Fabrice: Tout à fait.

Prot: Tout est public.

52:47 Les tableaux Org

Fabrice: Un truc qui m'intéresse dans les fichiers Org, j'utilise beaucoup les tableaux. Par exemple, mes factures que je fais chaque mois. Je l'ai fait avec Org. J'ai un fichier qui ressemble à ça. Ici, j'ai mis dans le fichier de note de cette présentation, j'ai mis un petit exemple avec des personnes, des prix. Ici, on a un total de 2250 euros. Et ça, c'est un prix hors TVA. Ici, j'ai une autre table qui va appliquer la TVA. Elle reprend. Ici, j'ai un remote. Donc je vais chercher dans la carte participant, je vais chercher ce montant là. Et donc il vient se copier là tout seul. Et sur ce montant là, j'applique une TVA à 21%. Donc j'ai le montant, toutes charges comprises, donc avec la TVA. Et donc le truc que je trouve embêtant, par défaut, enfin qui m'embête moi, c'est que potentiellement, je vais dire en fait non, et si c'était pas 500, c'était 1500. Et je vais sauver mon buffer. Et en fait, c'est totalement incohérent maintenant, puisque la somme n'est plus correcte. Et en plus, un tableau qui se trouve ici juste derrière, mais qui pourrait se trouver totalement ailleurs dans le document, n'est plus du tout à jour non plus. Et si je fais un export, je vais publier un document qui sera totalement avec des fautes, en fait. Et donc, j'ai surchargé le Save Buffer dans le mode Org. Et dans le mode Org, quand ça fait Save Buffer, ça fait aussi le recalcul de toutes les tables dynamiques, le recalcul de tous les blocs dynamiques, pardon, le recalcul de toutes les tables et aussi le réalignement des tags. Par exemple, ici, je vais mettre un tag. Tout ça, vous verrez en même temps. Mon tag. Les tags, par défaut, sont alignés à 80 caractères. À la fin, ici, il y a une petite ligne grise. Je ne sais pas si vous la voyez bien. Elle est assez fine. Maintenant, quand je vais faire simplement save, chez moi, c'est F2, ça va calculer les montants. On va voir que ce montant-là va être changé, ce montant-là, celui-là et celui-là. Je vais faire save ici. Et mon tag a été réaligné aussi. 80 caractères, sauf qu'ici j'ai une présentation avec les deux chiffres, c'est pour ça qu'il y a un petit décalage. Donc de nouveau, si je mets 11500, C-x C-s, je suis sûr que mon document est correct. Je n'ai pas d'information incorrecte de table avec des erreurs, simplement parce que j'aurais oublié. Vous m'entendez encore ?

Prot: Sacha, je ne peux pas t'écouter.

Sacha: Je suis muette.

55:43 Les formations et les cours particuliers

Sacha: Ma fille s'est réveillée, donc je veux consacrer du temps pour la publicité de ta formation avant qu'elle devenue grincheuse. Si c'est acceptable, dites-nous sur les formations en présentiel.

Fabrice: Je fais effectivement des formations sur Emacs parce que je suis tellement passionné par cet outil et que je le trouve magnifique, je le trouve extrêmement puissant. que j'ai envie d'aider des gens à pouvoir s'en servir de la même manière que moi. Donc l'idée, c'est de montrer tout ce que j'utilise, un peu comme je le fais maintenant, mais ici, c'est avec quelques petits éléments à gauche, à droite. Et donc, j'aimerais bien en faire tout le temps, mais bon, j'ai fait pour l'instant déjà trois formations, deux à Paris, une à Rotterdam, et je compte en refaire une soit en Q4 d'RGSI, J'avais quelques notes, voilà. Soit en Q4 2026 ou alors probablement plutôt le Q1 2027, en refaire une autre à Paris, où l'idée, c'est en fait d'apprendre aux gens à utiliser Emacs et de montrer, comme je le fais un petit peu ici, montrer les packages que j'utilise et qu'est-ce qu'ils apportent, donc qu'est-ce qu'ils font de plus, qu'est-ce qu'ils permettent d'apporter plus, comme donc le keychords, le volatile-highlight. Les petites fonctions que j'ai écrites sur le recalcul des tables, par exemple sur Org, le M-w, C-w qui sélectionne la ligne d’office s'il n'y a pas de région, tout ça en fait, le but c'est de le montrer, de l'expliquer et que les gens puissent ou bien utiliser mon Emacs Leven tel quel et puis connaître tout ce qu'il y a dedans ou bien aller piquer des choses intéressantes qui sont dans ce fichier-là.

Sacha: Est-ce que tu es aussi disponible pour les cours particuliers comme le coaching de Prot ? Tu offres aussi les cours particuliers ?

Fabrice: Absolument, absolument. Je suis passionné par Emacs et donc effectivement, des cours particuliers, je suis aussi tout à fait disponible à le faire. C'est le même enthousiasme pour... En français et en anglais. En français, en anglais, en irlandais et en espagnol.

Sacha: C'est très cool. Tu as un autre rendez-vous. J'ai un fil. Merci beaucoup à Fabrice et à Prot.

Prot: Merci, merci Sacha et Fabrice.

Fabrice: Merci à vous. Merci beaucoup pour cette invitation. J'espère que ça donnera quelques idées. Je suis disponible pour... Pour toute question supplémentaire ou pour toute info supplémentaire, voilà, un grand plaisir.

Sacha: À la prochaine, je pense que nous devons à une autre conversation.

Fabrice: Avec grand plaisir. À la prochaine. Merci beaucoup.

Prot: Au revoir, au revoir, à bientôt.

Fabrice: Merci, au revoir.

View Org source for this post

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

-1:-- Emacs Chat 28: Fabrice Niessen (en français) (Post Sacha Chua)--L0--C0--2026-08-14T00:13:37.000Z

Magnus: Emacs salmagundi, 2026-08-13

Here's a few minor tweaks I've made to my Emacs setup recently.

Limiting enabled tree-sitter modes

After reading Rahul's post Emacs 31 Is Around the Corner: The Changes I'm Already Daily Driving I simplified my setup and turned on all the tree-sitter modes

(treesit-enabled-modes t)

I regretted that as soon as I opened a YAML file though. It turns out the yaml-ts-mode is rather unusable.1 I switched back to yaml-mode and adjusted the value of treesit-enabled-modes to include what I have use for (the change):

(treesit-enabled-modes '(bash-ts-mode
                         c++-ts-mode
                         c-or-c++-ts-mode
                         c-ts-mode
                         dockerfile-ts-mode
                         go-mod-ts-mode
                         go-ts-mode
                         go-work-ts-mode
                         json-ts-mode
                         python-ts-mode
                         rust-ts-mode
                         typescript-ts-mode))

The customization of treesit-enabled-modes contains a list of all the supported modes.

I hope someone will take pity on yaml-ts-mode and make it usable soon.

Tree-sitter grammar for Cabal files

When I began tinkering with my own treesitter-based mode for Haskell I failed to find a tree-sitter grammar for Cabal files so I wrote a rather rough one myself. My hope was that it could serve as a base, or motivation, for someone else to write a proper one. It took more than 2 years, but now it's finally happened!

I've switched my mode to use Curtis's grammar and I'll be archiving mine. (The change.)

Hello editable xref, good bye wgrep

Rahul's post also mentions that editable xref buffers. I had the same itch to have editable xref buffers, and I'd added a function to turn them into grep buffers to be able to edit them. I've now switched to using the built-in support, and removed the function.

At the same time I also decided to remove wgrep and instead use the builtin function for editing grep buffers.

Footnotes:

1

A mode for an indentation-sensitive language really ought to at least handle indentation!

-1:-- Emacs salmagundi, 2026-08-13 (Post Magnus)--L0--C0--2026-08-13T19:44:00.000Z

Sacha Chua: 27 août: Emacs Chat: Fabrice Niessen (en français, partie 2)

Nous allons à nouveau parler en direct avec Fabrice Niessen d'Emacs, de ses flux de travail, de sa configuration et d'autres sujets en français. Voici ses notes.

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.

Voici notre conversation précédente :

View Org source for this post

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

-1:-- 27 août: Emacs Chat: Fabrice Niessen (en français, partie 2) (Post Sacha Chua)--L0--C0--2026-08-13T15:58:31.000Z

Raymond Zeitler: One Hundred Times the Highest Priority

Please note that org‑get‑cust‑priority has been updated. The code that was published on 2026-07-10 causes org‑tags‑list to crash.

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 the function
`org-priority-get-priority-function' is set to."
  (interactive)
  (if (not (functionp org-priority-get-priority-function))
      (org-get-std-priority s)
    (string-match ".*?\\(\\[#\\(!\\)\\] ?\\)" s)
    (if (and (string-match ".*?\\(\\[#\\(!\\)\\] ?\\)" s) (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.


2026-08-13 Update org-get-cust-priority. Previous version breaks org-tags-list.

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-08-13T15:34:56.336Z

Irreal: Some Thursday Humor

I don’t see anything in my feed worth writing about so I offer this bit of humor to help you get through the day. If you’ve ever wondered why an S-expressions is called an sexp, here’s your answer.

Like all humor, there’s a bit of truth here. Not the part about sexp, of course, but the notion that many of us come to Emacs for some particular feature, venture too close to the init.el event horizon, and never escape.

-1:-- Some Thursday Humor (Post Irreal)--L0--C0--2026-08-13T15:05:22.000Z

The Emacs Cat: Emacs, Dired, and Ubuntu 26.04

Just recently I upgraded one of my Dell laptops to the latest Ubuntu 26.04 LTS with a clean install. The installation process was smooth and enjoyable.

Ubuntu 26.04 ships with Emacs 30.2, which made me happy, so I installed it immediately—more precisely, the emacs-pgtk Wayland-optimized package, since Wayland is the default windowing system on Ubuntu 26.04.

The Problem

I started Emacs and, after a short time, ran into a problem: Dired (C-x d) did not work properly. It displayed a strange mess of unsorted, chaotic entries, as shown below. It was a big surprise.

Click or tap to view the full-size picture.

I’ve been using the same .emacs multi-platform config file for Linux, macOS, and Windows for years and have never had any problems with Dired on different platforms, let alone across versions of the same OS.

My Dired configuration is quite simple:

;;; `DIRED'
(require 'dired)

;; Dired options for Windows and non-Windows (Linux, macOS) systems.
;; http://oremacs.com/2015/01/13/dired-options/
(if (string-equal system-type "windows-nt")
    (setq dired-listing-switches "/A:H /O:NG")
  (setq dired-listing-switches "-laGgh1v --group-directories-first --time-style=long-iso"))

;; `dired-subtree' (since 2022-10-18)
;; https://xenodium.com/drill-down-emacs-dired-with-dired-subtree/
(use-package dired-subtree
  :ensure t
  :after dired
  :config
  (bind-key "<tab>" #'dired-subtree-toggle dired-mode-map)
  (bind-key "<backtab>" #'dired-subtree-cycle dired-mode-map))

;; By default, Dired buffers will not update themselves
;; unless you explicitly tell them to.
(setq dired-auto-revert-buffer t)

The dired-listing-switches variable defines the switches passed to dir or ls for Dired on Windows and Linux/macOS systems, respectively. I’ve been using this configuration for years.

On Ubuntu 24.04 everything looks fine. So, what’s wrong with Emacs 30.2?

I decided to test the ls switches in the console. Oops—the same result as in Dired under Emacs 30.2!

Click or tap to view the full-size picture.

This means the problem is not with the new version of Emacs. After a short search, the answer was found.

The Bug in Ubuntu 26.04

It turns out the bug in Ubuntu 26.04 is real, documented, and already tracked in Ubuntu’s Rust-based coreutils for Ubuntu 26.04. The issue specifically affects ls when using --group-directories-first and is tied to missing locale collation support in the Rust rewrite.

Launchpad Bug 2154042 — Incorrect alphabetical sorting with --group-directories-first

This is the main bug report affecting Ubuntu 26.04 users after the Rust coreutils transition. It explicitly states that:

  1. ls from the Rust-based coreutils does not sort alphabetically when using --group-directories-first.
  2. The bug appears both when LC_ALL is set and when it is unset.
  3. GNU ls (gnuls) sorts correctly; Rust ls does not.

Conclusion

Since Dired—and possibly other Emacs packages—uses ls to list directories and files, Dired will not work properly on Ubuntu 26.04 regardless of the Emacs version.

Solution

Luckily, the Ubuntu team has kept the old version of coreutils (GNU coreutils), written in good old C. The GNU version of ls is gnuls and is installed by default on Ubuntu 26.04. This means we can use gnuls—which works properly—for Dired instead of ls when running Ubuntu 26.04.

To do this, we will use the insert-directory-program variable, which holds the absolute or relative name of the ls-like program. This variable is used by insert-directory and dired-insert-directory (and therefore by Dired). Its default value on Linux and macOS is ls.

First we need to determine the name and version of the OS. The custom ubuntu-version function helps with that:

(defun ubuntu-version ()
  "Return (MAJOR MINOR) as integers if running on Ubuntu, else nil."
  (when (and (eq system-type 'gnu/linux)
             (file-readable-p "/etc/os-release"))
    (with-temp-buffer
      (insert-file-contents "/etc/os-release")
      (let* ((os-release (buffer-string))
             (is-ubuntu (string-match-p "^ID=ubuntu" os-release))
             (version-id (and is-ubuntu
                              (when (string-match "VERSION_ID=\"\\([0-9]+\\)\\.\\([0-9]+\\)\"" os-release)
                                (list (string-to-number (match-string 1 os-release))
                                      (string-to-number (match-string 2 os-release)))))))
        version-id))))

On my machine ubuntu-version returns (26 4).

Now we can replace ls with gnuls if we are on Ubuntu 26.x:

(when (eq (car (ubuntu-version)) 26)
  (setq insert-directory-program "gnuls")
  (message "`ls' is replaced with `gnuls' on Ubuntu 26.x."))

After that, Dired works as before.

Click or tap to view the full-size picture.

Happy Emacsing on Ubuntu 26.04!

– The Emacs Cat.

-1:-- Emacs, Dired, and Ubuntu 26.04 (Post The Emacs Cat)--L0--C0--2026-08-13T09:55:47.000Z

Meta Redux: Projectile 3.4

Projectile 3.4 is out! After the four releases that preceded it this summer, this one is polish rather than ambition.

Most of it came out of an annoyance I’d been living with for years without ever quite naming it: a lot of what I work on isn’t one directory. CIDER is really CIDER plus cider-nrepl plus orchard plus clj-refactor plus a handful of others. RuboCop is RuboCop plus rubocop-ast plus rubocop-rails and the other extension gems plus the style guide. I’ve been maintaining families of repositories for the better part of a decade.

Lately I’ve also been reaching for git worktrees far more than I used to, so the same repository is now checked out two or three times on my disk at any given moment.

Projectile saw all of that as a pile of unrelated projects, which is exactly what it looks like from the outside.

Somewhere else in the same project

projectile-switch-worktree (s-p W) offers the other checkouts of the repository you’re in, each labelled with whatever tells it apart - the branch for git, the workspace name for Jujutsu:

Switch to worktree:
~/src/myapp-main/     (main)
~/src/myapp-hotfix/   (hotfix/crash-on-open)

Git worktrees are the obvious case. Projectile asks git about those, so a worktree you’ve never opened in Emacs shows up anyway. Jujutsu workspaces work the same way.

The case I care about more is the one without any plumbing: a second git clone of the same upstream. Same workflow, done by hand. I looked at my own ~/projects while writing this and found four separate clones of CIDER sitting in it. Nothing records those anywhere, so Projectile can’t ask git to list them - it matches them among your known projects by their remote instead.

Somewhere else in the same effort

The other half is projectile-switch-sibling-project (s-p n), which offers the projects related to the one you’re in rather than every project on the machine.

The interesting question was what “related” should mean. My first instinct was to compare directory names and look for a shared prefix. Before writing anything I tried the candidates out on my actual project directory, all ninety-odd repositories of it.

Comparing names does find rubocop, rubocop-ast and rubocop-rails. But grouping by the owner of the upstream remote finds this:

The sibling project prompt in CIDER, listing sixteen related projects including orchard, haystack, port and sayid

Half of those names have nothing in common with cider, and no amount of staring at directory names would ever have related them. haystack parses stack traces, port is a printer registry, sayid is a tracing debugger. Nothing in those names says “CIDER”.

What they do have in common is an org: they all live under clojure-emacs on GitHub. That’s not incidental, it’s how the project is actually organised - the org is the boundary of the effort, and the individual repositories are just where the pieces ended up when they got big enough to split out. Same story for the rubocop org, and for nrepl. Once I saw that, keying on the owner of the remote was obviously right, because it’s the same fact GitHub is already recording for me.

It needs a bound, though, and the same experiment showed why: about 40% of my checkouts are under my own GitHub account, and “we’re both under bbatsov” doesn’t relate anything. So an inferred group covering more than a quarter of your known projects gets dropped and the next signal takes over.1 If you’d rather just say what belongs together, projectile-project-groups is there and is never second-guessed.

Switching is only the obvious thing to do with this. Now that Projectile can tell which projects belong together, the same grouping could back a find-file or a search across the whole family - one prompt that reaches every repository in the org rather than just the one you happen to be sitting in. I go looking for “which of these fifteen repos defines this var” often enough that I suspect it’s the more useful half. Nothing built yet, but that’s where I expect this to go.

The commands come with you

Once Projectile knows two directories are the same repository, the command history can follow you between them.

Press M-p at the compile or test prompt in a worktree you made this morning and you get the commands the project is actually built with, instead of an empty history. That’s an issue from 2022 that a stale bot had helpfully closed for me at some point.

What doesn’t follow you is anything that runs without asking - what a prompt is pre-filled with, and what projectile-repeat-last-command replays. I had those shared too in the first draft, and then watched a repeat in one worktree rebuild the tree next door. A remembered command can carry absolute paths back to where it was typed.

More languages, fewer surprises

projectile-run-test-at-point (s-p c .) arrived in 3.1 knowing Python, Go and JS/TS. It now also knows Ruby (both RSpec and Minitest), Rust, Elixir, Java, Erlang and F#.

Ruby is written the same way whichever framework you use, so there the project type picks the runner rather than the syntax; Java’s picks between Maven and Gradle. Elixir tests get addressed as FILE:LINE, because ExUnit can’t select a test by name from the command line. OCaml deliberately gets nothing - its tests are ordinary values you register with Alcotest or OUnit, so there’s no syntax to recognize.

projectile-find-file-of-kind (s-p j) and projectile-toggle-related-file (s-p J) learned Phoenix, Laravel and Next.js. Rails and Django had been the only frameworks with file-kinds tables out of the box, which felt like a strange place to have stopped.

The reports answer back

The dashboard and the doctor from 3.3 both got a pass. They’re no longer plain text - section headers, field labels and findings are faced by meaning, with findings colored by severity and sorted so anything wanting your attention comes first. The faces only inherit from standard ones, so your theme styles them without knowing Projectile exists.

The doctor’s findings now come with a button that acts on them:

The doctor's findings section, with a warning about projectile-mode carrying an enable button, above four ok findings

[enable] for a projectile-mode you forgot to turn on, [enable caching] on a big uncached project, [open dirconfig], [edit .dir-locals.el]. Pressing one regenerates the report. Findings Projectile can’t act on stay plain advice.

And since a doctor report usually ends up pasted into an issue, w copies the buffer as plain text, without the faces and buttons.

Odds and ends

  • projectile-find-changed-file (s-p C) completes over what git reports as staged, unstaged or untracked - or, with a prefix argument, everything that differs from a revision you pick.
  • projectile-run-task discovers rake tasks now, read out of your Rakefile and .rake files rather than by running rake -T, which would load the whole application.
  • projectile-ignored-project-patterns is the regexp-matching sibling of projectile-ignored-projects, so keeping a whole area of your machine out of the known projects no longer needs a lambda.
  • Messages Projectile emits on its own initiative are prefixed with [Projectile] now, and the ones answering a command you just invoked aren’t. There were five different conventions in there before, which I’d somehow never noticed.
  • The known projects file, the frecency store and the session directory are resolved with locate-user-emacs-file, so they land in the right place if your configuration lives in ~/.config/emacs. Nothing moves if it doesn’t.
  • Elixir’s implementation/test toggle offers foo_test.exs rather than foo_test.ex, a script ExUnit will actually run. Project types can declare their test file extension now.

Upgrade notes

Nothing here should break a working setup, but two things are worth knowing.

A batch of options were renamed or folded together. The six projectile-<cmd>-use-comint-mode options became one projectile-use-comint-mode; projectile-per-project-compilation-buffer and projectile-per-command-compilation-buffer became projectile-compilation-buffer-scope; and a handful of options that had broken their own naming schemes were renamed to match their siblings.2 Every one of them is still honored under its old name, so your config keeps working - you’ll just see an obsolescence notice.

Two options are gone: projectile-tags-file-name and projectile-go-project-test-function were only ever read as Projectile loaded, which means setting them from your init file afterwards did precisely nothing.

And the command history is now the repository’s rather than the directory’s. Set projectile-command-history-scope to project if you’d rather have it per directory. Histories you already have are adopted, not dropped.

The full changelog is here, and the manual is at docs.projectile.mx. The cross-repository features have a page of their own, limitations included.

Five releases in six weeks is not a pace I intend to keep up, and this is the natural place for the burst to land. The big pieces from 3.0 through 3.3 have had their corners sanded down, and what’s left on my list is smaller and less interesting to write about. Which is roughly where a fifteen-year-old package ought to be.

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

  1. Which is why projectile itself comes back with no siblings on my machine. The cap is working; the answer is a configured group. 

  2. projectile-global-ignore-file-patterns, projectile-cmd-hist-ignoredups, projectile-related-files-fn-function, projectile-auto-discover, and the three reviewable-search options that were named after replace. 

-1:-- Projectile 3.4 (Post Meta Redux)--L0--C0--2026-08-13T07:30:00.000Z

Protesilaos: About Emacs themes, Solarized, and wallpapers

JTR from the blog The Art Of Not Asking Why sent me a few questions regarding Emacs themes, my opinion on the Solarized colour scheme, and even my choice of wallpaper.

JTR asks about Solarized because I consider Ethan Schoonover’s Solarized an excellent project, given what Ethan was trying to achieve. I asked JTR if I could publish my thoughts and they agreed. JTR has also written the article Back to Solarized.

Note that throughout this article I am only commenting on Ethan Schoonover’s Solarized (the original Solarized). There are many Solarized-like themes out there with lots of additions on top, including higher colour contrast, which are separate themes as far as I am concerned.

I should also point out something I always mention: themes are a personal choice and there are many factors that go into what a person wants. Keep in mind that readability is also a matter of font size, ambient light, and the screen brightness you are using.

To give a simple example, modus-operandi in a very dark room with maximum screen brightness at a fairly large font size will most probably harm you and is thus a terrible choice. But the same setup on a sunny Cyprus day in my well-light house is how you can read the screen without distractions.

Generally though, people forget to adjust their screen brightness, so always do that and rely on environmental light to not tire out your eyes.

Be mindful of the context

Have your thoughts changed about Solarized since the creation of Modus themes? Later, did they change after you created the ef-themes?

In short, no as the technological realities for my case and for the original Solarized remain the same. Ethan Schoonover wanted to have a colour scheme that would work reasonably well with terminal emulators and, by extension, Vim. Most terminal emulators let you define 16 colour values. Then applications running inside the terminal can map those to the objects they display.

Applications running inside the terminal cannot change the 16 colour values of the terminal. This means that Ethan’s Vim configuration necessarily had to have a light and a dark theme that worked with the same 16 colours. Ethan could have developed two distinct colour schemes for the terminal, which would then underpin two separate Vim themes, but I understand how this is a more complex arrangement.

My older Tempus themes project, from my pre-Emacs days, is for terminals, Vim, and friends. Same idea for its predecessor, Prot16.

I think both of those older projects are fundamentally flawed because the technological constraints they are designed to work in result in all sorts of little compromises. To give an example that many Linux users may have noticed: if you use a light theme in a terminal and do apt update && apt upgrade you will get the progress bar with a green background and a black foreground. Colour schemes cannot change that combination, so they make that text virtually unreadable (in a terminal, colour combinations should always be ACCENT + BACKGROUND, not ACCENT + BLACK/WHITE, as the accents are always designed to be visible against the background).

There cannot be one Solarized

Even though the original Solarized was designed to be a single theme, in practice it is not and cannot become as much. There is Solarized Light and Solarized Dark, sharing those same 16 colours. Solarized light has a background that is closer to yellow, while its dark counterpart’s background is closer to blue. This means that accent values which are picked to work with both eventually do not work equally well as they do not evoke the same feeling.

Without even considering the specific values of the Solarized palette, a combination of blue/cyan, magenta, and purple as foreground colours will be on the opposite side of the spectrum to the yellow background and on the same side as the blue background. This means that if we pick blue/cyan, magenta, and purple as our main colours we have a light theme (yellow) with a completely different feel than the dark theme (blue) even though they are supposed to be the “same Solarized”.

Let me outline this for you:

Foreground colours Solarized Light background Solarized Dark background
green, yellow, red/orange Same side Other side
blue/cyan, magenta, purple Other side Same side

So if we force the same mapping of foreground colours, then we have two themes with contrasting harmonies and necessarily distinct characters. In this context we begin to appreciate the importance of judgement: sometimes you achieve likeness through difference. For the table above, inter-theme likeness exists on the diagonals, so either have both themes with “same side” combinations or both themes with “other side” combinations.

This concerns the predominant colours. A theme that uses all accents in equal proportion will look very busy and thus run into other problems.

The designer can, of course, pick a colour that does not follow the overall pattern to produce an emphasis of some sort. Though they cannot start with emphasis as their baseline.

One set of values leads to compromises

Because Solarized Light and Solarized Dark are designed to share the same palette, individual values are picked as a matter of compromise, even though they are “equally good” on paper in terms of distance as measured by various luminance metrics. The blue used for the light theme, for example, must also be good enough with the dark theme. But “good enough” for both may not be optimal for either.

Concretely, the Solarized blue stands out more in the light version than in the dark version (because of the complementarity between yellow and blue). If the light effect is supposed to evoke sunlight on a surface, and if that is the reason green and yellow are desaturated, then the blue should not be as saturated for the light theme. It would have to be a considerably more washed out colour. Though the blue has to be fairly saturated to be readable on the Solarized Dark background and still look like a blue, hence the compromise.

Same idea for the green and yellow colours which are consistent with the light background but would require tweaks to be harmonious with the dark background.

The magenta does not fit any background: it stands out as an exaggeration. To be consistent with the light background, it has to be less saturated. To fit the dark background, it needs to have greater contribution from the blue channel of light (i.e. move closer to purple) or become more pink. But moving in either direction compromises the opposite theme…

There is no “bright green” (terminal colour 10), “bright yellow” (terminal colour 11), “bright blue” (terminal colour 12), and “bright cyan” (terminal colour 14). I believe this is because they would not fit both Solarized Light and Solarized Dark.

My themes are designed for Emacs

As noted, Ethan’s Solarized was developed with a specific technology in mind. The same is true for my case: I develop all my themes to work with Emacs. This typically means that I can rely on more than 16 colours and each theme can have its own colour values.

For example, if I choose to apply a green background colour to the “current match” in a search, its value is not the same green I would use for, say, a “DONE” keyword in Org. Whereas in a terminal with 16 colours what you normally do is invert background and foreground colour combinations, which results in things standing out more than they need to (and becomes overwhelming when you want to use many such elements).

Each of my themes relies on its own palette. If I tweak one theme’s colour values, I am only affecting that one theme. Because each theme is its own design, I can make things similar by making them different. For example, modus-operandi-tinted is a “yellow-red theme” (simplifying greatly now) but its counterpart modus-vivendi-tinted is a “blue-purple” theme. On paper, this description alone makes them very different. But in terms of character, they both communicate a similar feel, owning to considerations that follow from what I mentioned above.

Additionally, each theme has its own colour mappings. This means that I do not have to make every theme use, for example, blue for links. In Emacs, I have greater control over what each “application” does, by configuring the relevant faces. There still are constraints, as I cannot for example decide which faces are applied to what portion of text, but this is generally not a problem.

Differences between my theme projects

What’s the difference between prot16 and your ef-themes? They seem to cover similar ideas, but it doesn’t look like prot16 has been updated.

My older projects are for terminal emulators. It is why they were inspired by Solarized. Terminals only have colour schemes though, not themes. The designer cannot control how colours are mapped to objects, so they have no means of enforcing thematic consistency.

A colour scheme sets the 16 values, but otherwise has no mappings for things such as “here is an error message” or “this is a progress bar and things are going well”, or even to specify “I only want green and blue here, but never magenta”.

If a colour-coded message gets a style, it will typically be hardcoded to red, green, or yellow. And if the colour scheme wants to support users with blue-yellow colour deficiency, for example, it will have to add a yellow value where the nominal red is (terminal colour 1 and terminal colour 9). But doing so leads to weird issues because, fundamentally, we are conflating colour names with colour mappings (there may be an application that wants to use yellow text on a red background, but now that actually becomes yellow on yellow, so you get the idea).

In Emacs, a theme designer has more control. They can specify colour values and write exactly how they are used by the relevant faces. For example, we do not only define “blue” but also say “I want keywords in programming modes to be blue”.

The constraint in Emacs is which faces do packages define and how exactly they apply them to elements of the display. A theme cannot, for instance, dictate how many separate faces a programming mode will add to its text. Furthermore, a theme cannot govern how generic faces, like font-lock-variable-use-face are set by modes to do things they are not supposed to do (for example, a mode specifies that face because they like its green colour in the one theme the author is using, but the thing being styled is not a variable and that specific face does not produce an optimal result in its context).

Additionally, themes cannot determine the surrounding text. For example, the Org export dispatcher has keys wrapped in square brackets (and I sent a patch that was merged a long time ago to have decent colours there by default when no theme is used). The bracketing is something the theme cannot change, so it has to do strange things to produce readable text because in an [i] indicator it is hard to discern the i due to how the square brackets limit the perceived boundaries—and you need to apply a highlight so that the user knows which exporter they are picking. An [ i ] is somewhat easier in this regard and a variation of it without the square brackets is even more flexible as then the theme can apply all the colour and typography attributes it needs.

Another constraint with Emacs is that we do not have real padding and margins, the way those are understood in CSS. I think this is fine for the majority of cases, but can be a problem sometimes such as when we need to use a coloured background that spans a small portion of text (e.g. with the Org dispatcher or with the style of rendered keys like <home>).

The gist is that Emacs gives the theme designer more control and in a more integrated way than a terminal+TUIs arrangement. This results in a coherent experience. Terminal-centric environments cannot have that to the same level because of disparate applications trying to form one computing environment, each doing its own thing in the process. I tried inter-application consistency in the terminal, discovered its limitations, and is the single most important reason why I switched to Emacs.

Contrast and readability

My understanding is that the contrast for Solarized wasn’t good enough for you. I don’t know if you have my issue specifically (https://taonaw.com/2023/07/05/halatoion-and-accessiblity.html), but I’m curious: don’t you get halation as well? Doesn’t it bother you, especially if you switch from a dark theme to something lighter? (For example, you read something with a dark background and light text, then switch to a page that has a light background and dark text)?

We measure contrast between colours to have an imperfect approximation of what might work in the average scenario. Though readability is influenced by many more factors beyond the applicable colours. Consider the environmental light and the brightness of the monitor. In addition, check the font size and even the font family you are using. Many font families can produce a halo effect at different sizes, regardless of colour combinations.

Have an extra source of light beside the monitor’s. If you do not, the eye focuses on one spot, which is more tiresome. And always adjust the screen brightness to not be too bright or too dim relative to the environment.

As for fonts, you may benefit from a design that has more open shapes. The exact size is also a factor.

For example, I have a font called Aporetic that is derived from Iosevka. What motivated me to do my own thing is the original monospaced m of Iosevka (maybe it is changed now), which blurred into a box at small point sizes and was thus not nice to read.

The Hack font I used to use (and which I had also modified), had similar issues. The Ubuntu Mono font that Ubuntu and derivative Linux distributions rely on, can have a g with a very short descender at certain point sizes, as well as a dotted zero where the dot is off centre.

I can go on to great details but you get the idea: you have to check all the variables and find what works for you. I am confident in stating that this is never a theme-specific issue.

As for switching between applications, these days most desktop environments have a concept of a light/dark preference, which many websites also follow. For example, GNOME has a menu from where you can toggle the “Dark Style” on/off.

The specific problem you will encounter with Solarized is that practically no other application or website uses it or similar colours. So either you will have to set Solarized everywhere you can, or you will be switching from the yellowish/blueish background to white/black, with whatever context-switching friction that may have.

Thoughts on wallpapers

This is an extra one I’m curious about: what are your thoughts about wallpapers? I noticed, the few times I worked with you and on video, that usually you have a natural scene. Have you ever switched to something else (like a drawing, perhaps an artwork, or maybe even a technical scheme)?

I have used wallpapers of all sorts and find that pictures I take of landscapes or flowers are the best for me. The reason is that they avoid exaggerations. Many pictures that are used for wallpapers are designed to show something in a more pronounced way and are thus calling for your attention. That is fine when the object of attention is the art piece. But when I am doing work on the computer, I do not want other elements to be competing for attention with whatever it is I am working on.

“Avoiding exaggerations” is a principle that informs my lifestyle. It also is why I prefer to live in the mountains and to spend plenty of time outdoors. The mountains do not have the intense stimuli a city has. In a city there are bright lights everywhere, a faster pace to motions, and sharper noises all around. Whereas the mountains are subtle, even though they are full of life.

People who go on a retreat say “I went to this holy place and found peace”, when in reality what likely happened is that they reduced their exposure to exaggerated stimuli and were thus able to lower their baseline intensity for a little while.

Because we are also conditioned by our habits, we do not realise just how much we have gotten used to things in our milieu, even when those are not benign to us.

People want magical solutions when the basics will suffice. We are heavily influenced by our environment and the elements we are exposed to. A stressful place will increase our stress levels. A harmonious place will make us more harmonious. Inconsiderate food choices will disturb us long-term, a poor work-life balance will harm us, and so on.

People think they can brute force their way into calmness or focus by means of “truly believing” in their project. This is a fool’s errand. Changing the scenery and relevant lifestyle will be far more effective than whatever willpower one thinks they can muster on demand.

-1:-- About Emacs themes, Solarized, and wallpapers (Post Protesilaos)--L0--C0--2026-08-13T00:00:00.000Z

Raymond Zeitler: Emacs Numbered Backup Strategy

Emacs produces a backup of each file by default. It does this by appending a tilde to the file's name. For example, Myfile.txt is backed up to Myfile.txt~. It's a reasonable strategy, assuming that a Version Control system is in place.

But in the absence of version control, you're limited to restoring just the previous version; older versions need to be restored from backup.

I use git for coding projects that I share to the public. But I didn't create a repository for my home directory. So when C-c a m TAG1 generated an error instead of a list of TAGged items, I hoped fervently that the latest init file change was to blame.

After finding and fixing the problem quickly, (and realizing I was lucky this time), I resolved to be more diligent about disaster recovery. I looked into what backup schemes Emacs already provides.

Emacs can create unique backups for a file rather than overwriting the previous backup. When Emacs sees a numbered backup for a given file, it will make a new, sequentially-named file for each backup of that file, while continuing to use the "append tilde" method for other files.

Once I renamed (or copied) .emacs~ to .emacs.~1~, Emacs created backups named .emacs.~2~, .emacs.~3~ (and so on). I didn't even need to change a variable -- when Emacs detected .emacs.~1~, it knew that backups of .emacs needed to be numeric, so it acted accordingly. Emacs continues to use bills.org~ as the backup for bills.org. Until I tell it otherwise.

A list of file names in light grey text on a blue background: .emacs; .emacs~1~; .emacs~2~; .emacs~3~;

Read the Emacs Manual to learn more about numbered backups.


1 C-c a m invokes org-tags-view

The default values for the pertinent backup variables are:

  • make-backup-files is t
  • backup-by-copying is nil
  • version-control is nil

-1:-- Emacs Numbered Backup Strategy (Post Raymond Zeitler)--L0--C0--2026-08-12T23:01:35.675Z

TAONAW - Emacs and Org Mode: Back to Solarized

Years ago, when I started the old blog1, I discovered Solarized.

I liked it. The colors were beautiful and worked for me, but eventually I switched to the Gotham theme in Emacs, which, if you take a look, is not much different — just a bit darker. I stayed with this theme for a long time, tweaking it a bit after a trip to New Mexico.

When I received my Mac and started working on it at home, I enjoyed its automatic switching between dark and light themes. At the same time, Prot started coming out with more of his excellent Ef Themes, and I adopted ef-reverie for my light theme and ef-night for my dark one (once again, if you look at the images, you’d see they’re not far off from Solarized, especially my choice of a light theme).

This has been the state of affairs until the beginning of this week, when I “found” Solarized again. I discovered Prot himself was heavily influenced by Solarized, and he discussed it several times on his blog. Here’s part of his background story for his project Prot16:

My starting point for developing a colour scheme was Ethan Schoonover’s Solarized. A true masterpiece. I thus settled on a method of using a 16-colour palette that consists of 8 base values and 8 accents. As with Solarized, the themes would have to be able to transition from ’light’ to ‘dark’ environments with minor alteration to the mapping of colours (though not the values themselves).

Prot’s quest is an interesting one. Like me, he enjoyed Solarized, but eventually decided the colors were not contrasting enough for his taste. His reaction was to create his well-known Modus themes, which, along with his Ef-themes and Prot16, set new theming standards for Emacs.

Meanwhile, I have had the other issue since childhood:

I’ve never liked harsh contrast for this reason. As a matter of fact, a more “gentle” contrasting theme is one of the reasons I like working in Emacs. My website and my wiki are a result of these preferences. The black text on the beige background is comfortable on my eyes. Enough contrast to read, but not enough to burn text into my vision, as many dark modes out there do.

While Prot’s ef-themes offer softer contrast (I didn’t check, but it does seem so visually) which I find excellent, I decided to try out bbatsov’s Solarized for Emacs, which I didn’t know about in the past when I first picked up this theme for Emacs — the official Emacs port is not as good in my opinion.

I’ve learned a couple of things I wasn’t aware of. For starters, you can have many variations of this theme, even though the main 16 colors are still preserved. It makes sense when you think about it: one person can choose Solarized’s red (#dc322f in hex) as an emphasis, while another can select its yellow (#b58900) or orange (#cb4b16), for example. Webpages and Emacs take it a step further, as certain texts can be bold or italicized, with solid underlines or dashed ones, etc. All of those changes take place without touching the colors themselves.

Solarized also helps me with a certain itch I’ve had for a couple of months: a dark theme for my blog. I know most of you read my posts in your RSS reader, hopefully with a theme you enjoy, so this won’t matter much. For me, how my site looks and is styled is important, and getting there is fun.

A minimalist (solarized) skyline of a city with a prominent tall building, set against a dark background with waves in the foreground and a red moon in the sky.

  1. This post is here, on the new blog, because I migrated it from the old. The date on it is accurate however, and it’s the first post I have saved in my old archive. The same is true about the New Mexico post. ↩︎

-1:-- Back to Solarized (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-08-12T14:09:15.000Z

Ashish Panigrahi: Reviewing finished tasks from last week in org-agenda and emacs

Being a PhD student within a research group entails some structure to tracking your progress as you proceed with research. Within the current team that I'm in, we have weekly group meetings where each member presents the tasks that they've completed during the previous week and discusses any issues they might've faced (if any), to our supervisor.

Usually before the meeting, I jot down things that I've finished with pen and paper. But since I'm in the spirit of integrating my workflow with emacs and orgmode, I thought why don't I try implementing this part of my routine also into orgmode.

Enter org-agenda

All my todo items are tracked with org-agenda where I capture every task I need to finish with org-capture1.

I have a custom org-agenda view just for viewing my daily and weekly TODO items, events/meetings scheduled at a certain time and tasks that have a deadline associated with them. Suffice to say, this workflow has replaced all my needs for having a calendar over the cloud, be it Google, Outlook, what have you2. The only downside that I haven't figured out (yet) is to incorporate calendar invites that I receive via email, into my org-agenda automatically instead of manually capturing an event into the agenda view.

With this custom agenda view, as I finish tasks and mark them DONE, they disappear from the current view. This is by design. I figured I could just review my completed tasks by visiting the relevant files (tasks.org, meetings.org, etc. in my case). But who wants to sift through all the completed tasks and filter them manually to only note down the tasks completed during the previous week? Why not automate this with the power of emacs?

Elisp snippet that implements this weekly review

The idea is fairly simple. I'd like to look at the tasks that I've completed since the last group meeting. This assumes I review these tasks on the day of the group meeting (which I do currently3).

We simply then instruct orgmode to filter out the DONE items from the last 7 days. The org-agenda-custom-commands variable is what we'll use to define this custom org-agenda view.

(setq org-agenda-custom-commands
      '(("w" "Finished tasks from last week" tags "CLOSED>=\"<-7d>\""
         ((org-agenda-overriding-header "Finished tasks from last week")
         (org-agenda-archives-mode t)
         (org-agenda-tag-filter-preset '("-emacs" "-personal" "-email"))
         (org-agenda-prefix-format '((tags . " ")))
         (org-agenda-remove-tags nil)))))

Let's go over the various config options one by one:

  • The first line defines the keybinding in the primary org-agenda dashboard and the associated title for the keybinding.
  • tags "CLOSED>=\"<-7d>\"" is pretty self-explanatory. We filter for items that were completed in the last 7 days.
  • org-agenda-overriding-header: Simply defines the title of the agenda view.
  • org-agenda-archives-mode: This is set to t (True) meaning to also look at the archived entries from last week. I typically archive my entries frequently so this is helpful.
  • org-agenda-tag-filter-preset: I don't want personal tasks appearing in this view, so I remove any items that have the relevant tags.
  • org-agenda-prefix-format: By default, the agenda view contains information about which file the entries belong to (in my case, it's tasks.org). This appears as tasks: per entry in the agenda which unnecessarily clutters the view.
  • org-agenda-remove-tags: I don't want to remove the tags of the various entries (they are useful to me) that appear in the right margin. Hence this variable is set to nil (False).

To invoke the view, we can simply run M-x org-agenda and hit w to get into the view. My org-agenda dashboard looks like this:

My org-agenda dashboard

All the above keys are present in the default view, except my custom views (depicted by j and w keys).

Invoking custom org-agenda views (slightly) faster

For my workflow, I mostly deal with just the two org-agenda views (represented by j and w). It's a good idea to have a global keybinding to quickly invoke these views. Let's define the function and keybinding for it.

(defun pani/done-items-prev-week-org-agenda ()
  "Custom function to immediately jump to my custom org-agenda view."
  (interactive)
  (org-agenda nil "w"))

To invoke the function, we define the keybinding like via a use-package macro:

(use-package org-agenda
  :ensure nil
  ;; Don't need to go through org-agenda template for custom agenda
  :bind (:map global-map
	      ("C-c w" . pani/done-items-prev-week-org-agenda)))

I can then just hit CTRL c w to bring up this new agenda view.

That's it! This should make things easier for me during our group's weekly meetings.

Special thanks to Marci and my sister, Alaka Panigrahi for pointing out grammatical errors and typos.
  1. More on this in a future blog-post.

  2. This is fully offline. Between my personal laptop and office PC, I simply use syncthing to sync all my orgmode files via peer-to-peer.

  3. This is perhaps not the best approach but is a good enough starting point for me.

-1:-- Reviewing finished tasks from last week in org-agenda and emacs (Post Ashish Panigrahi)--L0--C0--2026-08-12T00:00:00.000Z

Raymond Zeitler: How Much Management Does Knowledge Need?

The topic of this month's Emacs Carnival interests me, even though I "dump ... notes into a directory and simply grep for information ...."0 Org does not work the way my brain works,1 or rather, my brain doesn't work the way Org works.

I do use Org. At first I created a few Org files (work.org and home.org, and maybe todo.org) thinking I could rigidly dedicate all my topics to them.

Over time I had about dozen files, one for nearly every major facet of my life until I gave up. Why? Because it really doesn't matter where I put Org TODOs and their notes. When I need to locate something, Org provides a way to search entries for a regular expression, which is what grep does, sans all that Unixy CLI stuff.

So faced with the task of paying one of my wife's medical bills, I could agonize over whether to put the task into wife.org or bills.org or medical.org.2 Or I could just:

  • establish the task somewhere,
  • act on it
  • link to a receipt
  • mark it done3

I created a special template for org-capture, which places these one-off tasks into todo.org. And it clocks into the task, too.

A topic with even greater complexity is "education." I'm a lifelong learner; my edu.org file has (only) 29 headings, all of which have at least a few subheadings. If I learn about an Emacs feature, I might want to write about it, so I'll put a task into blog.org that links to it.

Occasionally a post will span multiple topics in edu.org, topics such as Emacs, Vivaldi, LibreOffice, HTML or LaTeX. In a year from now, which file should I look for it in? The keystroke C-c a s comes to the rescue.4 I might search for "Sacha" because all I remember is that I discovered an Emacs feature on Sacha's website.5 Incidentally, I get 13 hits when I search for "Sacha," one of which was the one I had in mind.

The question I think all Emacs users face is, "At what point does tweaking Emacs (and self-quantifying tools in general) tip the scales from aiding our progress to impeding it?" I dread going down a Knowledge Management rabbit hole,6 but I'm sure I'll enjoy the leap.

0 Charlie Holland's Search for Knowledge

1 A tree works like your brain.

2 Don't even get me started on insurance.

3 All of this occurs while clocked in to the task.

4 In this case, C-c a is bound to org-agenda.

5 Sacha Chua's blog

6 Can one gain knowledge of Knowledge Management Systems without having a Knowledge Management System in which to record that knowledge? That may be a Zen koan for someone.

-1:-- How Much Management Does Knowledge Need? (Post Raymond Zeitler)--L0--C0--2026-08-10T22:19:40.478Z

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!