Amin Bandali: FFS code review with Protesilaos

In the recent weeks I've been engaging Prot as an Emacs coach to help with doing review passes over my upcoming ffs package as I work on polishing and documenting it in preparation for offering it for inclusion in GNU ELPA.

Yesterday we had our second session focused on ffs, which I recorded and share publicly with everyone with Prot's permission, so that others can also benefit from Prot's insights and experience as we discuss various aspects of Emacs package development with the concrete example of ffs.

Here is the video recording of our session:

You can view or download the full-resolution video from the Internet Archive.

I addressed most of Prot's feedback about ffs from our first session, and I'll be working on the changes we discussed in this session in the next days.

In the last third of the video we switched topics to discuss a few Emacs-related tangents including adding a 'padding' effect for the mode line and its constructs, and distilling and separating the easily-reusable package-like parts of one's Emacs configuration from the actual configuration of those parts (e.g. the distinction of prot-lisp and prot-emacs-modules in Prot's Emacs configuration).

For mode line padding, here is the snippet I'm using with Prot's doric-themes:

(doric-themes-with-colors
  (custom-set-faces
   `(mode-line
     ((t :box (:line-width 6 :color ,bg-shadow-intense))))
   `(mode-line-inactive
     ((t :box (:line-width 6 :color ,bg-shadow-subtle))))
   `(mode-line-highlight
     ((t :box (:color ,bg-shadow-intense))))))

Take care, and so long for now.

-1:-- FFS code review with Protesilaos (Post Amin Bandali)--L0--C0--2026-05-08T02:10:33.000Z

Charlie Holland: My Dotfiles: macOS Bootstrap and an Emacs Distribution

1. About   dotfiles macos emacs setup

dotfiles-banner.jpeg

Figure 1: JPEG produced with DALL-E 4o

My dotfiles for my MacOS rice and Emacs configuration live in two public repositories. Both repos are shared as a reference; clone, fork, or just lift the bits that look useful to you!

This post is a thin entry point, and the READMEs in each repo carry the actual detail.

2. chiply/.files   shell tmux macos bootstrap

A single bootstrap.sh that takes a clean macOS install to a fully provisioned development machine in roughly thirty minutes. It installs Xcode CLI tools, Homebrew, and a long list of CLI utilities and language toolchains, then symlinks every config in files/ into the matching path under $HOME.

See repo for installation instructions.

What gets installed:

  • Shell: zsh + zinit, starship prompt, atuin shared history
  • Terminal: Ghostty with cursor shaders, plus Nerd Fonts
  • Multiplexer: tmux with tmux-powerline, tmuxinator, TPM
  • Window manager: AeroSpace, simple-bar, JankyBorders
  • Languages: pyenv, Poetry, uv, nvm + Node, language servers (json, eslint, copilot, svelte)
  • CLI: gh, k9s, bat, fzf, ripgrep, eza, jq, lazygit, AWS CLI v2, and more

The full package list lives in the repo's Brewfile. bootstrap.sh also clones .zetta.d to ~/.zetta.d as part of the Emacs setup; if you only want the shell side, comment out the emacs section.

3. chiply/.zetta.d   emacs zetta distribution

My Emacs configuration, packaged as a small distribution. Around 320 packages wired up via a module DSL, with an Elpaca lockfile pinning every package to an exact commit, and byte- and native-compilation done up front so the first launch is clean.

The name is a cheeky play on how we name certain minimalist text editors after Metric Prefixes nano (10^-9) or pico (10^-12). This maximalist editor config is named after zetta (10^21).

See repo for installation instructions.

Notable parts:

  • Triple-modal editing: Evil, Meow, and vanilla side-by-side, switchable on the fly
  • Module DSL: enable categories or individual packages via zetta-modules! in ~/.zetta.el
  • Reproducible: lockfile + compile-by-default install
  • CI-tested: Emacs 29.4, 30.2, and the 31 snapshot

Zetta also hosts several small packages I've written that live in their own public repos. See the README's "Bundled custom packages" section for the list. None of these are released or publicized yet, so bring a pinch of salt if you choose to try them.

-1:-- My Dotfiles: macOS Bootstrap and an Emacs Distribution (Post Charlie Holland)--L0--C0--2026-05-07T15:18:00.000Z

Irreal: Grove

Jonathan Chu is a software engineer and Emacs enthusiast who’s been looking for a note taking app that he can actually use. He’s tried all the usual suspects but found them too opinionated for his liking. He wanted to work in Emacs and write his notes in Org mode but none of the existing packages were quite right. So he did what Emacs users always do when things don’t work exactly as they like: he wrote an Emacs package do things the way he wanted. The result was grove.

Chu says that Grove was informed by his experience with Obsidian. I’ve never used Obsidian so I can’t comment on that aspect but Chu says that Grove is Obsidian-like. The only external dependencies are ripgrep, Graphviz if you want the graph view, and Consult if you want the enhanced search capability. One of his goals was to have an easily installable and configurable package.

It looks like a pretty nice package if you’re looking for a note taking method with a bit of but not too much structure. I use Org mode for all my note taking across many dimensions and have never felt the need for a more organized approach. Sure, sometimes I think about using Denote or Org Roam but—for me—they never seemed worth the effort. Many others disagree, of course, but Emacs has a home for us all,

Take a look at his post or the Github repository for the details and a bit more on how things work.

-1:-- Grove (Post Irreal)--L0--C0--2026-05-07T14:58:24.000Z

Dave Pearson: blogmore.el v4.4.0

I've released an update to blogmore.el, my Emacs package that helps me out when writing this blog. I've added two commands to this version which help me be lazier than ever.

The first is blogmore-become-like. When run, this prompts for another post and, once selected, it sets this post's category and tags to be the same as the other one. I added this because I'm often writing an occasional series of posts that are all about the same project, and so I always find myself copying and pasting those frontmatter properties from another post.

The second command I've added is blogmore-toggle-image-centre. Built into BlogMore is a little bit of styling that will ensure an image is placed in the centre of the page, if the URL for the image has #centre on the end. This means that, for most images I add, I have to go and edit the URL to add that. Now I can just run a single command when the cursor is on an image and it'll add (or remove, if it's already there) that styling hint.

In both cases, I've added the commands to the transient menu too.

-1:-- blogmore.el v4.4.0 (Post Dave Pearson)--L0--C0--2026-05-07T08:05:10.000Z

Bicycle for Your Mind: Capture and Move On

capture iconcapture icon

Product: Capture
Price: $7.99 · lifetime updates · up to 5 Macs · 7-day free trial

Capture is a product designed to capture something, and move on with your life. It is an universal product, you can be in any application, Safari, Chrome, BBEdit, Finder, anywhere. You can press the keyboard shortcut to invoke capture. Fill in what you want to capture. Hit return and go back to whatever you were doing.

The product is advertised as “Capture thoughts to Obsidian without leaving what you’re doing.” You don’t have to use Obsidian. I use Emacs and it works great for that too. You can use any text editor and it is as useful.

Two Routes

capture optionscapture options

Capture gives you two options to capture your content.

  1. You can capture to a new individual file. Every capture gets put into a time-stamped new file.

capture where?capture where?

  1. You can append to an existing file which you have specified, or append to a daily note.

capture and folderscapture and folders

You can define the Obsidian vault that Capture deals with. In my case it is the folder which contain my org files. Within that folder is another folder called ‘capture’ where I put my captured files into.

capture contextcapture context

Within these options you have various options. You can include context. You get to define the context.

capture tagscapture tags

You can add tags to your captured content.

I am happy with Capture. It is a focused utility which does what it promises well.

Wishes for Capture

  1. I understand that the link to Obsidian means that this deals with Markdown files. We don’t need that restriction. It can work with Org-mode files and plain text files. Give me the ability to specify the extension I want my files to be.
  2. The command to save the capture is ⌘↵. On my machine ↵ saves the capture. There is no way to add a new line. This must be a bug.

Recommendation

I used the program for fifteen minutes and bought it. It is the kind of utility which I am going to use multiple times a day.

Recommended most heartily.

macosxguru at the gmail thingie.

-1:-- Capture and Move On (Post Bicycle for Your Mind)--L0--C0--2026-05-07T07:00:00.000Z

Andros Fenollosa: twtxt vs Org Social: the evolution of an idea

twtxt was one of the strongest inspirations behind Org Social: a plain text file served over HTTP, with no active server, no database, no signup. I personally admire the work done by Buckket: the concept is so powerful that it has captured the hearts of many people over the years. Its author gave us an elegant solution for creating a personal microblog, an alternative to Twitter (now X).

That said, Org Social would not exist if it weren't for the limitations of twtxt or the natural evolution of social networks. Org Social keeps its spirit, learns from its mistakes and from the competition, reinforces other concepts (like federation) and adopts modern features (like visibility). It is not an improvement, it is a shift in approach.

That's why I'd like to go over the three most important aspects where Org Social improves on twtxt.

Technical improvement

The most obvious one is the ability to have structured metadata thanks to Org Mode.

#+TITLE: Bob's journal
#+NICK: Bob
#+DESCRIPTION: I'm a software developer and I love open source.
#+AVATAR: https://my-awesome-website.com/avatar.jpg
#+LINK: https://my-awesome-website.com
#+FOLLOW: https://foo.org/social.org
#+FOLLOW: https://jane.com/social.org

* Posts
** 2024-12-12T12:00:00+0100
:PROPERTIES:
:LANG: en
:TAGS: emacs org-social
:CLIENT: org-social.el
:VISIBILITY: public
:MOOD: 😊
:END:

Hello Org Social!

This opens the door to giving context to each post, having native threads, tagging, organizing in groups, defining visibility, running polls...

Feature twtxt Org Social
Format plain text (Markdown optional) Org Mode
Mentions yes yes
Profile metadata minimal rich
Tags #hashtag inside the body :TAGS: as metadata
Native threads no REPLY_TO
Multiline no yes
Sub-headers no yes (#+)
Federated groups no yes (GROUP)
Languages no yes (LANGUAGE)
Polls no yes
Reactions / boosts no yes
Visibility no VISIBILITY (though not fully private)
Account migration no MIGRATION

We don't just get richer post bodies: the possibilities for interaction between users grow without limit. The specification invites you to create, not to work around limitations.

5 design principles

Org Social follows these principles:

  • Simplicity: the format must be easy to understand and write with any text editor, with no special tools or advanced technical skills required.
  • Accessibility: the feed must be readable by both humans and machines.
  • Decentralization: every user should be a node, a self-hosted feed or one publicly accessible.
  • Org philosophy: the format must take advantage of Org Mode features, like links, tables, code blocks, checkbox lists, etc.
  • Your information belongs to you: no one owns your feed, content, or followers. Just you and your text file. Where you host it is a separate matter.

Without clear rules, the ecosystem fragments. There isn't a single line of the specification that isn't aligned with the points above.

Infrastructure should add value, not be a requirement

A very important element in Org Social are the Relays, which let you organize threads, discover other users, receive notifications, run searches, get an RSS feed, etc. However, without them the network would still work: users could keep publishing, following others and interacting. Relays are not the core of the social network. Clients can rely on them, but should never depend on them. This isn't a quality exclusive to Org Social: twtxt is also aligned with this principle. The point lies elsewhere.

The goal of clients is to make reading and writing feeds (social.org) easier, not to be a requirement to participate. That said, they are very practical for building your timeline, since you need to read and sort the feeds of the users you follow. For that there are Desktop and Android versions thanks to Emacs, plus a native iOS version written in Swift.

The differentiating point is balancing different user profiles via the infrastructure. For example:

  • Users with no technical knowledge: in a couple of clicks you can have an account thanks to Org Social's free hosting. The clients will take care of syncing and publishing for you. It is very similar to how other networks like Mastodon work, except that everything lives in a plain text file locally and transparently.
  • Users with some technical knowledge: they can host their social.org on GitHub, GitLab, their own server, etc. They control and manage where it is hosted and how it connects.
  • Users with deep technical knowledge: they fully control their presence on the network. They run their own server (with WebDAV, for example), with their own configured domain, with their own caching policies... or they even spin up their own Relay for extra speed or to build private federations.

It embraces every kind of user, without sacrificing the essence of the social network. You don't need to be an expert to take part, but if you are one, you control every byte.

Conclusion

Org Social doesn't just improve on twtxt technically: it also evolves the concept of a social network on top of plain text. It takes a step forward by providing more infrastructure and incorporating modern features.

The future is not about going massive or competing with Mastodon, but about satisfying a very small niche of users.

Focus on the content or on engaging with others, Org Social will take care of the rest.

-1:-- twtxt vs Org Social: the evolution of an idea (Post Andros Fenollosa)--L0--C0--2026-05-06T13:21:31.000Z

Amin Bandali: Emacs Chat with Sacha Chua

Yesterday I joined Sacha Chua for a new episode of her Emacs Chat podcast, where we talked about Emacs and life. I gave a quick tour of my Emacs configuration, discussing at length my configurations for EXWM (Emacs X Window Manager) among other topics like Emacs's facility for visually indicating buffer boundaries in the fringe by setting indicate-buffer-boundaries and my convenience configuration macros.

The above video is provided with closed captions and the below transcript courtesy of Sacha with minor fixes and formatting by me. I've included some of Sacha's screenshots from our chat, you can see the rest on the episode's page on Sacha's blog.

A few links from our chat:

It was a lot of fun - thanks again for having me, Sacha!

Take care, and so long for now.

Transcript

-1:-- Emacs Chat with Sacha Chua (Post Amin Bandali)--L0--C0--2026-05-05T23:43:44.000Z

Charlie Holland: Emacs Completion Showcase with VOMPECCC (video)

1. About   emacs completion workflows

vompeccc-showcase-banner.jpeg

Figure 1: JPEG produced with DALL-E 3

This is the fifth post in my series on Emacs completion. The first, Incremental Completing Read (ICR), explains what modern completion actually is, and how Emacs exposes it as a programmable substrate rather than a closed UI. The second introduced the VOMPECCC stack of eight packages covering the six orthogonal concerns of a complete completion system. The third toured spot, a Spotify client built as a thin shim on top of those packages. And the fourth built a produce picker from scratch, demonstrating the specific features that each VOMPECCC package provides.

This post is the practical complement to all the other posts. Here, we showcase over a dozen workflows I use every day. Most are powered entirely by features that ship in the box with the VOMPECCC packages, and there are 'Bonuses' which demonstrate workflows enable by 3rd party packages that build on top of VOMPECCC. The prose is deliberately thin, and you will find most of the demonstration is in the video below.

2. The Video   demo

As in the previous posts in this series, the upper-right of my Emacs (in the tab-bar) shows the keybindings and command names I am invoking, so you can map what you see onto your own configuration.

3. A Note on My Configuration   setup

Two configuration choices show up repeatedly and are worth naming once upfront so the keystrokes are intelligible.

Async split character. My consult-async-split-style is comma, not the default #. In Consult commands like consult-ripgrep, everything before the first , is sent to the external tool as the search pattern, and everything after is filtered locally with my completion style.

Orderless dispatchers. My orderless-style-dispatchers bind affix characters to matching styles: @ for Marginalia-annotation matching, ~ for flex, ` for initialism, ! for negation. Each can be a prefix or suffix on a component. My orderless-component-separator is also ,, so a single comma serves double duty depending on context.

4. Multi-File Refactor   ripgrep embark wgrep

consult-ripgrep → input your search term → embark-exportwgrep-change-to-wgrep-mode → edit as you like → C-<return>

5. Async + Local Two-Stage Search   consult async orderless

consult-ripgrep with , splitting external (ripgrep) from local (Orderless): for example, error,handler,~retry,!test.

6. Unified Buffer / File / Bookmark Switcher   consult narrowing

consult-buffer with narrowing keys: b SPC for buffers, f SPC for recent files, m SPC for bookmarks, p SPC for project items.

7. Buffer and Project-Wide Line Search   consult preview

consult-line within the current buffer; consult-line-multi across all buffers (or the project, with a prefix argument).

8. Code Symbol Navigation   imenu navigation

consult-imenu within the current buffer; consult-imenu-multi across every buffer of the same major mode.

9. Documentation Search   consult docs

consult-info for Info manuals (Emacs, Elisp, Org, plus every package that ships its own .info file); consult-man for system man pages.

10. Find Commands by Docstring   marginalia orderless

M-x window @frame. The @ dispatcher routes a component through orderless-annotation to match against Marginalia's docstring text rather than the candidate name. This lets you query for commands by what they do rather than what they are called.

11. Mass Action Across Candidates   embark batch

C-> (embark-act-all) runs a single Embark action on every candidate currently surviving in the prompt.

You can also embark-select to create a subset of displayed candidates and use embark-act-all to act on only those selected candidates.

12. Pivot Mid-Prompt   embark flow

embark-become switches the active command (e.g. find-fileswitch-to-buffer) without losing the input I have already typed.

13. Symbol-Aware Multi-File Refactor   xref embark wgrep

xref-find-references (M-?, with xref-show-xrefs-function set to consult-xref) → embark-exportwgrep-change-to-wgrep-mode → edit → C-<return> to write your changes. This is very similar to the ripgrep version above but driven by the language server, so foo the variable and foo the unrelated comment stay separate.

14. Recent Files as a Filesystem   dired embark

consult-recent-fileembark-export produces a Dired buffer, putting every Dired operation (mark, copy, rename, chmod, batch shell command) on the recent-files set.

15. Avy-Style Jump Then Act   vertico embark

In any Vertico session, C-' jumps to a labeled candidate (Avy-style); C-" does the same jump and hands the candidate to Embark.

16. Resume the Last Session   vertico repeat

s-V (vertico-repeat) reopens the last completion session with its prompt, input, and selected candidate intact.

17. Bonus: Magit-Style Working Copy as Completion :consult-ls-git   git

consult-ls-git surfaces working-copy status, tracked files, and branches in a single multi-source prompt with narrowing keys. A nice on-the-fly alternative to the Magit status buffer.

18. Bonus: Browse GitHub Repos from the Minibuffer :consult-gh   embark

consult-gh-search-repos streams GitHub repos as candidates; C-= previews the README; M-S (vertico-suspend) (or simply moving your cursor out of the minibuffer) detaches the minibuffer for free reading; s-V (vertico-repeat) (or simply moving your cursor back into the minibuffer) resumes; C-. exposes Embark actions (clone, browse, view issues, view PRs, view files, fork).

19. Bonus: Search All Public Code on GitHub :consult-gh:code-search:

consult-gh-search-code against the contents of every public repository on GitHub. You get the same VOMPECCC features, but with the search space expanded to "all open source code in the world".

20. Bonus: Multi-Source Web Search :consult-omni   web

consult-omni-web fans one query out across Google, Brave, Wikipedia, StackOverflow, YouTube, and a gptel-backed LLM source simultaneously; s-j / s-k jumps between source groups; C-= previews; C-. surfaces Embark alternates (open in EWW, copy URL, etc.).

21. Closing   closing

What makes this so cool is that none of these workflows required a single line of custom code. Each is built entirely out of the features that ship with one or more of the VOMPECCC packages. Pick the two or three that map onto frictions you already feel, and the rest will reveal themselves ad-hoc as you encounter new frictions.

22. TLDR   tldr

Over a dozen high-impact Emacs workflows are demonstrated in this post: multi-file refactor, two-stage ripgrep, unified buffer switching, line search with preview, symbol navigation, docs search, M-x by docstring, batch action, mid-prompt pivot, symbol-aware refactor, recent files as Dired, and quick jump + act, session resume. Each of these workflows is composed entirely from features that ship in the box with the VOMPECCC packages.

-1:-- Emacs Completion Showcase with VOMPECCC (video) (Post Charlie Holland)--L0--C0--2026-05-05T18:15:00.000Z

Curtis McHale: The gravitational pull of familiar tools

I understand the gravitational pull of Emacs though for me it's been Vim/Neovim. I've spent entire year trying out tools like VSCode and Atom and sure they had some features that were neat, but they were never Neovim.

It's been at least 5 years since I tried another editor for coding though after realizing that I'd just go back to Neovim, which brings me to my Emacs adventures. I tried nvim-orgmode and it sort of worked but had issues that I was never able to work through. Then I went back to Todoist for a bit and missed orgmode, even if it was a hobbled form in Neovim.

This led me to Emacs and orgmode which I've been loving for about a year now. Still I keep wondering if I should move my coding workflow to Emacs and abandon Neovim?

There is a pull to just use one tool.

-1:-- The gravitational pull of familiar tools (Post Curtis McHale)--L0--C0--2026-05-05T16:26:00.000Z

Curtis McHale: Emacs Carnival: May I recommend...

This month's carnival topic is "may I recommend...". I'm too new to Emacs to recommend some fancy package or an interesting workflow so I'll reach back to my 20+ years doing software and finding tools.

May I recommend....keep it simple. Don't grab 50 new things from some video or blog post that some creator put up. People do that because it looks cool and then they over complicate their systems and abandon them. They layer complexity that looks cool over a system that already is working for them to solve problems they don't have with solutions often produced by people whose job is to get a new piece of content out that will go viral and pay their bills.

Many times it doesn't even seem like the creator uses the system at all, it just looked cool and had the proper keywords for what's popular right now.

I say, identify a problem you actually have. Spend some time researching options. Try out a few of them to see what works. Then stop messing around with your system and get work done.

  • I always hear about the carnival from Sacha
-1:-- Emacs Carnival: May I recommend... (Post Curtis McHale)--L0--C0--2026-05-05T16:11:00.000Z

Irreal: Emacs Keyboard Ergonomics

Protesilaos (Prot) has an excellent post on keyboard ergonomics for Emacs users. He considers various strategies for making Emacs commands easier to use and less likely to cause RSI damage. He considers everything from split keyboards to evil-mode.

He says he has both a split keyboard and a keyboard with a normal form factor. He says that the split keyboard is not a magic solution and that what works best for him is to configure “one shot modifiers” where the modifier is simply pressed—but not held down—before the key it modifies. So, to use his example, Ctrl+x is typed by tapping the Ctrl key and then the x key.

Along with that he suggests ordering the modifier keys in a way you find comfortable. The most important thing, he says, is to keep the Ctrl key close to the space bar so that you can use your thumb to press it. It’s also important to have the modifier keys located symmetrically so that, for example, you can use either hand for the Ctrl key.

As for the common advice to map Caps Lock to Ctrl, Prot says that’s okay but that it encourages the bad habit of always using your left pinky for Ctrl.

There’s a lot more information in Prot’s post—including how to configure one shot modifiers—so be sure to take a look at it.

I’ve been incredibly lucky. I use a standard keyboard, map Caps Lock to Ctrl and don’t have any symmetric modifier keys. Nonetheless, I haven’t suffered any RSI problems despite spending the majority of my day at the keyboard. But, as I say, I’m lucky. The smart thing to do is to read and head Prot’s advice.

-1:-- Emacs Keyboard Ergonomics (Post Irreal)--L0--C0--2026-05-05T16:05:03.000Z

James Dyer: A Zoomed in vc dir for the Current Directory in dired

I almost always reach for project-vc-dir when I want a VC status overview, and most of the time this is exactly what I want, the whole project laid out in one buffer, every modified, added and unregistered file in the repo sitting right there, ready to be diffed or committed. But every so often, particularly when I am deep inside a big repository and I only really care about a single subdirectory's worth of changes, that project-wide view is, frankly, a bit too much. Too many rows, too much scrolling, too much noise.

20260416103000-emacs--A-Zoomed-in-vc-dir-for-the-Current-Directory.jpg

So, what am I actually after?, I want the same vc-dir buffer, but scoped to whatever directory I happen to be looking at, most commonly the directory I have open in dired. And it turns out this is almost trivially easy in vanilla Emacs.

The bit I had not initially appreciated is that vc-dir itself already accepts a directory argument, it is the interactive prompt that steers you towards the repo root, because it defaults to (vc-root-dir) rather than default-directory. If you call it non-interactively with a subdirectory instead, the Git backend quite happily scopes the status listing to files underneath that path, even though the overall VC root is still the same project root.

So the fix is a tiny wrapper that just hands vc-dir the current default-directory and skips the prompt entirely:

(defun my/vc-dir-here ()
  "Run vc-dir on the current directory (dired's dir when called from dired)."
  (interactive)
  (vc-dir default-directory))

Then, because the main place I actually want this is from within dired, a keybinding that sits nicely alongside the standard C-x v family:

(with-eval-after-load 'dired
  (define-key dired-mode-map (kbd "C-x v D") #'my/vc-dir-here))

The mnemonic, such as it is, is that C-x v d is the normal vc-dir binding with its usual prompt, and capital D is the "here, right now, this directory" variant. Lowercase for the prompted version, uppercase for the zoomed-in one, which also pairs up nicely in my head with project-vc-dir being the zoomed-out project-wide thing on C-x p v.

So the little two-tier workflow I have settled into is:

  • C-x p vproject-vc-dir, show me everything in the project
  • C-x v D from dired – my/vc-dir-here, show me just this subdirectory

And that is really the whole post, nothing clever, no new package, just a three-line wrapper and one keybinding, but it has genuinely taken a surprising amount of friction out of navigating VC state in large repos where I know perfectly well the only thing I have touched is under src/foo/, and I do not particularly want to be reminded of every other outstanding change elsewhere in the tree.

And yes, magit can do this with some narrowing, but actually, I like vc-mode!

-1:-- A Zoomed in vc dir for the Current Directory in dired (Post James Dyer)--L0--C0--2026-05-05T06:50:00.000Z

Sacha Chua: Emacs Chat 21: Amin Bandali

: Added file enclosure so that it can load as a proper podcast.

I chatted with Amin Bandali about Emacs and life.

View it via the Internet Archive, watch/comment on YouTube, read the transcript online, download the transcript, or e-mail me your thoughts!

Links:

Chapters

  • 0:11 Introduction: Amin Bandali, software developer and free software activist
  • 1:06 Aspects of life: notetaking, editing, multiple
  • 3:03 Configuration: keeping things simple
  • 5:03 user-lisp-directory, site-lisp if you're using an older Emacs
  • 6:35 Organizing configuration into modules
  • 7:49 early-init
  • 9:09 ring-bell-function
  • 9:41 performance optimizations
  • 10:27 user-lisp
  • 11:16 ignoring byte compilation warnings
  • 11:58 init-file-debug = –debug-init
  • 12:56 Core
  • 13:57 no longer using bandali-configure; scoping errors, timing execution
  • 17:06 Why not use use-package
  • 18:39 Defining multiple keybindings
  • 19:48 doric-oak uses emphasis instead of colours
  • 20:52 global font scaling instead of the local ones
  • 21:39 display-fill-column-indicator
  • 22:57 emacsclient for EDITOR and VISUAL
  • 23:38 fundamental-mode-hook
  • 24:25 indicate-buffer-boundaries
  • 26:38 enabling and disabling commands
  • 27:42 package-review-policy
  • 28:58 getting the Info files from the Emacs source directory
  • 29:46 recentf, adding directories
  • 31:41 Scrolling
  • 32:36 auto revert
  • 33:16 Repeat mode
  • 34:53 EXWM
  • 38:05 Audio setup
  • 39:15 keymaps for launching different applications
  • 39:55 bandali-call-interactively-insert
  • 42:29 workspaces
  • 43:50 ZSA Voyager split keyboard, super x as a single key
  • 46:28 Keybindings
  • 48:08 Media buttons
  • 49:45 exwm-input-simulation-keys!
  • 51:43 exwm: managing floating windows
  • 53:13 exwm: application-specific local simulation keys
  • 54:09 binding C-q to exwm-input-send-next-key
  • 54:31 Renaming buffers
  • 55:38 dunst for notifications
  • 56:55 exwm xsettings and responding to screen configuration changes
  • 59:03 Slowly getting back into Org mode
  • 1:00:01 chat notes
  • 1:00:54 Mode line
  • 1:01:50 display-buffer-alist
  • 1:02:24 TRAMP slowness, maybe disabling VC detection?
  • 1:03:42 eat
  • 1:05:09 TRAMP completion
  • 1:06:55 ffs: form feed slides, ^L
  • 1:09:36 Speaker notes

Transcript

Transcript

0:00 Introduction: Amin Bandali, software developer and free software activist

Sacha: Let me do the thing. Go live. Let's check in. Alright, hello. This is Emacs Chat 21 coming back after a decade of not doing it, so... And today I've got Amin Bandali who's a... Is it seven years now that we've been doing EmacsConf together?

Amin: I think so. Since fall 2019. Yeah.

Sacha: Yeah, yeah, yeah, yeah. But of course you also do a whole lot of other things. I was looking through your Emacs configuration and there's like translation and other stuff in there. So would you like to start off with a brief introduction of who you are and how and why you use Emacs?

Amin: Yeah, sure. Yeah, first of all, hello, everyone. Sorry if I'm looking to the side. This is a new setup. My laptop, which has my webcam, is there, but my main display is here. So I might be looking to the side from time to time. But yeah, that aside, hello.

1:05 Aspects of life: notetaking, editing, multiple

Amin: Yeah, I'm Amin Bandali. I've been, I think, using Emacs since 2014 or 15, so I guess more than a decade now. I'm a software engineer by day, or software developer, slash programmer, slash computing scientist. I'm also a free software activist. I volunteer on a lot of free software projects as well, which Sacha mentioned. I do things around GNU. I volunteer with FSF. I'm a Debian developer, so I try to maintain some packages in Debian. I try to help run EmacsConf from time to time. Hopefully this year I will be much more present. But yeah, that's that. So I first got into using Emacs, I guess, as a programmer tool, like as a text editor. But I've since then kind of integrated it into a lot of other aspects of my life. And I do much more with it, as I'm sure a lot of us do. Yeah, so I use it for kind of note-taking, just any writing, editing purposes. in multiple natural and programming languages. Reading and sending email for chatting via IRC. All of that good stuff.

Sacha: This is the sort of thing that isn't immediately obvious from your configuration. I know you've got your Gnus setup in there and you've got your ERC setup in there, but sometimes when newcomers are trying to figure out, okay, there are all these packages, but how do I use them to get stuff done? That's one of the reasons why we want to do this Emacs chat, so that maybe you can show us some of the cool stuff. We are live, but if you accidentally show something personal, let me know and I can kill the stream within 10 seconds and I think then we can be like, okay, we'll just flush that out and then come back once we've hidden the top secret plans for taking over the world, that sort of thing. Sounds good. Where do we want to start?

3:00 Configuration: keeping things simple

Amin: I'm happy to do it however you like. I can either share my screen, pull up my configuration. Yeah, okay, so let's do that.

Sacha: Yeah. If you share your screen sometimes, I think what we did ages ago was we just started walking through the configuration and then sometimes people say, oh yeah, that's really interesting. Let's go and demonstrate that so that people can get a sense of how this actually works. And there were some things in your configuration that I had no idea, like what is FFS? There's like no package. I couldn't find any information about it. But yeah, so your config, if you want to go ahead and share your screen while I Fill the air with hand-waving. Admin's config tends to be more on the minimalist side. I think you mostly rely on built-in things with a couple of external packages. You don't even use use-package at all. It's all run-at-idle-time to delay the startup of various things, and then it's all vanilla Emacs as you can get for loading and configuring things.

Amin: Yeah, pretty much, yeah. Yeah, so before I continue, quick note, Sacha, if you can make me presenter because I don't have access to share my screen.

Sacha: Oh, that would be important, yes. Hang on a second. Let me see. Okay, here we go. Make presenter. I might as well promote you to moderator while we're at it. There you go. You should now have magic powers.

Amin: Thanks. Let's see.

Sacha: It's a good thing we're practicing this before EmacsConf so I remember how all this stuff works.

Amin: Yep, for sure. Okay, let's see. I think I got it now. Can you see my screen?

Sacha: Yes, I can see your screen.

Amin: Okay, excellent. Let's see. Okay.

4:58 user-lisp-directory, site-lisp if you're using an older Emacs

Amin: Yeah, so as Sacha mentioned at the moment, my config is kind of very minimalist and kind of conservative by design, in part because I tend to work on a lot of different machines, whether it's for work or volunteering or whatever, and I prefer to use Emacs if I can. So I want my config to be fairly self-contained so I can easily either git clone or rsync it over. Yeah. To keep it simple, I was using package.el for a while for installing and managing my packages, which I don't keep in my configs repository. But then I decided to switch over to very manual package management with the awesome new feature user-lisp-directory of the next upcoming Emacs release, which basically you can give it a subdirectory in your .emacs.d or .config/emacs. And then it'll go through all the Emacs Lisp files recursively, byte compile them, native compile them, all that good stuff, and add them to the load path. And for people who are using existing or older releases of Emacs, there's also site-lisp by Philip Kaludercic, which is kind of the... I guess first implementation of what later became user-lisp and built into Emacs. So you can make it conditional and fall back to site-lisp if you want to be able to use user-lisp on older Emacs but still have your configuration be usable. Yeah, anyway.

6:32 Organizing configuration into modules

Amin: So I've experimented with like a couple different ways of managing my configurations like single giant init file of like four or five thousand lines which I know is actually not very large by comparison to I think like someone like Sacha's configuration and also like You know, split into multiple different files, which has its own benefits. And I've kind of actually converged to the approach that Prot uses. If you actually take a look at my configuration file, you see I've drawn a lot of inspiration from Prot switches. Having a literate single file configuration, which then all of the Emacs LISPs source blocks get tangled to individual files. So I can maintain a single source of truth and edit it all in one place, but then also easily be able to share individual pieces to people if they want. So yeah, that's kind of the general approach. And I can dive right in.

Sacha: Yeah, that's definitely the structure that I've also stolen from Prot. And I like the way that you're Your heading names are all long and descriptive, and you've got everything broken down in detail. So yeah, go ahead and walk us through it, please.

Amin: Yeah, sure. Let's see.

7:45 early-init

image from video 00:08:00.067Amin: So that's a brief introduction, and then I have an early init section for doing the early init file. There's a couple of subheadings here. Actually, let me enlarge the font size a little bit to make it more legible. OK, great. I do a couple of things here like disabling package at startup because I don't use package as I mentioned. I manually install and update my packages as git submodules in my configurations repository.

image from video 00:08:15.567Amin: I set load-prefer-newer to t to make sure that I never load any stale code. For example, I might edit some Emacs Lisp file by hand and forget to byte compile or native compile it. And this tells Emacs to basically just use the version of these three variants that's the most recent. Yeah. Nothing super fancy here.

image from video 00:08:35.700Amin: I turn off a couple of things that I find a little bit distracting, like the menu bar or toolbar. Although I do say here that for people who are new to Emacs, they're actually super helpful. Sure, it's a little bit of visual clutter, but in the beginning, it's really, really helpful to help you orient yourself of what mode you're in, what tools do you have available in your disposal. And even someone who's been using Emacs for more than 10 years, I also use it sometimes when I'm like... just starting to use a new mode. So yeah, good stuff.

9:06 ring-bell-function

Sacha: I was very amused by the comment on the... "I don't like getting jumpscared out of my chair." You turned off the bell.

Amin: Yeah, because that actually used to happen when I first started using Emacs. Like when I would, I don't know, I don't even remember when it bells or rings, but Maybe if you like quit like with C-g or like try to backspace into like delete where there's no more characters to delete so it rings a bell and it's very like can be jarring so yeah I turn that off.

9:40 performance optimizations

image from video 00:09:56.367Sacha: Yeah, and then you've got a whole bunch of things where you set some variables to nil temporarily to make it faster, so that's in your startup in garbage collection.

Amin: Exactly. Empirically, there is no hard and fast science behind this. I experimented over the years. I'm pretty sure I believe the default, for example, the garbage collection con threshold is about eight megabytes. I tried increasing that a little bit to see how much If I increase it to what point will it make my startup faster? And I found this 30 megabytes or mibibytes to be kind of a sweet spot. So I bumped that up. And then after Emacs has finished initializing, in the after-init-hook, I just restored the defaults.

10:25 user-lisp

image from video 00:10:51.900Amin: And then, yeah, this is the bit with the user-lisp-directory that I was talking about. Awesome stuff. So you can basically designate a directory. For example, in my configuration, it's just a lisp directory. And then on startup, Emacs will go through and byte compile, native-compile if necessary, and then add all of that stuff to the load path automatically. So you get that. Yeah, and then this is the bit about site-lisp that I was talking about. So if you want to use user-lisp, but you're still using older Emacs versions that you maintain, you need to maintain backward compiling in your config. This is how you do it, for example. So you just yeah, add it to load-path, require it and then call prepare-user-lisp. That's about it.

11:14 ignoring byte compilation warnings

Sacha: I'm picking up that tip about using the ignore directories. I'm getting by with just ignoring all of the byte compilation output, but it would be nice to just say, you know, that stuff is test. I don't need to worry about it.

Amin: Right, right. Thanks. Yeah, I was also doing that. I actually have it as a comment to suppress warning types, like by compilation, but I was... I plan on working on some packages, whether my own or others, and it would still be helpful to get those warnings, so I keep them enabled. It's still a bit annoying. I still get some of them when I launch emacs but I don't restart or launch emacs as frequently so it's pretty bearable.

11:55 init-file-debug = --debug-init

image from video 00:12:00.400Amin: Yeah, and then I have the main init file. And there's not much in it. It's just the debug-on-error and debug-on-quit. So the debug-on-error thing, I set it to the value of init-file-debug. And if you look at that, the help for this variable, basically if you pass or launch Emacs with --debug-init, this variable will be true. So yeah.

Sacha: I did not know that. Cool.

Amin: Yeah, it's pretty helpful. I think, if I'm not mistaken, I took this from John Wiegley's .emacs, but I can't remember for sure. It's been years. Yeah, it's pretty nice. And then here, I just set my name and email address. And very early I set a custom file to keep all of that stuff separate from my .emacs. I don't want it mixing in.

12:53 Core

image from video 00:13:03.467Amin: And then pretty much the only other thing that's in my main init file is just to require and load these different modules or packages of my configuration. I have these as actual packages or as actual features. They provide themselves. And that's just something that I've found straightforward enough to do. I know, for example, Prot uses a dual approach. He has some of his configuration that's more readily usable, available as actual packages. And then the other ones, it's just Emacs Lisp code. It's not actual packages. But for me, I just keep it simple. Everything has packages and that's about that.

Sacha: Fantastic. Let's dive into some of those configuration modules.

Amin: Sure, let's see. Yeah, so this there's this like core thing which is kind of included gets included in all of my other files.

13:53 no longer using bandali-configure; scoping errors, timing execution

image from video 00:14:27.533Amin: I wrote a bandali-configure macro shamelessly based on prot-emacs-configure which is what Prot uses and it basically is a way of kind of similar to use package for like wrapping a bunch of relevant like Emacs Lisp code all together. It has the benefit, if you use it, if there is an error in that block or in the body basically, then it won't crash everything. That body will just get ignored and we display an error. And that's also the main reason that Prot uses it. The one thing that I added extra to mine, which I took with inspiration from Echelle Yaron's ESY slash init step, is to wrap it up in basically time the execution of each of these blocks, which can be pretty helpful to help you see, okay, which part of my configuration is particularly slow. Usage examples. I just have it here. You can either basically pass it like a symbol like thing or you can also pass in a string as the first argument. And this is what will be displayed when you display a list of the evaluation times for all of these blocks in your configuration.

image from video 00:15:22.133Amin: Yeah, and then I have a neat little function here like configure-report-times that will report these times, whether in the order that it's encountered them, or you can have it sort by fastest to slowest, slowest to fastest, blah blah blah.

Sacha: You mentioned you're no longer using this. Is it because you wanted it to be easier to copy and paste your code? What got you to shift back to the regular vanilla type of configuration?

Amin: Right, as neat as it is, I didn't find it super useful. For one thing, because I don't add or remove a ton of stuff to my Emacs configuration regularly, so if there is an error, it wouldn't cause an issue for the rest of my configuration. I didn't really find that very useful. And then my other potential concern is that the way I was structuring things, I would put all of the configuration, let's say for GNU, in one of these blocks. But I wanted to be able to break that down into, for example, Org Mode sections more easily. So far, I just decided to not use it. I know I could technically break those down into smaller blocks, but I haven't done that yet.

Sacha: Ihor says, this configure macro looks a lot like good old use package, which you're not even using in the rest of your config. And I hear you about wanting to be able to split things into smaller blocks with more explanations in between them. So in my config, yeah, sure, I've got the use-package there to do the ensure and all that stuff. But I also have with-eval-after-load because I still want, you know, the links and the screenshots in between.

17:02 Why not use use-package

Amin: Right. Yeah, exactly. use-package is awesome. I have used that in the past, especially when I was using the straight.el package manager. It pairs nicely with it. But yeah, since then, I found it a little bit like too magical for my tastes, kind of along the lines of declaring an init file bankruptcy at some point I really wanted to understand every single line that I have in my Emacs configuration. And at the time, I didn't know a whole lot about macros or wasn't very well-versed with them. So I just ditched it in favor of simply using, as you mentioned, with-eval-after-load. And then that causes all that code to be basically delayed, not evaluated immediately, but when that package is loaded. And then as to when to pull that package in, depending on if I want it right from the get-go of my Emacs starts, then I would require it. Otherwise, I add this, as you also mentioned earlier, this kind of timer thing where if Emacs is idle for, I don't know, 0.2 seconds or 0.4 seconds, then go ahead and require this package.

Sacha: Ihor has a tip in the chat. Of course, Ihor has an Org way to do this. He uses use-package whatever config and then he has a noweb reference to the Babel blocks. Then he just says :tangle no on the source blocks so that they don't actually get repeated. Anyway, you can look at it later when you go through. I'll send you the comments or whatever. But show us how you're actually configuring things since you're not using this.

18:37 Defining multiple keybindings

image from video 00:18:55.133Amin: Then I just have another quick macro thingy here, bandali-define-keys, which wraps around Emacs's define-key. It affords me the convenience of defining multiple key bindings, and Prot's version of this (I think it's prot-emacs-keybind, or something like that) he imposes the limitation that the keys should be valid strings that can be passed to the =kbd= function, which is very fair and valid, but I wanted to not impose that, to keep the flexibility of using define-key directly. The consequences of that, as we can see, is we can pass in the old representation of key bindings, like the vector or whatever syntax, which Prot's doesn't support by choice, whereas mine does. Let's see. For example, let's look at the Bandali theme, which is all about... The appearance, I guess, of Emacs.

19:45 doric-oak uses emphasis instead of colours

image from video 00:19:45.900Amin: Yeah, so I just have a conditional block where, you know, if you're in a graphical environment, I'll just go ahead and load Prot's Doric themes, specifically Doric Oak, which is what we're seeing right now. I'm using, it's very beautiful, it's very subtle, and it uses emphasis, bolding and stuff to draw your eye to something instead of using a million different colors, which I find pretty nice. Yeah, and then for example here I set up some fonts. I use this Sahel font for Persian and Arabic text. I set a color emoji font here and this is like we get a kind of preview of what I do. It's like with-eval-after-load faces and then blah blah blah.

Sacha: Ihor would like to point out that with-eval-after-load is also a macro that calls another macro. So I'm just going to mention it because it's there. These are your fonts. This is your theme. This is great because everyone always asks, what theme is this? What font is this? All right.

20:49 global font scaling instead of the local ones

image from video 00:20:59.967Sacha: I like your text scaling tweaks that you're just about to go into. You've changed the global mappings.

Amin: Yeah, yeah, yeah. And I actually took this from Prot as well. And it makes a lot more sense. So by default, this, C-x C-+, -, blah, blah, blah, it only scales the text for the current buffer only. But in newer version of Emacs, in Emacs 29, they also added commands to adjust this globally, including the mode line and all that stuff, which is usually what I want, for example, in this presentation or when I'm sharing my screen right now. It scales everything up globally. So yeah, I just swapped these to be the default, and then I add keybinds for the just local variants in case I need to use that. Yep.

21:37 display-fill-column-indicator

Amin: And then here I have display-fill-column-indicator. I don't know, maybe this is just me, but sometimes I'm kind of OCD about keeping my text lined up at exactly, for example, the 70 characters column. I care a lot about that, especially if I'm writing code or text that I want to also visually look nice. And I enable this. And let's see, I enable it for prog-mode. So yeah, I guess if I, for example, do this... This little thin line that we see here, that's the display filler column indicator. I used to have it globally enabled, but then I found that a bit too much, so I just enable it with a hook in the modes that I want.

Sacha: Yeah, and the theme makes it very subtle. It's just there as a reminder, don't go beyond this line. You can if you really want to, but just try not to.

Amin: Yeah, exactly. And then my essentials... This is where I configure a lot of key behaviors of Emacs, all built-in stuff for the most part, or things that are key to my workflows. For example, I always want to start with a scratch buffer.

22:53 emacsclient for EDITOR and VISUAL

image from video 00:22:53.767Amin: Start the Emacs server if it's not running. And this is very useful, very helpful so that then you can call into an existing Emacs process with Emacs client and have it edit a file. I don't use it for anything fancy just yet. I believe Prot also mentioned in his video with you, Sacha, that he uses it for things like org-capture to spawn a new buffer in his existing Emacs session and things like that. You can do pretty cool things with it. But yeah, I just use it for being able to easily use my Emacs as editor and visual text editors. So yeah, this sets that up.

23:37 fundamental-mode-hook

image from video 00:23:42.200Amin: Adding a fundamental mode hook. Again, I took this from Prot.

Sacha: I was surprised by that because I was like, oh, there isn't a fundamental-mode-hook? Okay, that makes sense now.

Amin: Right, right. Yeah, there isn't a fundamental-mode-hook by design. But I still, in the past, have found that I wanted that. For example, for this display-fill-column-indicator, when I had it enabled everywhere, I was like, it would be nice if I could at least disable it for fundamental mode. And at the time, I didn't have this. I added this just recently. So if I decide to go back to using something globally, but I don't want it in fundamental-mode, then I can disable it using this. Yeah, and then some standard stuff like I prefer spaces and a tab with four characters.

24:23 indicate-buffer-boundaries

image from video 00:22:02.433Amin: Visually indicate buffer boundaries. This is a little bit hard to see right now, but here at the bottom left

image from video 00:22:02.433Amin: you see a little down arrow

image from video 00:24:33.800Amin: and then the little top arrow. And... Let's see if I can.

Sacha: Oh!

image from video 00:24:43.167Amin: And also here, for example, when it all fits in the view.

Sacha: Huh, that is cool. I was looking at that. What does it do? And so that tells you, you can still scroll up or you can still scroll down, and you don't have to look at the scroll bar to see where you are. It just says there's more there.

Amin: Yeah, exactly. Yeah. And it also helps distinguish when there's a newline character at the end of the file or not. So here in this buffer, there is.

image from video 00:25:10.533Amin: But if I delete that, you see this indicator here changed shape. But if I go back and add the new line again. So yeah, that's also been very helpful for me because I added configuration files and some of these pieces of software are sensitive to having a new line at the end of the file. So yeah, it's very helpful and useful for that.

Sacha: I would not have guessed that from the very short line in your config that turns that on. It's one line, setq-default indicate-buffer-boundaries 'left, and yet it adds this nice little nuance to the way that fringe looks.

Amin: Right. Yeah, absolutely. Perhaps I should expand more on it at some point later to explain these things. But yeah, just this one line.

Sacha: May I recommend screenshots?

Amin: Yes, you may, for sure. Yeah, I will definitely do that as well, because I'm also a bit of a visual person. I like seeing screenshots and videos, so yeah I'll take that to heart and do that for my own configuration as well.

Sacha: When I post this, I'll probably... I figured out how to have the transcripts and then screenshots embedded into my transcript. I'll generate it automatically from the subtitle file. Our EmacsConf transcripts are going to get so fancy next year. But you can pull those screenshots and drop them into your config. It'll be great.

Amin: Nice. Yeah, for sure. Sounds good.

26:36 enabling and disabling commands

image from video 00:26:36.433Amin: And then here, I just enabled some of these commands that are disabled by default. So yeah, it's useful, especially narrow-to-page, for example, or narrow-to-region. These are commands where Emacs disables them by default so that newcomers don't accidentally hit them and get very confused by what just happened. It doesn't disable them for good. It just basically prompts you for confirmation. Are you sure you want to run this command? I'm sure, at least about these commands. So I just enabled them. And then something like, for example, overwrite-mode, which I never use and I don't want to accidentally enable. I just put it disabled so that if I do accidentally hit the keys, which might be, I don't know, something insert or whatever, then it will prompt me to make sure that I meant to do that.

Sacha: That reminds me, I should probably turn that off for myself and then you get a whole new keyboard shortcut you can use too.

Amin: Right, yeah. Let's see.

27:37 package-review-policy

image from video 00:27:37.900Amin: Yeah, I have just one line setting for package.el. In Emacs 31, we will be getting a package review policy which is very helpful. So if you do use package.el for installing packages from GNU ELPA, NonGNU ELPA, MELPA or whatever else, you can enable this, and then whenever you update your packages, you'll get a diff of what changed in this new revision of the package that you're downloading and you're about to enable. And you can presumably say yes or at least see what's going on, which I find helpful.

Sacha: But you're not using packages, you mentioned, so you're just checking everything out and then you're just git pulling whenever you feel like it.

Amin: Yeah, so right now I'm using git pulls and git submodules, very manual. I put this here because I think it's generally a very welcome change and awesome new feature that I want to spread the word about. So maybe someone who's looking at my config, they use package and that's perfectly fine. So this is just here to spread the word about it mainly, I guess. And if I start using package at some point myself in the future, then I will have this enabled. Let's see.

28:52 getting the Info files from the Emacs source directory

image from video 00:28:52.800Amin: Very quickly, here I extend Info-directory-list. I like to, at least on some of my machines, use Emacs that I built from source directly in the source repository of Emacs. Just after doing make, I don't run make install, even though it's very easy to do that. You can install to a custom location by providing dash dash prefix when you're configuring Emacs. Sometimes I just find it more convenient for me to not do that and just run make and then exit and reopen Emacs. And for that kind of a setup, I just extend the info directory list to include the info subdirectory of the Emacs source repository so that the built-in Emacs info manuals will be available to me.

29:45 recentf, adding directories

image from video 00:29:46.600Amin: And then I use recentf for tracking recent revisited files. I bind it to C-c f r e for me to get a pop-up completion for visiting a recent file, it has completion. So if I hit TAB here, for example, we can see some of these files or directories that I visited recently.

Sacha: I see. And then you're adding the directory to it. So what does that let you do? Because I'm assuming you're already in there in the directory. But how does that change your recentf?

Amin: Right. So I need to think to remember this, but I think the point of this was that if I open a project in VC or in Dired, then I would like that directory to also get added to my recentf files list, because I think by default, recentf only includes files, not directories.

Sacha: You're in it, you start up Magit or whatever, and then you move on to something else, but you want to be able to easily go back to it.

Amin: Yeah, for example, I like to keep my recently visited directories in recentf as well. Because that's one of the main ways I jump between projects and stuff, even though there is literally a built-in Emacs project mode, which I still use. The only thing that I have here is... I don't want to add my home directory to the recently visited list, so the only thing that this function does is to skip that if I'm opening the home directory. That's about it.

31:38 Scrolling

image from video 00:32:10.933Amin: And then here I configure mouse and scrolling behavior. So I want Emacs to scroll very gently, one line at a time. I think the default is that when you reach the end of the page, it'll jump half a page down and then recenter. I don't remember default behavior because I don't use it very much, but yeah, this basically makes it very predictable. For example, when I reach the edge of the page here and I press C-n, it'll only scroll one line at a time, instead of jumping and then doing something like this.

Sacha: Oh yeah, mine does! Mine doesn't do that, so it does that jumping thing. I see what you mean here. Interesting.

Amin: Yeah, so you can tweak that with scroll conservatively and then scroll preserve screen position, I believe.

32:28 auto revert

image from video 00:32:37.733Amin: Yeah, and then I use autorevert, which is pretty helpful. So this will have Emacs watch, for example, files that are open in your buffers. And if they change on disk, Emacs will automatically refresh the buffer so that you get the latest version. The cool thing is you can press undo in one of these files that's been autoreverted so that you get the revision that was there right before the change. So I've used that sometimes as well.

Sacha: Yeah, and sometimes autofollow also is nice for log files and things like that. But yeah, autoreverting is great.

Amin: Yeah, for sure.

33:14 Repeat mode

image from video 00:33:14.067Amin: Repeat mode is something that I've only recently started using, especially with my Emacs EXWM setup, using Emacs as my window manager. For example, if I hit C-x o, we see here in the echo area where it says repeat with o or capital O. So I can now only press o instead of saying C-x o, C-x o to do that multiple times. Keymaps that have support for this basically indicate that they want to be repeatable can declare that. And then once you invoke one of the keys in those keymaps, then you can repeat it with just that single character. And for example, for my setup, I have that with my EXWM workspace switching keys. So I can easily go to the next and previous workspaces, many of them at a time by just pressing P and N instead of doing the shortcut multiple times.

Sacha: And actually, if you don't mind jumping ahead, the EXWM part of your config is fairly complex, and I think not a lot of people have a lot of experience seeing EXWM in action. And I don't know whether you're comfortable sharing you switching around to different workspaces, but if that is something that you can do, how are you doing all this awesomeness? I'm still too scared to use EXWM myself. Stability. But that's a me problem, not an EXWM problem.

34:51 EXWM

image from video 00:35:26.600Amin: Yeah, EXWM was pretty awesome. I used it back in 2018, '19 for a while, and then I kind of moved on to Sway and Wayland. But I don't know. It's something that I feel like once you try it, you want to keep going back to it. So recently, this past month or so, I decided to give it an earnest try and try to actually address any pain points that I've noticed. So it's much more usable for me now, and I'm sticking with it for now. I'm not a Wayland hater, but I'm just saying, at least for now, I'm using EXWM. And I'm happy to talk about it.

Sacha: OK, what do you love about your setup for that one?

Amin: EXWM?

Sacha: Yeah, yeah. Like, you're doing a lot of rename buffers. Yeah, yeah, yeah.

Amin: Right. Yeah, let me think. There's a couple of things. So, for the longest time, my Emacs EXWM configuration used super key as a prefix, which is the Windows [key] or the one with the logo, basically, to switch workspaces, launch applications and such. And at least the way that EXWM is right now, it doesn't... Like the way you have to add those global key bindings and kind of slows down the EXWM startup. And I had many such key bindings.

image from video 00:36:16.467Amin: So one thing that I did kind of recently is to define a prefix map here, like bandali-prefix-exwm-map. So I bind all of the keys and commands that I want here, and then this helps me really minimize what I'm telling EXWM, which is here. For example, this is how you set global keys with EXWM, and I just point it to my prefix map. C-c x and then any of those letters and functions that we saw. That's kind of annoying. I still use the super key here, but I have it s-x and s-,. On the left-hand side of my keyboard, X is right next to super, so I can hit it in one go with one motion almost as a single key with these two fingers. On the right side of my keyboard, I don't have a super key, but I have a control key that I remapped to super. On the right side, I do s-, with these two fingers. It's still very convenient for me to invoke those commands. And pairing this up with repeat mode, as we can see just here, actually, then I can hit s-, and then P, N, or H, J, K, L many times to switch workspaces or shift focus to different windows and stuff without having to hit that kind of annoying s-x or s-, repeatedly. Yeah.

Sacha: That sounds really cool. I should look into that. Sorry, quick aside.

38:03 Audio setup

Sacha: @blaiseutube would like to compliment you on your awesome audio setup. It sounds like you're in the room with him. Apparently, I sound like I'm on speakerphone, but your audio setup is top-notch, apparently. But that looks like a Blue Yeti, so I have to find out what's going on. what microphone are you using?

Amin: It is indeed a Blue Yeti.

Sacha: Yeah, yeah. So I just have to ask him for okay, what kind of boom mic? Anyway, we'll do that all offline because it's not Emacs related.

Amin: Yeah, it's just the Blue Yeti. Yeah, I turned down the gain. I used to have gain higher, but then it picks up more noise from around the room or around the house. So I turned down the gain a lot and then I get close to the mic so that it only captures my voice. Okay.

Sacha: I'm gonna need the boom. Otherwise, I'm squished into that corner. All right. So you were doing repeat-map before I said oh, let's talk about EXWM because you've got cool stuff there.

Amin: Yeah, and I can continue talking about the EXWM. There's a lot here.

39:10 keymaps for launching different applications

Amin: I have, let's see, s-, SPC. I bind it to async-shell-command to use as my simple, little, dmenu-thing for launching applications.

image from video 00:39:11.767Amin: Some of these things, like browsers, I still do them frequently enough, and I use different browser profiles. So I just define a new keymap so I can basically one-shot launch Chromium or Firefox in a specific browser or an incognito window and such. So yeah, I just do s-x b and then, for example, c to launch Chromium and all that stuff. So I found this pretty convenient.

39:49 bandali-call-interactively-insert

image from video 00:40:57.567Amin: Speaking of key bindings, before I get down this, let's see if I can find... C-c h. I think this is just before my EXWM setup. I'm pretty proud of this. I love this. It really goes to show how awesome Emacs is and extensible it is. Let's see. So as we know, these various help commands and describe commands are under C-h prefix. But some of them are not bound. for example, find-library or describe-face. Some of these I use pretty frequently. I was really having trouble coming up with descriptive-enough keybindings or short-enough keybindings for all of them. I put some of them here, for example, like C-c f l for find library. But I can't do that for all of them. What I did was just do C-c h a or C-c h d. What this will do is basically, if I show that, It basically opens up M-x, fills in describe-, and then I can just type, for example, face, and that's it. So it basically opens up the minibuffer for me, pre-fills it with the string that I want, and I can type what is it that I'm looking for. And I found this to be better than trying to bind a million different keyboard things for describe this and that, apropos this and that, find this and that. So yeah and the way that we do that is to just use a minibuffer-with-setup hook, and you just have a little lambda to insert the string that you give it, and then you invoke it.

Sacha: Yeah, this is pretty cool. When I saw that in your config, I was like, I'm going to steal that. Pre-filling the minibuffer but still letting you do stuff with it, it's such a powerful thing, not just for completing the command itself, but even for when you're using the command, but you want to do something with the input before. You don't want to do it all the way, send it in and submit right away. You want to actually do something with it after you insert it. So great tip.

Amin: Yeah. Thanks. Yeah, it's pretty useful. It's pretty nice. Yeah. And then back to the Emacs or EXWM stuff. So before I had, I used to yeah, sorry, go ahead.

Sacha: Sorry. I forgot whether I was muted or unmuted.

Amin: Okay, no worries.

42:26 workspaces

image from video 00:42:56.600Amin: For the longest time, I had 10 default EXWM workspaces on startup, and that can slow things down a little bit. So I found that okay, I don't really use all 10 workspaces always. So I set it to five. So I get five workspaces initially. But I still bind keys here. Like if we go down. Let's see. Here. So here, I define those keys for all the way from, let's say, from 0 to 9 for all 10. And then if I try to switch to a workspace that doesn't exist, then EXWM will just go ahead and create it for me. Yeah, so I found that pretty cool. You can create workspaces on the fly. Yeah.

Sacha: Yeah, and I saw that it moves your current window there, too. So that's just like, OK. Let's move it to workspace number two or whatever. Very cool.

Amin: Yeah, yeah, yeah. I have keys or convenience keys for moving some window to some workspace. Yeah, it's nice. Let's see. Let's see. Yeah. So these are just made key bindings. I use hjkl here for switching windows.

43:46 ZSA Voyager split keyboard, super x as a single key

image from video 00:45:46.167Amin: I also have a ZSA Voyager split ergonomic keyboard. I can basically customize it infinitely. For example, I don't really have a super key on the first layer. What I have is a key that will do the s-x thingy, basically my prefix. So that's the last missing piece is that if I'm at home and if I have this keyboard with me, then I just hit one key and then that's it. I'm in my prefix. But even if not, on the laptop, the s-x or the super comma are still easy enough for me to hit it with one hand.

Sacha: Now I'm jealous and I definitely want to assign my prefixes to their own keys. Very tempting. I've started using the numpad because my laptop has one. I only use the numpad rarely, but we all need more keys.

Amin: Yeah, ergonomic keyboards are pretty nice, especially these ones. For example, the ZSA ones where you can put QMK on it, the QMK firmware. You can define keys in a C file. I can actually show that. Let's see... QMK Firmware, Keyboards, ZSA, Voyager, Bandali, and then keymap.c.

Sacha: Is this in your repository somewhere?

Amin: Right. It's in a different repository, but it's still on git.kelar.org next to my configs repository. You can find this as well, but if I go smaller... Yeah, you can define keys here and have different layers, like the base layer. And then you can define a key to switch between different layers and put some of the keys there anyway. So yeah, it's a whole rabbit hole in and of itself. Prot also uses a split ergonomic keyboard. It really does help if you're typing for long periods of time. I actually had these for a while, and I wasn't using them too much, but I started slowly getting some pain in my wrists and here. So I was like, okay, I have the keyboard. Might as well put it to good use, and I've started using it.

46:26 Keybindings

image from video 00:46:53.767Sacha: Okay, so most of your keyboard shortcuts come off that kind of s-x or C-c something, and then you have a long prefix sequence, and you just remember everything or you use your... pre-fill some of it and then fill in the rest of the command.

Amin: Pretty much all my window management related keys are on this s-x prefix that I'm showing here. And then I have a few other ones which I think I showed earlier. Is it this one? Anyway, I bind a few general keys outside of the s-x thing, like C-c e i. For example, I have C-c e e for eval-last-sexp. I do that a lot, so it's easy to hit that. Making frames or deleting frames

Sacha: I love how Emacs uptime is something you use frequently enough that you have a keyboard shortcut for it.

Amin: Yeah, of course. I mean, I'm sometimes curious to see how long has my Emacs session been running. To continue with the EXWM stuff, let's see. This is just some keybindings I define here. It's all Emacs Lisp, right? It's amazing. You can mapc over whatever sequence and create keybindings like that. Only with Emacs we can do things like that. I just love it. Let's see.

48:05 Media buttons

image from video 00:48:36.200Amin: I still keep these three other keys for raising and lowering the volume and toggling mute off of that prefix and just directly on my keyboard, hitting it directly in the exwm-input-global-keys because I do that very, very frequently. But I also have scripts that I can invoke. I should do keycast. So yeah, I can invoke the prefix with semicolon. I can set my volume here, adjust it here, type in what volume I want, or with the single quote, I can enter a value for the screen brightness. I like these things to be exact depending on the lighting in the room. I have preferred brightness values of 50 or 12 or 10 that I manually adjust. I guess it's a poor man's version of having something with a light sensor that can pick up and adjust automatically. I do it manually. Yeah. Sorry, you just muted yourself again.

Sacha: You're just probably this close to writing the Emacs Lisp that takes your webcam image and then adjusts your light. But I think Prot was also saying he likes to do the lighting changes manually as well because warmer colors versus cooler colors and all of that stuff. Anyway, so you have all these buttons that EXWM listens to and it can launch various things for. That's a lot of things.

Amin: Yeah, those are pretty cool.

49:43 exwm-input-simulation-keys!

image from video 00:50:08.267Amin: EXWM has this lovely feature called input simulation keys where You can basically use it to bring Emacs key bindings to other applications like Firefox or whatever. And yeah, it's mind blowing when you try it for the first time. for example, I bind C-b to just hit the left arrow on the keyboard. And it does that. So I can define all of these commands that I'm using or used to using in Emacs. So I can get them in Firefox or other applications as well. Realistically, it's mostly Firefox. It's the only other program that I spend any reasonable amount of time outside of Emacs.

Sacha: Let me point out this very important one that you have there. Under selection, cut, copy, paste, I see a control W. Input simulation keys. So this is for all the people who have accidentally closed their browser tab while trying to copy text. This is how you solve that problem. Use EXWM and use EXWM input simulation keys and you don't have to accidentally close your browser tabs again. @blaiseutube asks, hey, what about time since last save? Or do you have some kind of autosave magic? you know, in reference to the uptime thing, right? You have this thing that shows you...

Amin: I don't think I have anything for autosave, but I have this habit of... I save everything pretty regularly. Yeah, so I've never really needed that feature, but I'm sure Emacs has something where you can, at the very least, just very dumb, simple implementation of has it been idle for one minute, then just do a save buffer. You can roll your own. But I don't have anything.

Sacha: All right. I'm getting really tempted now to try out EXWM, even if it's just for those global keyboard remapping things.

51:39 exwm: managing floating windows

image from video 00:51:43.100Sacha: How is it for windows that you've got to have floating? I feel like it's very good at handling tiling things, but how is it for sometimes the apps kind of really want the floating window?

Amin: Right, yeah, so you can toggle any window to be floating or not, and you can also — actually, we're just looking at it here. EXWM manage configurations, to match on the instance name or the class name of a window that you can get from `xprop`, to automatically make that tiling. For example, if I do my prefix and then capital T, it launches a floating terminal for me here. And if I go back to where I set it up, I just launch Xterm with the name argument. This is where it can set the instance. And I just put any string you can want, like floating, for example. And then here in my configuration, I just check that if the instance name is floating, then I'll go ahead and float the window. Simple as that.

Sacha: All right. This is starting to look exceedingly tempting. Lol, I save everything regularly, so he's one of those people who compulsively hit C-x C-s.

Amin: Yeah, I do that a lot. I don't know. It's just me. But, yeah. Yeah. And then, I don't know. EXWM is awesome.

53:11 exwm: application-specific local simulation keys

image from video 00:53:11.000Amin: You can also put local simulation keys, application-specific simulation keys, depending on, the application, terminals, for example, or, Zathura. This is a PDF viewer. To have application-specific custom key bindings, how cool is that? For example, if I'm in Xterm or something like the Mate terminal, hitting C-c C-c twice basically, it'll just send the C-c key to the terminal. Because one thing with EXWM is that you can set it to capture a couple of Emacs prefixes, like C-x or C-c. So the application by default doesn't see it because Emacs captures it. But this is one of those mechanisms by which you can send a key through. Let's see.

54:04 binding C-q to exwm-input-send-next-key

Amin: The other thing is, you can set it like EXWM inputs send next key. So the default is C-c C-q, but I just bind it to C-q, and I, for example, can do C-q C-t to send C-t to the underlying application. So that's the other thing. Yeah, and then let's see.

54:28 Renaming buffers

image from video 00:55:05.333Amin: So this thingy here, I enable EXWM and I add this rename hook and all it does is basically to add the window titles to the buffer that I can see on the mode line. But as long as it's within a certain reasonable length, like for example, I have 25 characters. If it's longer than that, it will just put dot dot dot. So yeah, that's all the purpose of that. Let's see, for example, if I launch Xterm, it appears there. The perfect example is actually here on the right-hand side. On the mode line, we see Firefox, ESR, Emacs, Comp Chat. It's a bit long, so it just puts the dot dot dot there. So that's all that does.

Sacha: Yeah, now being able to use Emacs to manage the tiling of these things instead of my having to fiddle with alt-dragging things to snap nicely into buffers. Yes, very cool stuff. EXLDM. Gotta try it.

Amin: Yeah, for sure. Yeah, let's see.

55:36 dunst for notifications

image from video 00:55:36.300Amin: Here I launch Dunst if the executable is installed for getting notifications in ESWM. I think there's at least one or two Emacs specific packages that implement a simple notification daemon or backend so that Emacs itself can handle that. But I found Dunst good enough for my use cases coming from I3, Sway, like tiling window manager background. I just reuse that. So yeah, I just start a process, keep a handle of it in this dunst process variable here. And this thing I discovered recently, it's cool. using set-process-query-on-exit-flag, you can basically have Emacs not ask you if you want to exit Emacs if that process is still running. It'll just kill it without confirming with you. So just a little convenience.

Sacha: That is also cool. Just a heads up, I have about 15 minutes before the kiddo runs out because she'll be done with school then. Even just the EXWM part and other things that you've shown us in the config have been super awesome. But are there other things in the next 15 minutes that you would love to show people so that they can see how it works in practice?

56:54 exwm xsettings and responding to screen configuration changes

image from video 00:57:13.733Amin: One thing I'll just mention, EXWM, one more thing, and then I'll go check. I think this is kind of recent: EXWM xsettings, and this allows you to dynamically at runtime change some of these things that you would normally set in an X resources file, like fonts. These kinds of settings were especially commonplace back when Wayland wasn't a thing or wasn't very popular. You would set some of these font settings there. With EXWM xsettings, you can do this dynamically, and what's awesome about that is it also lets you hook into, for example, if your screen configuration changes, if you plug in a monitor or unplug it, then you can run whatever `xrandr` command to set it up and also adjust those settings. The main thing I use it for is to change the DPI setting. The thing with X11 or Xorg is, unfortunately, there's no per-monitor DPI. There's one global DPI. But I found that on my high-DPI laptop screen, if I set the resolution to 1920x1080 instead of the full resolution, then the default DPI of 96 works just fine with my external monitor as well. All this little hook does, by calling into this function, is: if I'm plugging in my external monitor, lower the resolution and lower the DPI, and if I unplug it, go back to the high thing. I just love this.

Sacha: That's great. We're definitely not going to demonstrate that because plugging in and unplugging monitors is not a good thing for screen sharing, but that sounds really cool. When things change, you can actually get your system to adapt to the changes for you.

Amin: Yeah, it's lovely. Let's see. There's so much more to talk about.

58:59 Slowly getting back into Org mode

Amin: I'm slowly getting back into Org Mode again. For the longest time, I didn't use it and I just used Markdown for my website as well. But I found that it's kind of limited. For example, I was using a Markdown implementation that was written in C and I can't easily customize it. Whereas with Org, I can hook into or create my custom HTML back-end that's a derivative of ox-html, even if I don't necessarily like the defaults or the settings for ox-html. I just recently started writing a new back-end called bhtml for Bandali HTML. It's just a boilerplate. I don't have much there yet, but that's the idea.

Sacha: I love how you can hook into all of these different aspects of Emacs and get it to do exactly what you want.

Amin: Yeah, so that's cool. Let's see.

59:58 chat notes

image from video 01:00:16.067Amin: I have written some things about the prompt for this meeting. Yeah, so I talked about that stuff briefly. Minibuffer setup. Things that I love about my setup is that it's kind of portable, simple. People can easily copy things from it if they want. It's kind of self-contained. And that was kind of a big thing a while back when I wanted to use my configurations on a couple of work machines. And these don't have direct outbound internet access. So I couldn't do things like installing packages with Elpa because that's done over HTTP. So yeah, I use submodules now. I recently began documenting my setup, very much inspired by Prot and Sacha and others.

1:00:52 Mode line

Amin: The things that I'm looking forward to tweaking next is the mode line. This is basically the default mode line of Emacs. A couple versions ago, they added a setting for compacting the mode line, which improves a lot of the extraneous whitespace in it, which is great. It's still... There's too much information. If you use multiple windows or even especially if you use EXWM all of those things like the date or like the battery get repeated in all of the windows, so I'm looking forward to doing my mode line in such a way that for example, it shows most of those things. And Prot actually has an excellent video about that where he shows how you can create your own custom mode line.

Sacha: I've also been tempted to start using the header line too because that's another thing that you can put information in.

Amin: Right, yep, header-line is awesome.

1:01:49 display-buffer-alist

Sacha: Yeah, the display-buffer-alist is particularly powerful because you're combining it with EXWM, so it'd be interesting to see how you can manage windows and applications and stuff.

Amin: Especially, just like how we saw in today's video call and also a call that I had with Prot recently. For example, if I open a describe-variable or something, it'll by default use the right area of the screen right now where our video is. So it reuses that. So I'm also looking forward to reading more about and configuring display-buffer-alist.

1:02:23 TRAMP slowness, maybe disabling VC detection?

Amin: I'd like to figure out some TRAMP slowness. I recently tried using it again. It's awesome. You can seamlessly open files, SSH into other machines and edit files there. But I don't know. It's kind of slow. So I want to see aside from the latency, you know, the physical limit of the latency because of the distance. Is there anything slowing it down? I think I read in the Tramp FAQ that maybe trying to disable VC mode or VC detection for remote connections might help speed it up, or at least having it do only Git, for example, because by default, Emacs' VC has support for Mercurial, CVS, SVN, Git, RCS even.

Sacha: Anything anyone has ever wanted to use in the last 40 years. Here we go. I saw in your chat config actually that you were doing something with the SSH configs and I'd never come across that. So I was like, oh, that's something I should look into later.

Amin: I don't remember the specifics, but it's all out there. Feel free to look into it.

1:03:39 eat

Amin: Especially with this EXWM setup, I still use Xterm sometimes and I have the Emacs EAT terminal, which is a terminal emulator written in Emacs Lisp. If I launch it right now, it's awesome. It actually is very powerful and it's a properly capable terminal emulator. It just can be a little bit slow. It is slower than xterm, but it's still a lot faster than whatever Emacs has built in. So this is pretty cool. But yeah, I don't want to use it a lot. And I kind of started testing, delegating more things or using more async-shell-command to just basically open this prompt and then do whatever I want. anyway.

Sacha: I've also heard things about Ghost TTY. Anyway, so that's another thing to look into. Yes, so @Paniash47 says, "With Emacs 31, there's a new variable where you can hide the minor modes in the mode line." @pkal says it's mode-line-collapse-minor-modes. And @Paniash47 also says, "I personally use the Minions package by Tarsius, and it has some nice features in addition to the built-in features." So other people are tinkering around with their mode lines as well.

Amin: Yeah, it's pretty cool. And then I don't know, I think maybe you touched on something a couple of minutes ago that I was going to go back to, but I forget.

1:05:07 TRAMP completion

Sacha: Tramp SSH completion out of your configs. I was like, there's a Tramp sconfig in here that I've never used. And that sounded interesting. Yeah, tramp-parse-sconfig.

Amin: Ah, right, right, right. Yeah.

Sacha: Which, of course, we're not going to let go because it's private stuff, but yeah.

Amin: Right. Yeah, you're welcome to try this. I'm pretty sure, actually, I took this from the Tramp manual itself. And it's one of those things where it's set and forget, I don't remember. But yeah, it's here. There was something else that I also wanted to show, but I forget. Let me see if looking at the outlines will remind me or if I will see it.

Sacha: And that's one of the things I love about literate configuration is, you know, just kind of look at the structure and skim it and try to find something with keywords and ordered lists and all that stuff.

Amin: Right. Yup. Exactly.

Sacha: Oh, and you know, people will have access to your full configuration because it is in your repository and you have that lovely HTML expert for it as well. So if you, uh, if, if people want to follow up, they can go through that at length. At some point, you're going to add some more screenshots and possibly even video clips to it. so that's there you at git.kelar.org

image from video 01:06:34.567Amin: This is my configurations repository. If you go here to treeview .emacs.d, this is the org file. I also export all of those individual components into this lisp subdirectory. All that stuff is here. The QMK thingy that was mentioned.

1:06:54 ffs: form feed slides, ^L

image from video 01:08:15.933Amin: Oh, I wanted to mention FFS. Okay, I'll do that as well. Yeah, what's up with that?

Sacha: I was trying to find information. It was like, there's no package. It's not what is this thing?

Amin: It's FormFeed Slides and it's going to soon be a package. I was actually talking to Prot about it and I'm hoping to submit it for inclusion in GNU ELPA within, I don't know, the next couple of weeks. It's basically very similar to Prot's Logos package. Turns out we both had the same kind of idea at the exact same time in 2022, and we both used it for our LibrePlanet 2022 presentations. Of course, Prot being the diligent person that he is, he polished his work, documented it, put it on GNU ELPA. I still haven't gotten around to doing it yet, but better late than never. Yeah, let's see. I can maybe show a quick demonstration of that. So let's see. Let's see. Anyway, so if I go to my website sources and net-beyond-web. So I had the LibrePlanet talk a couple years ago. So what FFS is basically, it looks for a particular character in this case, or the default case, it's the page delimiter, ^L, which you can insert by hitting C-q C-l. It basically then designates each of these areas as one slide. So, very, very simple slideshow that you don't even have to use Org or outline or any other major or minor mode. If I launch ffs, by default, it's in a mode where it binds a couple of convenience keys, like p and n, to go into the next and previous slide. You can hit e to edit a slide, similar to Org source, and then make your changes and all of that. And then you can start a presentation by hitting s.

image from video 01:08:58.767Amin: It has hooks for, for example, bumping up the font size or whatever, hiding the mode line. I can toggle the mode line by hitting M here. Let's see. I can also toggle the cursor, to make the cursor visible or not. So, yeah. And then I'm just hitting P and N.

Sacha: Very simple, very minimalist. You have a file, you've got page markers, and that's all you got.

Amin: Yeah, pretty much. And then...

1:09:34 Speaker notes

Amin: The neat thing that it has that I also liked implementing at the time is it has a speaker notes feature.

image from video 01:09:47.767Amin: So you can designate a file as being the speaker notes where it has the same structure separators with ^L. But you can type your notes over here, whatever. And you can basically open these in two different windows or two different frames on separate displays. And then in whichever one of those you advance the slides, like p n n, it also does the other one.

Sacha: That's brilliant. I was looking for a way to do that so I can pretend to know what I'm talking about when I have something on screen, but I can just read my notes or even just remember what points I wanted to make. So this is great. You have speaker notes. You've got the main screen. They can be in two different frames. You can have your frame that you're sharing and your frame that you're not sharing that has all of your cheat sheets. Excellent. And on that note, in about one minute, the kid is going to come running out and want to have snack and all that stuff. Thank you so much for walking through parts of your config. There is more. And so everyone who wants to find out more can go check out your setup. I have a great many things that I want to try out, starting from EXWM to little things like figuring out a boom mic setup because apparently your audio setup is making me very jealous. Yes, thank you for doing this. I'm going to post the transcript and the chapters. I have a chapter every minute. It's going to be a long time. But it was good. Lots of cool stuff. Thank you again.

Amin: Sounds great. And yeah, you're very welcome. And thank you so much for having me as well, Sacha. I'm very delighted to be here, especially, I think, just by chance. I think I'm the first person who you're doing this with after the long hiatus. So that's an extra honor for me. But yeah, it's been fun. I could go on for hours. I'm sure we both could. This has been fun.

Sacha: If we wanted to go on for hours, Prot has more flexible scheduling, so he can chat with people for two hours and stuff, and you already have conversations going on with him. But I unfortunately have a small mammal who's 10 years old and loves me very much, and likes to not let me concentrate for very long. But thank you everyone for joining. Thank you for the chat. And thank you also, stream, for all the interesting questions. I will send you all the information and update the post. And we'll see you all on Thursday. I've got another chat. All of a sudden, all these Emacs chats are going to happen. Thanks. Oh, and you said you're happy to be on the hook for doing another EmacsConf this year, right?

Amin: Yes. You can hold me to that. There will be another EmacsConf and I will be active in it.

Sacha: Alright then, I'm going to end that broadcast. Thanks everyone, bye!

Amin: Thank you, bye bye!

Chat

  • sachactube: This is a test message
  • sachactube: Getting ready for Emacs Chat 21 with Amin Bandali, https://sachachua.com/blog/2026/05/emacs-chat-with-amin-bandali/
  • JacksonScholberg: Yo
  • sachactube: Yo yo yo, we are live!
  • IhorRadchenkoyantar92: … and the list can continue until the end of the stream? :)
  • IhorRadchenkoyantar92: do you compile those packages?
  • sachactube: Automatically compiled by prepare-user-lisp because of user-lisp-directory, I think
  • IhorRadchenkoyantar92: makes sense
  • IhorRadchenkoyantar92: this configure macro looks a lot like good old use-package
  • IhorRadchenkoyantar92: I just do (use-package foo :config ) and then :tangle no in actual src block
  • IhorRadchenkoyantar92: what is funny, with-eval-after-load is itself a macro
  • sachactube: hahaha, it's much smaller though
  • IhorRadchenkoyantar92: not smaller at all! Because there is recursion with-eval-after-load (macro) -> eval-after-load (also macro!)
  • IhorRadchenkoyantar92: hmm. wrong
  • IhorRadchenkoyantar92: ok. let me not do two things at the same time
  • blaiseutube: yay, I made it!
  • blaiseutube: screenshots and also asciicinema
  • blaiseutube: asciinema ?
  • blaiseutube: whatever
  • sachactube: and gif-screencast
  • blaiseutube: nice
  • blaiseutube: Sacha, your mic volume is just a bit lower than his so it's a bit harder (for me) to hear you.
  • sachactube: Hmm, let me try turning my dial, let's see if this next one is better
  • blaiseutube: better, I think
  • blaiseutube: it's also that Amin has an awesome microphone. The result sounds like Amin is in the room with me and we are both listening to you on speakerphone. it's not terrible
  • blaiseutube: we're all friends her
  • blaiseutube: here
  • sachactube: I think we have the same mic, but he has an awesome setup, so I'm going to bug him for tips =D
  • paniash47: Hello there! Nice to see this chat. :)
  • blaiseutube: yes, low gain and close mic is good. Sacha if prefer to avoid a boom, you can use a microphone with a tight pattern and increase gain. LMK if you want to unleash my inner audio engineer.
  • sachactube: oooh. my mic is right next to my laptop though, so I'm not sure I can get away from the typing noises
  • sachactube: I'll just have to get cozy with y'all
  • blaiseutube: mini buffet is an underrated superpower. I think Kakoune adopted that also
  • blaiseutube: helpful for a11y and users with sequential processing/ ADHD issues
  • blaiseutube: (I noticed that the comments are recorded so I'm trying to add value 🥴)
  • paniash47: Split keyboards make sense with vanilla keybindings. I'd like to switch but moving from evil is difficult :(
  • sachactube: much appreciated!
  • blaiseutube: what about "time since last save" or do you have some auto save magic?
  • blaiseutube: 🤯
  • blaiseutube: emacs all the things
  • blaiseutube: LOL, "I save everything regularly" …so he's one of those people.
  • paniash47: I think with emacs 31, there's a new variable where you can hide the minor modes in the modeline
  • pkal_: mode-line-collapse-minor-modes
  • paniash47: I personally use the minions package by tarsius (Magit author) and it has some nice features in addition to the built-in feature.
  • paniash47: ghostel is the package :)
  • blaiseutube: BRB

Find more Emacs Chats or join the fun: https://sachachua.com/emacs-chat

View Org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- Emacs Chat 21: Amin Bandali (Post Sacha Chua)--L0--C0--2026-05-04T18:28:11.000Z

Sacha Chua: From David Dimagid: What we talk about when we talk about recommending Emacs packages

David Dimagid wrote this post for Emacs Carnival May 2026: "May I recommend…". Here it is!

Someone recently said on emacs-devel that they'd like to talk about recommending ELPA packages. Someone else said we should first ask what "recommending" actually means. RMS opened a thread asking that very question. It's still open, and you can follow it there (ELPA: to curate or not to curate).

I think we could apply Rich Hickey's technique here and start by looking up the definition of "recommend" in the dictionary. I invite everyone to do so with whatever dictionary you have at hand and to trust your definitions.

Now, we could evaluate ELPA packages for recommendation based on whether they complement or improve functionality already present in the core. For example, diff-hl by Dmitry Gutov. Its description says:

diff-hl-mode highlights uncommitted changes on the side of the window, allows you to jump between and revert them selectively. In buffers controlled by Git, you can stage and unstage the changes.

That last feature —staging partial hunks— is missing from VC, and diff-hl adds it seamlessly. We could say diff-hl complements the core.

Then there are major mode packages, like csv-mode, markdown-mode, cobol-mode, and so on. They add functionality that doesn't exist in the core. They have no direct equivalent. We could call them standalone packages.

Now consider another excellent package, like diff-hl, that depends only on the core: expreg, by Yuan Fu, the region expansion package. With a single key, it expands the region based on context. The core already offers this through sexp movement commands, but not with a single keybinding — you need several. Some will prefer the native core way; others will prefer the package. We could say expreg improves or, depending on how you look at it, duplicates the core's functionality.

So, in my opinion, package recommendations should be structured around their relationship with the Emacs core. I believe the best-regarded ELPA packages should be those that encourage users to use what the core already offers, first and foremost, and then try those packages because they extend a feature the core lacks or complement it. This would also help more people discover lesser-known core features, increase bug reports, and, over time, bring more contributors to Emacs. That way, the Emacs community could have a package repository it can trust for as long as Emacs exists. Perhaps the person who wrote Elfeed would have known about Newsticker and would have contributed to that package instead. Perhaps if we recommended what Emacs already offers, the Elisp we write would be Elisp of and for Emacs.

If you e-mail me your comments, I can forward them to David!

View Org source for this post

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

-1:-- From David Dimagid: What we talk about when we talk about recommending Emacs packages (Post Sacha Chua)--L0--C0--2026-05-04T17:06:00.000Z

Sacha Chua: Emacs Carnival May 2026: "May I recommend..."

It's May and I like puns, so I'm going to suggest "May I recommend…" as our Emacs Carnival theme this month, building on lively conversations about people's favourite packages on lobste.rs, Reddit, and Hacker News. Let's go beyond packages and talk workflows, tips, practices, perspectives… whatever you'd recommend!

It was pretty nice having a wiki page that people could edit without needing to wait for me, so if you write about this topic, feel free to and add your link. If you run into problems doing that, please e-mail me and I can add the link for you.

People have already started sharing their recommendations:

I'll also do a round-up post at the end of the month so that it shows up in people's RSS feeds.

Looking forward to seeing what y'all recommend!

View Org source for this post

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

-1:-- Emacs Carnival May 2026: "May I recommend..." (Post Sacha Chua)--L0--C0--2026-05-04T16:50:38.000Z

Sacha Chua: 2026-05-04 Emacs news

Thanks to everyone who shared their thoughts on the April 2026 Emacs Carnival theme of Newbies and Starter Kits. Check out that post to see all the entries people have shared so far. I enjoyed chatting with Prot about the topic, and he shared some defaults that even experienced users have been trying out. The carnival theme for May 2026 is "May I recommend…". Looking forward to reading your posts!

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-05-04 Emacs news (Post Sacha Chua)--L0--C0--2026-05-04T13:08:22.000Z

Bicycle for Your Mind: Links of Note 2026-05-04

ToolsTools

  • I get the sense that there are going to be more applications like this. I Shipped a macOS App Built Entirely by Claude Code Indragie Karunaratne
  • This made me laugh. Emacs elevator pitch. I agreed with the pitch, but it still made me laugh.
  • Jack and I do share this trait. I think I need a workflow intervention (aka Stop Leaving Emacs) Baty.net. I fight the urge with the acknowledgment that even in this alternative to Emacs I will try to press the Ctrl + Spacebar to set a mark to select a section. The Emacs keybindings have fused themselves into my fingers and I feel lost without them. But the urge to switch is a real thing.
  • I go through my application folder and sometimes I have no clue what the program I am looking at does. I blame it on this blog. To come up with content, I have to try a whole host of programs and I forget what they are supposed to be for. This is an attempt at keeping some control over the content of your application folder. macAppLibrary · coefficiencies
  • stoicswe/minimalist: Minimalist text editor for macOS.. This is not bad. I prefer Coteditor for this task. Minimalist might appeal to some of you. Check it out.
  • An entry into the “second brain” product category. Tolaria — A second brain for the AI era. It is free and open source.
  • Loren came to the same conclusion I did. Done with Medium. Control your own content on the web. The service I use, Blot.im, is cheap and fantastic. There are a lot of other solutions available for the non-technically inclined. Stay away from people who want to monetize your content for their own wallets.
  • Markdown Ate The World. I found myself agreeing with this article. I use Org-mode for my personal documents. It is similar to Markdown, in that it is syntax applied to a plain-text file. At the end of it, it is just that, a plain text file.
  • Monarch - The Productivity Engine for macOS. Looks interesting. I plan to try it out.
  • The field of launchers is getting competitive. I use Alfred. There are a host of other competitors. LaunchBar, Raycast and QuickSilver are still in the marketplace. Tuna is a new application which is in beta. My hesitation to try out Monarch is based on the realization that I am used to Alfred and it seems to do all that I need or can think of. Makes it difficult to summon up the energy to try something new. SOL - Open Source MacOS Launcher, is a free and open source competitor. I haven’t tried it out, you can, and let me know how you like it.
  • FINAL|FINAL - A Writing App for Academics. This is an app I am keeping a close eye on. Has potential, but it is alpha software so tread carefully.
  • Zed — Your last next editor reached version 1.0. It is good. Their focus is on developers and AI. I think they ignore writers for the most part. I like it better than VSCode.
  • Notepad++ for Mac: Free Native macOS Code Editor got released. There are ex-Windows users who were waiting with bated breath for this. I don’t get it. There is Zed, BBEdit, Coteditor, SublimeText, and VSCode, to name only a few. What is the lure? I downloaded it and will write up a review of it one of these days.

Update: There are some legal issues associated with the Notepad++ product I talk about here. Trademark Violation: Fake Notepad++ for Mac | Notepad++. The original author says that the macOS version is an unauthorized clone. I am not going to cover this product. The macOS version has been renamed to Nextpad. Sounds shady as fuck.

That is all I have for today.

macosxguru at the gmail thingie.

Note: Thanks to Skylar Kang for the picture.

-1:-- Links of Note 2026-05-04 (Post Bicycle for Your Mind)--L0--C0--2026-05-04T07:00:00.000Z

Magnus: Jumping to errors in Evil

Recently I realised that it'd be really nice if jumping to errors would store the previous location in the Evil jump list. These definitions do just that

(evil-define-motion mes/evil-goto-next-error (count)
  :jump t
  (unless (bound-and-true-p flymake-mode) (signal 'search-failed nil))
  (flymake-goto-next-error count))
(evil-define-motion mes/evil-goto-prev-error (count)
  :jump t
  (unless (bound-and-true-p flymake-mode) (signal 'search-failed nil))
  (flymake-goto-prev-error count))

and for now I've bound them to C-j and C-k (because that's what evil-collection does)

(general-def flymake-mode-map
  :states 'normal
  "C-j" 'mes/evil-goto-next-error
  "C-k" 'mes/evil-goto-prev-error)

This makes it easier to make a change, fix the errors caused by the change and then return to where I was.

-1:-- Jumping to errors in Evil (Post Magnus)--L0--C0--2026-05-04T06:16:00.000Z

Protesilaos: Emacs live with Sacha Chua and Philip Kaludercic on 2026-05-14 17:30 Europe/Athens

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

On the 14th of May we will meet with Sacha and Philip to talk about the upcoming Emacs 31 and other developments heading into Emacs 32. Philip is a contributor to core Emacs and the main driver behind the new newcomers-presets theme, among others. We will cover themes related to the newcomer experience.

I am looking forward to it!

-1:-- Emacs live with Sacha Chua and Philip Kaludercic on 2026-05-14 17:30 Europe/Athens (Post Protesilaos)--L0--C0--2026-05-04T00:00:00.000Z

Protesilaos: Re: Emacs and keyboard ergonomics

This is an excerpt from an exchange that I am reproducing with permission from my correspondent. I am not sharing their contact details.


As I understand, you’re using the regular keybindings in emacs. I’m trying to transition away from the vim way of editing text and was wondering if you’re using a more ergonomic approach for your text editing needs. I remember you were using some ergo split keyboards for that?

Indeed, I have a split mechanical keyboard (a gift from a person who identifies as “Andreas”). It is the Iris revision 8 by Keebio. I also have a regular full-sized keyboard (a gift from Arialdo Martini), which is the Keychron K5.

[ Keyboard-related articles of mine: https://protesilaos.com/keeb/. ]

The split keyboard is nice, though it is not a magical solution. Even with a regular form factor keyboard, I can work with the standard Emacs keys without any problem. What helps me the most is to configure one-shot modifiers. This means that I can tap (press once, then release) a modifier key, then tap a regular key to register it as a modifier+key event. For example, C-x is this: tap Ctrl, then tap x. Shift tapping is especially nice for prose, by the way, and eases the pressure on the otherwise weak pinky. The time window for registering a one-shot modifier is configurable.

The other useful tweak for a regular keyboard is to rearrange the layout of the modifiers. I prefer this order:

Super Alt Ctrl Space Ctrl Alt Super

[ If there is a Menu key, move it to the right corner and consider assigning it to the Compose key if you are on Linux. ]

Now Ctrl is under the thumb, which is especially nice for Emacs. Alt and Super can be swapped, if you use Super more heavily (e.g. with a tiling window manager). But the important part is to keep Ctrl close to the Space key.

Whatever you do though, remember to use both sets of modifiers. For example, C-x involves the right hand for Ctrl and the left hand for x. C-p needs the left hand for Ctrl and the right hand for p. This way you distribute the burden so no one side is overworked (and, of course, you take regular breaks from typing altogether).

Lots of Emacs users will rebind Caps Lock to Ctrl. While this is fine in its own right, it tends to embed bad habits, such as with using only the left hand to register the very common C-a, C-e, C-d, C-f, C-s, C-r, C-w, C-g, C-z, C-x, C-c, C-v. Exclusive left-handed use involves awkward twisting of the muscles which will probably hurt you over time. Plus, the left pinky is forced to press and hold a key while being stretched—looks bad. Again, distribute the load.

The final part that makes everything easier is to configure a “layer” key. When you press and hold this key, other keys register a different input than normal. For example, Caps Lock can be the layer key, which then makes h, j, k, l act as arrow keys. Same idea for mapping Home, PgDn, PgUp, End someplace that makes sense (mine are on y, u, i, o). I prefer to have Space as that layer key: it only is a layer when I press and hold it, otherwise it performs the ordinary function of the Space key.

It is okay to press and hold Space because you do it with your relatively strong thumbs. Better have it this way than pressing and holding with the pinkies.

All this can be achieved with software such as kanata. A custom keyboard with QMK firmware can get the same configuration embedded directly in the keyboard (so it works without any special program running on the computer). Kanata will be the cheaper solution and is probably better overall if you consider that it can apply to a laptop’s keyboard.

A mistake in all this is to think that an expensive keyboard is inherently more ergonomic. If you keep curling, overextending, or anyhow stressing your muscles the injuries will occur regardless. A new keyboard can help if you use it as an opportunity to retrain your muscle memory.

Custom keyboards have potential advantages in terms of comfort because you can change the switches and keycaps that they use. For example, you can pick a lighter or heavier switch to match your typing technique. Then you can combine it with a shallower or steeper keycap profile to get the most out of that setup. There is no right or wrong here. It is a matter of optimising on top of the strong fundamentals that I outlined above.

As for evil-mode in Emacs, I think it is a good solution overall. You do, however, need to install the evil-collection package and probably also configure lots of other key bindings to get exactly what you want. There are other packages that give you modal editing, though I have only ever used evil-mode in earnest: it is fine.

That granted, I find that I do not like modal editing in general. It is especially inconvenient for me when I write at length (which I do a lot) because I tend to produce a wall of text in one go. Having a modal interface gives me no advantage in this common scenario. I also doubt it ever gave me the edge while programming. The bottleneck is how quickly and clearly I can think, not how fast I can edit lines of text (though, yes, Vim’s paradigm is powerful).

Consider then the overall comfort of your setup. Both in terms of the ergonomics of hardware but also how much effort it takes to maintain your Emacs configuration. The standard approach to key bindings gives you something that “just works” with practically every package you install. It may feel awkward in the beginning if you are coming from the Vim keys, but will be the most robust solution long-term from the perspective of maintainability.

-1:-- Re: Emacs and keyboard ergonomics (Post Protesilaos)--L0--C0--2026-05-04T00:00:00.000Z

Randy Ridenour: Managing Multiple-Choice Questions With Org Mode

I love teaching. I do not love grading, though. I’ve always thought it would be an ideal job if there were no requirement for grades, and if there were no grades, then there would be no need for exams. This, of course, isn’t true. In an ideal world, each student would be motivated to acquire knowledge, wisdom, and understanding, not a mere grade. Good students would still want some kind of assessment, so that they could know if they had reached their desired level of understanding. It seems that I can’t escape the necessity of testing.

I prefer to give essay exams in my upper-level courses, but the larger sizes of introductory courses make that impractical. Multiple-choice questions are easy to grade, but good ones are difficult to write. It would be nice to have a question bank from which I could easily choose questions to include on an exam, all stored in an easy-to-write format that could be used to produce both online and print versions of a test. This sounds like a perfect task for Emacs and Org mode.

Two years ago, I posted my method of writing quizzes in Org mode for import into Canvas, the LMS our university uses. I decided to use this format for multiple choice:

1. Multiple choice question (single correct answer)
   a) Wrong
   b) Wrong
   c) Right*
   d) Wrong

The multiple choice question bank will then consist of a collection of enumerated Org mode lists, something like this:

1. Why is global skepticism especially difficult to rationally respond to?
     a) It requires accepting the existence of the external world before any argument can begin.
     b) It is logically self-contradictory and therefore cannot be engaged directly.
     c) There are no premises the skeptic would grant that could be used against the position.*
     d) It collapses into local skepticism when pressed by careful argument.
2. What is Mackie's central claim in "Evil and Omnipotence"?
     a) The existence of evil makes theism unlikely but not impossible
     b) Religious beliefs about God and evil are positively irrational, not merely unsupported*
     c) Omnipotence is logically incompatible with omniscience
     d) The free will defense successfully resolves the logical problem of evil
3. Why is proving the nonexistence of the evil demon insufficient to establish knowledge of the external world?
     a) The evil demon could simply be replaced by an equally deceptive force.
     b) Disproving active deception still leaves open the possibilities of dreaming and naturally broken senses.*
     c) The evil demon argument applies only to sensory knowledge, not to rational knowledge.
     d) Proving a negative is logically impossible within Descartes' method of doubt.            

I decided to have a frame split into two windows, the question bank in one and the scratch buffer in the other. I’d like to select questions from the bank and have them copied to the scratch buffer. Here’s the function that I wrote with the help of Claude:

(defun rlr/copy-mcq-to-scratch ()
  "Copy the multiple choice question at point to the *scratch* buffer."
  (interactive)
  (save-excursion
    (let* ((question-start
            (progn
              (end-of-line)
              (if (re-search-backward "^[0-9]+\\." nil t)
                  (point)
                (error "No question found at point"))))
           (question-end
            (progn
              (goto-char question-start)
              (forward-line 1)
              (if (re-search-forward "^[0-9]+\\." nil t)
                  (match-beginning 0)
                (point-max))))
           (text (buffer-substring-no-properties question-start question-end)))
      (with-current-buffer (get-buffer-create "*scratch*")
        (goto-char (point-max))
        (insert text)))))

If the point is anywhere in a question or one of its answer options, calling rlr/copy-mcq-to-scratch appends the question to the scratch buffer. I could use a keybinding for this, but I’m not sure I write exams often enough to waste a good keybinding. It’s easy enough to create a quick keyboard macro to select the first question and then press F4 for other questions.

If I want to create a Canvas Quiz, then it’s ready to be converted into a QTI file using the method I wrote about earlier. For print exams, I use the LaTeX examdesign class1, which requires this format for multiple choice:

\begin{question}
  Multiple choice question
  \choice {Wrong}
  \choice {Wrong}
  \choice[!] {Right}
  \choice {Wrong}
\end{question}

To convert the questions, I select the entire scratch buffer and call this function. It formats the questions properly and inserts a blank line between each of them.

(defun rlr/org-mc-to-latex-questions (beg end)
  "Convert org-mode multiple choice questions in region to LaTeX format. Questions are numbered lines followed by lettered choices (a-z). Correct answers are marked with * after the choice text."
  (interactive "r")
  (let* ((text (buffer-substring-no-properties beg end))
         (lines (split-string text "\n"))
         (result '())
         (in-question nil))
    (dolist (line lines)
      (cond
       ;; Numbered question line: "2. Question text"
       ((string-match "^[[:space:]]*[0-9]+\\.[[:space:]]+\\(.+\\)$" line)
        (when in-question
          (push "  \\end{question}" result)
          (push "" result))
        (push "  \\begin{question}" result)
        (push (format "    %s" (match-string 1 line)) result)
        (setq in-question t))
       ;; Choice line: "  a) Choice text" or "  a) Choice text*"
       ((string-match "^[[:space:]]*[a-z])[[:space:]]+\\(.+?\\)\\(*\\)?[[:space:]]*$" line)
        (let* ((text (match-string 1 line))
               (correct (match-string 2 line))
               (tag (if correct "\\choice[!]" "\\choice")))
          (push (format "    %s {%s}" tag text) result)))))
    (when in-question
      (push "  \\end{question}" result))
    (let ((output (mapconcat #'identity (nreverse result) "\n")))
      (goto-char beg)
      (delete-region beg end)
      (insert output))))

The next tasks are functions for easily removing and reordering questions. For that, I should probably wait until finals are over, though.

Now, unfortunately, it’s back to grading.

EDIT (May 4, 2026):

I must not be able to think when it’s later in the day. Reordering the questions is easy. Since they’re just Org lists, M-up and M-down moves an item and its child lists up and down. Deleting questions is not quite as easy, but it still wasn’t difficult:

(defun rlr/delete-mcq-at-point ()
  "Delete the multiple choice question at point, including all its choices."
  (interactive)
  (save-excursion
    (beginning-of-line)
    ;; If on a choice line, move up to the question line first
    (unless (looking-at "[[:space:]]*[0-9]+\\.")
          (re-search-backward "^[[:space:]]*[0-9]+\\." nil t))
    (let ((start (line-beginning-position))
              (end (progn
                       (forward-line 1)
                       (if (re-search-forward "^[[:space:]]*[0-9]+\\." nil t)
                           (match-beginning 0)
                         (point-max)))))
          (kill-region start end)))
  (org-list-repair))

I used kill-region to save the question to the kill ring, followed by org-list-repair to renumber the list. Killing instead of deleting is useful in cases where one might want to move the question farther than would be convenient with a simple reordering.

Tagged: Emacs Org Teaching

Footnotes

1

There are several LaTeX packages for producing exams. I’ve found that examdesign has the best combination of features for my needs. It can be used to easily print different versions of an exam to discourage cheating with an answer key for each version. It can handle five different types of questions, and has a block environment for questions that need to be placed in a group with instructions for the group. The questions in the block stay together even if the rest of the exam is randomly shuffled. If you need printed exams, I highly recommend it.

-1:-- Managing Multiple-Choice Questions With Org Mode (Post Randy Ridenour)--L0--C0--2026-05-03T19:31:00.000Z

Irreal: Prot’s New Org-to-buffer Package

Protesilaos (Prot) has a new, interesting package available: buffer-to-pdf. It does just what its name suggests. It takes the current Emacs buffer and exports it to PDF preserving things like theme, and font characteristics.

It’s easy to use. You simply call buffer-to-pdf and you get a PDF of your current buffer. You can specify the orientation—portrait, landscape, or current window—for the output but that’s the only choice you have to make.

The most complicated thing about the package is its pagination. If you have an Org file, each headline is a new page. The idea is to turn it the org file into a set of slides. Again, this all happens automatically.

For a simple text file, buffer-to-pdf paginates at screen borders. That seems like a natural thing to do and allows a certain amount of control over where the page breaks occur by adjusting the window size.

Finally, you can specify explicit page breaks by inserting a form feed (^L). Prot’s video doesn’t make clear how the form feeds interact with the window boundary heuristic but, as Prot says, the best way of thinking about buffer-to-pdf is as a screen capture that produces a PDF. It’s a perfect way to share an Emacs buffer with someone else.

If you’re interested in this package, take a look at Prot’s video at the above link. You can check out the package’s Git repository here.

-1:-- Prot’s New Org-to-buffer Package (Post Irreal)--L0--C0--2026-05-03T14:42:33.000Z

Jonathan Chu: Introducing grove.el

I’ve been looking for (or rather, chasing!) the right note-taking workflow in Emacs for over a decade now. Back in 2013, I wrote about setting up Deft mode with Org-Mode and it was my attempt at bringing that Notational Velocity-style simplicity into Emacs, and I used it for a long time. I genuinely liked Deft, but it’s no longer actively maintained, and the same goes for Zetteldeft which was built on top of it as an alternative successor. Since then, I’ve tried the other major players – org-roam, Denote, and a handful of others. They’re all extremely impressive and comprehensive packages that do more than I could ever ask for, but none of them ever quite stuck for me. I’d set one up and more or less force myself to use it for a few weeks, and then quietly drift back to a loose pile of org files in a directory. And on my darker days, Apple Notes.

The problem was never these packages – it was more me and my typical, simple workflow that I’ve grown so used to over the years. Learning a new tool carried implicit friction points that compounded over time. Org-roam is powerful, but it is opinionated about note creation - every note needs a unique org-id property and lots of frontmatter that I often had to lookup from previous notes to even remember what to include. The whole system, when used correctly, is backed by a SQLite database via emacsql that indexes your nodes and links. That’s a lot of overhead in my candid opinion. If the database gets out of sync or emacsql has compilation issues, you’re debugging infrastructure instead of taking notes. This was another pain point of mine since I often work on multiple machines (work, personal).

On the other hand, simple tooling like ripgrep is plenty fast for searching files, and removing the database removes an entire category of things that can break – especially for newer users who are already navigating the Emacs and org-mode learning curve. Denote is a great package that uses a lot of good, well-thought-out ideas and tooling, but I found it had a higher barrier to entry and is a bit opinionated in favor of note-taking workflows that I just never adopted or used over the years. This isn’t meant to be a knock on org-roam or Denote - it’s all my preference – but I wanted something that fit how I actually work without getting in my way - and something that I knew I would use long term. The note-taking workflow I wanted is something that felt closer to just opening a folder of org files, but with the modern conveniences that tools like Obsidian offers.

So I wrote grove.el.

What it is Link to heading

Grove is an Obsidian-like note-taking mode for Emacs. One keybinding opens a full workspace with a file tree sidebar and your org notes. No external Emacs dependencies, no databases – just org files, a directory, and ripgrep.

The feature set covers the things I actually want and use daily:

  • File tree sidebar – expand/collapse directories, indent guides, current file tracking, item counts, and optional nerd font icons
  • Quick capture – the first line becomes the title, saved straight to an inbox
  • Wikilinks[[note title]] syntax, click to follow, creates the note if it doesn’t exist
  • Backlinks – ripgrep-powered, computed on demand, displayed in a side panel
  • Daily notes – jump to today, yesterday, or tomorrow
  • Search – full-text search via ripgrep, with optional Consult integration
  • Tag search – works with both #hashtag and org :tag: syntax
  • Inbox review – triage your untagged notes
  • Graph view – Graphviz-powered visualization of how your notes connect

Why I wrote this Link to heading

The honest answer is that I wanted a note-taking system that I’d actually use. Every previous attempt had some piece that didn’t fit my workflow, and over time that friction compounded until I stopped using it entirely.

What I kept coming back to was how simple tools like Obsidian feels. Yes, I know - it’s basically org-mode but with a pretty GUI! But you open a vault, you see your files, you write notes with wikilinks, and the tool stays out of your way. The graph view is a nice addition - nothing new, but something that I knew I wanted to try and recreate in a less complex fashion. The sidebar is useful visually - often times it is very beneficial to focus on a single, solo buffer, but when it comes to note taking, visually seeing the other notes can add extra context without overloading you - enabling you to add categorization and linking with minimal overhead. My intention was to create something that doesn’t prescribe a methodology - you can do Zettelkasten, GTD, or just free-form notes, and Grove doesn’t care. I wanted that same feeling and purpose that I get when I’m in Emacs, where I already spend a good portion of my day.

Grove is intentionally less opinionated than many of the existing note-taking packages in the Emacs ecosystem. There’s no enforced file naming scheme, no required metadata format, no database to maintain. Your notes are org files in a directory. If you stop using Grove tomorrow, your notes are still just org files. I think that matters most in the end - when you stop using a tool, the underlying bits don’t change, including yourself.

At the same time, I wanted it to be batteries included. One of the barriers I’ve seen with Emacs note-taking setups is that getting everything working – the sidebar (especially this!), the linking, the search, the capture workflow - it all requires stitching together multiple packages and a fair amount of configuration. Grove bundles all of that into a single package with sensible defaults. The goal is that a new user can install it, point it at a directory, and have a complete note-taking workspace immediately. Whether you’re new to Emacs or have been using it for many, many years, the setup cost is minimal and investment low.

Installation Link to heading

Grove requires Emacs 29.1+ and ripgrep. Graphviz is optional for the graph view, and Consult is optional for enhanced search.

Grove is currently in review for MELPA. Once it is approved and merged, you’ll be able to install it with:

M-x package-install RET grove RET

Or with use-package:

(use-package grove
  :ensure t
  :custom
  (grove-directory "~/notes"))

In the meantime, you can install it manually by cloning the repo:

git clone https://github.com/jonathanchu/grove.git
(use-package grove
  :load-path "path/to/grove"
  :custom
  (grove-directory "~/notes"))

Usage Link to heading

The entry point is C-c v v to open the Grove workspace, or M-x grove-open. From there, the full keybinding set lives under the C-c v prefix:

grove
├── C-c v v  Open workspace
├── C-c v q  Close workspace
├── C-c v c  Quick capture
├── C-c v f  Find note
├── C-c v s  Search notes
├── C-c v d  Daily note (today)
├── C-c v y  Daily note (yesterday)
├── C-c v t  Daily note (tomorrow)
├── C-c v b  Show backlinks
├── C-c v #  Search by tag
├── C-c v i  Inbox review
├── C-c v l  Insert wikilink
└── C-c v g  Graph view

The file tree sidebar tracks your current file, and you can expand/collapse directories with TAB. Quick capture (C-c v c) opens a buffer where you type your note - the first line becomes the filename and it’s saved directly to your inbox for later triage.

What’s next Link to heading

I have a few things I’d like to explore next – note templates, tag autocomplete, and some refinements to the graph view to make it interactive. These are definitely nice-to-haves, but I would only want more time to think about some of these more thoughtfully as complexity increases with features like interactive graph views. But the core is solid and I’ve been using it daily as my primary note-taking system, which has been the real test.

If you’ve been looking for a note-taking setup in Emacs that doesn’t require much configuration or revamping your notes workflows, give Grove a try! Feedback and contributions are always welcome.

-1:-- Introducing grove.el (Post Jonathan Chu)--L0--C0--2026-05-03T04:00:00.000Z

Sacha Chua: May 14: Sacha, Prot, and Philip Kaludercic Talk Emacs: Newcomer Experience

Philip Kaludercic wanted to continue the conversation from YE24: Sacha and Prot Talk Emacs - Newbies/Starter Kits. He's spent a lot of time thinking about this as one of the main contributors to newcomers-presets, so there'll probably be much to cover!

(America/Toronto -0400) = Thu May 14 1030H EDT / 0930H CDT / 0830H MDT / 0730H PDT / 1430H UTC / 1630H CEST / 1730H EEST / 2000H IST / 2230H +08 / 2330H JST

We'll probably talk about:

  • Emacs 31 or Emacs 32 directions towards improving the newcomer experience
  • How the newcomers presets fits into the bigger picture
  • Documentation and guides
  • How to get more feedback from newbies (virtual focus group? mailing list? office hours?)
  • Informal community resources
  • Other things we can do to help

Related links:

View Org source for this post

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

-1:-- May 14: Sacha, Prot, and Philip Kaludercic Talk Emacs: Newcomer Experience (Post Sacha Chua)--L0--C0--2026-05-02T17:21:07.000Z

Irreal: The Start Of The Emacs 31 Release Process

Sean Whitton writes to say that the Emacs development team is planning to start the Emac 31 release process next week. That means that they will cut the Emacs 31 branch and start the process of testing and refining it in preparation for the final Emacs 31 release.

I really like how the development time is handling releases lately. They plan on a major release about once a year and in between time issue updates to fix bugs or problems in the latest major release.

If you follow the Emacs Devel mailing list, you’d think that the developers’ full time job is Emacs and that they have no family. It’s incredible how much unpaid time these people devote to Emacs
for the benefit of us all. As I always say, if you find yourself in a bar with any of them, the drinks are on you. These guys really are heroes.

-1:-- The Start Of The Emacs 31 Release Process (Post Irreal)--L0--C0--2026-05-02T14:53:37.000Z

Magnus: Follow-up on switching to eglot

Jan G sent me a two-part comment.

Part one

I was under the impression that when using elpaca you needed to disable use-package, and that when using elpaca-use-package, you were redefining the macro. I’m not 100% sure about this, but the documentation has an example of use-package and how it actually expands to an elpaca command.

I wouldn't know. All I can say is that it would be nice if package managers that hook into, or completely redefines use-package, would document if they deviate from the behaviour of "vanilla use-package" in some way.

Part two

Given that, use-package’s documentation is always going to be a little off, since elpaca is doing everything async. The only way I’ve found to reliably manage some dependencies is to use the elpaca-after-init hook, so they don’t even try to run until elpaca is finished loading everything.

I'd say it sometimes seems like the documentation for use-package is a little off for use-package itself 🙂

The README for Elpaca says that

Add configuration which relies on after-init-hook, emacs-startup-hook, etc to elpaca-after-init-hook so it runs after Elpaca has activated all queued packages.

but that seems like a very big hammer and as I understand it I'd have to move the whole :init block for python-mode into the hook in that case. Playing around with the various blocks for use-package isn't too time consuming and I think it's a good first thing to try.

-1:-- Follow-up on switching to eglot (Post Magnus)--L0--C0--2026-05-02T11:20:00.000Z

Magnus: Secrets when connecting to DBs

I should have dealt with comments I got to my posts on how I deal with secrets in my work notes, here, and here. Better late than never though, I hope.

Comment from Stefano R

The first one is a link to post titled How I use :dbconnection in org files. It describes a nice way of setting sql-connection-alist based on the contents of a file, in his case ~/.pgppass.

Comment from Harald J

The other starts with a function for searching ~/.authinfo.gpg for entries of the form

machine <host>/<dbname> login <username> password <password> port <port>

and then setting sql-password-search-wallet-function and sql-password-wallet to tell sql-mode to use it

(defun my/sql-auth-source-search-wallet (wallet product user server database port)
  "Read auth source WALLET to locate the USER secret.
Sets `auth-sources' to WALLET and uses `auth-source-search' to locate the entry.
The DATABASE and SERVER are concatenated with a slash between them as the
host key."
  (when-let (results (auth-source-search :host (concat server "/" database)
                                         :user user
                                         :port (number-to-string port)))
    (when (and (= (length results) 1)
               (plist-member (car results) :secret))
      (plist-get (car results) :secret))))

(setq sql-password-search-wallet-function #'my/sql-auth-source-search-wallet)
(setq sql-password-wallet "~/.authinfo.gpg")

The value for sql-connection-alist is then as normal

(setq sql-connection-alist
  '((some-dbname (sql-product 'oracle)
                 (sql-port 1521)
                 (sql-server ...)
                 ...))

and the blocks in orgmode looks like this

SRC sql-mode :product oracle :dbconnection i3v1e-ro :results raw
SELECT to_char(sysdate, 'YYYY-MM-DD HH24:ii:ss') AS today,
       to_char(sysdate + 1, 'YYYY-MM-DD HH24:ii:ss') AS tomorrow
FROM dual;
SRC

Thoughts

Both of these feel closer to the intent of sql-mode in a way. I'll have to try using sql-connection-alist at some point.

-1:-- Secrets when connecting to DBs (Post Magnus)--L0--C0--2026-05-02T10:41:00.000Z

Protesilaos: Emacs: save any buffer as PDF (my new buffer-to-pdf package)

Raw link: https://www.youtube.com/watch?v=JG4R-d0N-is

In this short video I demonstrate my new package for Emacs. It is called buffer-to-pdf. The idea is to save your current buffer to a PDF, while preserving how it looks. This means that your font size, theme, and other visual effects are preserved and written to the PDF. buffer-to-pdf is not meant to be a replacement for elaborate export methods: consider it a quick yet effective way to get a “screen capture” of your Emacs that you can then share as a document. I believe this will be especially useful for academics or people who need to distribute presentation notes on a regular basis. The package is available here: https://github.com/protesilaos/buffer-to-pdf.

-1:-- Emacs: save any buffer as PDF (my new buffer-to-pdf package) (Post Protesilaos)--L0--C0--2026-05-02T00:00:00.000Z

Irreal: A Couple Of Emacs Quickies

Here are a couple of quickies. Neither one requires much exposition on my part but they are both interesting. In the first, Prot shares what he considers decent default settings for Emacs. Naturally, I don’t agree with all of them but they’re an excellent start for your init.el. For example, I’d be more likely to prefer Melpa over Gnu Elpa, and I prefer Swiper to Consult but that may be simply because I’m used to Swiper and it works well for me.

Most of the rest of his choices I agree with or at least am agnostic about. That’s the thing about Emacs, you get to choose what works best for you.

The second quickie is a post from Charles Choi that discusses bulk search and replace commands in Emacs. Choi begins with reviewing regular expression syntax. That’s important because some Emacs command use the builtin regexp syntax and others call various versions of grep, which, of course, have their own regexp syntax.

As usual with Choi’s posts there’s a lot there and it requires careful reading but it’s worthwhile. As Choi says, the Emacs bulk search and replace commands make possible workflows that would be harrowing in other editors but they’re also difficult to discover. His post is intended to remedy that.

-1:-- A Couple Of Emacs Quickies (Post Irreal)--L0--C0--2026-05-01T14:48:49.000Z

Jiewawa: Overriding keybindings with Meow

I previously wrote about useful Emacs commands for reading. This allowed me to use j and k to scroll the window up and down instead of going forward or backward a line.

I am now using meow for my modal editing needs and have been really happy with it. Meow allows for custom states to be defined, but I have been using a different method recently that takes advantage of the built-in normal and motion states.

Meow has a very clever design choice in that when a key is pressed it can translate it to the default emacs binding and call it via a macro. Below is an example of the function meow-next, which I have bound to j. It essentially just executes the key-binding that is stored as the value of meow--kbd-forward-line which happens to be C-n.

(defun meow-next (arg)
  "Move to the next line.

Will cancel all other selection, except char selection.

Use with universal argument to move to the last line of buffer.
Use with numeric argument to move multiple lines at once."
  (interactive "P")
  (unless (equal (meow--selection-type) '(expand . char))
    (meow--cancel-selection))
  (cond
   ((meow--with-universal-argument-p arg)
    (goto-char (point-max)))
   (t
    (setq this-command #'next-line)
    (meow--execute-kbd-macro meow--kbd-forward-line))))

(defvar meow--kbd-forward-line "C-n"
"KBD macro for command `forward-line'.")

Because meow’s keyboard macro commands are all stored as variables, it is easy for us to overwrite them locally. In the following example I will show how I made a minor mode that lets me have a more enjoyable reading experience.

Firstly, we need to bind the new functions to some obscure key combination. I chose to add these to the global keymap in this case as I want them available across a variety of major-modes.

Then I wrote functions to toggle between the original values of the meow–kbd variables and their overrides.

Finally, I defined a minor mode that can be added to a major-mode’s hook.

(keymap-global-set "M-s-j" 'scroll-up-line)
(keymap-global-set "M-s-k" 'scroll-down-line)

(defvar meow-reading-originals
  '((meow--kbd-backward-line . "C-p")
    (meow--kbd-forward-line . "C-n")))

(defvar meow-reading-overwrites
  '((meow--kbd-backward-line . "M-s-k")
    (meow--kbd-forward-line . "M-s-j")))

(defun meow-reading-restore-directions ()
  "Restore Meow's direction variables locally."
  (dolist (binding meow-reading-originals)
    (set (make-local-variable (car binding)) (cdr binding))))

(defun meow-reading-overwrite-directions ()
  "Overwrite Meow's direction variables locally."
  (dolist (binding meow-reading-overwrites)
    (set (make-local-variable (car binding)) (cdr binding))))

(define-minor-mode meow-reading-mode
  "Adjust some bindings to make `meow-normal-mode' a better experience for reading modes."
  :lighter " reading"
  (if meow-reading-mode
      (progn
        (meow-reading-overwrite-directions)
        (setq-local meow-cursor-type-motion nil)
        (meow--update-cursor-motion))
    (progn (meow-reading-restore-directions)
           (kill-local-variable meow-cursor-type-motion)
           (meow--update-cursor-motion))))

Here is an example of how you can have the minor mode be loaded for all buffers of a given major mode.

(use-package elfeed
  :hook
  (elfeed-show-mode . meow-reading-mode))

The reason that I prefer this to making a whole new “reading-state” is that this is still using meow’s default motion-state. I get to take advantage of all of the default key-bindings. One of the reasons that I moved from evil to meow was that I was getting tired of having to bind so many functions myself. With this method, I can essentially say “I want this mode to behave the same as all of the others, just with one or two changes”.

I am also working on a minor mode for lisp modes that takes advantage of smartparens to hopefully create a lispy like environment, but that is still a work in progress.

-1:-- Overriding keybindings with Meow (Post Jiewawa)--L0--C0--2026-05-01T00:00:00.000Z

Sacha Chua: YE24: Sacha and Prot Talk Emacs - Newbies/Starter Kits

: Added chapters, transcript, and Prot's defaults.

Context

The Emacs Carnival theme for April 2026 is newbies/starter kits. I chatted with Prot about helping people get into Emacs and also supporting lifelong learning.

Prot had some notes on how he started with Emacs in 2019 in All about switching to Emacs (video blog) | Protesilaos. These notes were just a few months after he started, so his experience was pretty fresh.

In Computing in freedom with GNU Emacs | Protesilaos (2026), he said:

Remember that I started using Emacs without a background in programming. … I learnt the basics within a few days. I started writing my own Emacs Lisp within weeks. And within a year I had my modus-themes moved into core Emacs.

Prot has several projects that might be of interest to many newcomers to Emacs:

  • modus-themes, which are part of Emacs core and are therefore just a M-x load-theme or M-x customize-themes away
  • Emacs Lisp Elements, a book that helps people learn Emacs Lisp
    • Where does this fit into people's learning journeys? How can they come across it and use it?
  • perhaps Denote
    • What would it take for people to learn enough to be able to use this?

I'm also curious about his thoughts on the general Emacs newcomer experience and what we can do to make it better.

He also offers Emacs coaching. I wonder if any newbies have taken advantage of that. There are a few other coaches listed on the EmacsWiki. (Ooh, Emacs buddy, that was neat.)

Other possible topics: Philip suggested the following general themes for the Emacs Carnival:

  • What are your memories of starting with Emacs?
  • What experiences do you have with teaching Emacs to new users?
  • Do you think if starter kits are more of a hindrance in the long term or necessary for many users to even try Emacs?
  • What defaults do you think should be changed for everyone (new and old users)?
  • What defaults do you think should be changed for new users (see NewcomersTheme)?
  • What is the sweet-spot between starter-kit minimalism and maximalism?

Chapters

  • 0:00 Intro
  • 0:14 Warming up
  • 2:38 C-g is supposed to get you out of everything, but it doesn't work for the minibuffer
  • 3:14 Anything related to display-buffer is hard for people to configure. Many windows do not focus by default. You have to switch to the other window to q.
  • 4:32 Good defaults
  • 4:37 How do I set my fonts? Which is the one I should be using?
  • 5:16 ediff is unusable by default for everyone, not just newcomers
  • 5:54 Packages to install
  • 6:30 People muddle through, but it's confusing
  • 8:21 The wiki might be a good approach for the community. Start here.
  • 9:35 The direction of the newcomers theme is nice
  • 10:51 Themes versus minor modes
  • 12:20 People think of themes as styles, not arbitrary customizations
  • 13:57 Listing changes for newcomers-presets
  • 16:13 Terminology is also a challenge
  • 16:54 Maybe documentation aliases?
  • 17:57 Learning Emacs as a nonprogrammer
  • 19:31 Emacs Lisp Elements
  • 20:30 Getting the hang of Emacs
  • 22:31 Getting help when you have a starter kit
  • 24:29 Customize is overwhelming for beginners
  • 27:55 debug-init
  • 29:11 Getting help: partially bridged by LLMs?
  • 31:03 Things people don't even know about
  • 32:44 Filling in the blanks
  • 33:39 .emacs
  • 37:04 Discovery and the info manual
  • 38:36 Address your immediate need; small steps
  • 41:46 :config and setq is nicer than :custom for C-x C-e purposes (eval-last-sexp)
  • 45:31 Culture of documentation and sharing
  • 47:12 Link to a search
  • 49:49 Getting through the gap between beginner tutorials and the next step
  • 51:13 Predictability
  • 51:52 Brief mention of Popper
  • 52:28 Earlier is better than later for Emacs Lisp. Take it as is.
  • 55:19 Before and after comparisons
  • 56:07 user-init-directory
  • 57:21 Emacs core
  • 59:04 Getting past the initial awkward phase
  • 59:36 Even reporting an issue is a great contribution
  • 1:00:45 Next steps: adding to the wiki
  • 1:02:39 Core longevity

Transcript

Expand this to read the transcript

0:08 Warming up

Sacha: All right. Hello, this is Yay Emacs 24, I think. And today I'm going to be talking to Prot, who is going to join eventually. In about five minutes is our scheduled time. And I want to pick his brain about newcomers, the newcomer experience for Emacs, the starter kits, what we can do to make it easier for people to get into Emacs, and how we can support lifelong learning. So let me spend a few minutes here getting all set up so that if you have any questions, you can use the YouTube chat during the live stream so that I can read your questions out loud to Prot. And also so that I can share everything. I think my audio is working. And also in the meantime, I can tell you what I've been doing lately. I have just posted a guide to newcomers presets, which is a new feature in Emacs 31. It's a theme that enables a bunch of defaults. Sorry, that changes a bunch of defaults to make it a little bit nicer for people. And let's see, what was that? I don't know what that sound just meant. Okay, Prot, it says he's in the Google Meet room. So I will now admit him. And I think we should be live. Fantastic. Hello. Hello, hello. All right.

Prot: Hello, Sacha. Good day.

Sacha: Hello, Prot. Good day. Thank you for joining early. I was just doing my pre-session panicking and warming up. But since you're here and since I have a hard stop in about one hour, a little over one hour since I have to make the kid a grilled cheese sandwich, let's dive right into it.

Prot: Yes, yes. The grilled sandwich cannot wait.

Sacha: No, no, no. She'll be hungry. So, the theme for the Emacs Carnival this month was newbies and starter kits. And it gives us a good excuse to start thinking about How do we make the Emacs experience better for new users? Now I know you probably have run into a lot of new users from the talks that you've been giving, the packages you make, everything, the coaching. So tell me about what you've been thinking about this so far.

2:36 C-g is supposed to get you out of everything, but it doesn't work for the minibuffer

Prot: Yeah, yeah, yeah. So broadly speaking, there are a few pain points that I think every new user experiences. One is the behavior of C-g. The fact that you have the mini buffer open and you do C-g because C-g is supposed to get you out of where you are and the mini buffer will stay open by default. And I have seen people struggle live. It's like, oh, I am, you know, they have the mini buffer open, they click somewhere else, then they type C-g, the mini buffer stays there, and they're like, what is happening? Why is this not working? It stopped working. That's the one thing.

3:11 Anything related to display-buffer is hard for people to configure. Many windows do not focus by default. You have to switch to the other window to q.

Prot: The other big area where a lot of people, not just beginners, struggle with is anything related to display buffers, which can be configured, of course, via the display-buffer-alist. And some of the common pain points with that are the fact that many windows do not focus by default. For example, you open a helper buffer, it doesn't focus the window by default. So if you want to type q to dismiss it, you have to switch to it, then type q. You do a care, it doesn't focus a care by default. You have to go there and then interact with it. These sorts of things. And then there are a few other things. I have written some settings that I can share with you as well. Maybe I can, I don't know, email them to you and then you can... I don't hear you now. One second.

Sacha: Sorry, I turned on mute. Do you want to share your screen? Because that's another thing you can do.

Prot: Yes, of course, of course, of course. But I meant to say that, so I have this here, and I was of course about to write a blog post and all that. Let me increase the font size. Is this font size okay or is it too small?

Sacha: Oh, this is good. Yeah, yeah, yeah.

Prot: Okay, so I have written a few things, so I don't have to go through all of them.

4:28 Good defaults

image from videoProt: But these are basically good defaults based on what I have noticed.

4:35 How do I set my fonts? Which is the one I should be using?

image from videoProt: Another thing that is really common is how do I actually set my fonts, right? Because there are like a million ways to do this as well. And the people are like, okay, but which is the one that I should be using? And of course, when I pick one option, I don't mean to say that this is the right option, but it's just to not be technical about it. Like, okay, just use this and forget about it.

image from videoProt: A few other settings and a few common packages. And at the end of this... Oh, sorry. I have to really make this point.

5:13 ediff is unusable by default for everyone, not just newcomers

image from videoProt: Ediff by default is unusable. Out of the box, Ediff is literally unusable. I cannot excuse that. Everything else I can excuse, this is not excusable. Sorry. This is the minimum viable setup for it.

Sacha: So maybe that's something to suggest for newcomer presets or maybe even the defaults.

Prot: I would say the defaults. This is not a newcomer thing. Basically, if you want to have that default layout, you just have to opt into it. Sorry if I'm offending anyone, but I don't mean to say that. You have to consider the ergonomics of it.

5:52 Packages to install

Prot: And then towards the bottom of this list, some packages, third party packages. that I recommend for installation. This is not exhaustive. I try to be minimalist here. So, of course, there are many, many good, excellent, top-notch packages that I don't recommend here. And, for example, I don't recommend any of my packages here. But I just included some for people to get started.

Sacha: So it sounds like we should have a Prot starter kit.

Prot: No, no. I already have too many packages that I maintain.

6:28 People muddle through, but it's confusing

Sacha: It also sounds like you are talking to a lot of newbies and you are hearing about a lot of pain points and frustrations. How are people finding information in the first place? How are people finding this information? Do people tell you about their experience of getting into Emacs? Where are they finding the stuff? How do they find their way to you?

Prot: Generally they muddle through. So they will find a blog post, they will find a video, they will just do some search. Now, of course, there is also LLMs providing feedback. So it's a combination of all those and they try to piece together whatever kind of knowledge those sources provide. The thing with the newcomer experience is that there isn't a curation of content. Like of course you were doing that thing with the wiki, right? So of course you are working towards that. But what I mean is there are like options like, oh, you can do it in these 10 different ways. But for a newcomer, this is just details that don't make sense. Because the newcomer cannot weigh the pros and cons of each option, or even if they have pros and cons, or they are just different ways of expressing the same intent. Such as with the fonts, for example. You can do the frame fonts, or the faces, or whatever.

Sacha: Okay, so if there was something more curated, what would that look like? I know you spend a lot of time thinking about the, you know, the information architecture of your documentation, which is the lovely thing about your pack, one of the many lovely things about your packages. But what could that kind of newcomer experience look like for documentation?

8:20 The wiki might be a good approach for the community. Start here.

Prot: What you were doing with the wiki, I think is the right approach from a community perspective, meaning like, yeah, here is the single point of entry. Take it from there. Basically, don't look elsewhere. Start with this. No matter what you do, start with this. I think that's a good approach and basically in the community we should be agreeing on that. I didn't see all of your videos yesterday. I don't have the time to watch all of it. But basically on the Emacs subreddit, which is basically where a lot of people find information. That's the first thing that should be on the sidebar or basically it could even be pinned on the on the top of the tips and tricks section, the thread there. So that's the one thing. Yes, please.

Sacha: Yes, so the Emacs subreddit does have in its sidebar a link to the Emacs Wiki. Not calling out the Emacs Newbie page specifically, but there is a page. There's a link to the Emacs Newbie page from the Emacs Wiki homepage, I think. But yeah, as long as we can come up with a reasonably coherent starting point for people, then that will inevitably show up in people's recommendations as they respond to all these threads.

Prot: Yes, yes, very well, very well.

9:33 The direction of the newcomers theme is nice

Prot: Other than that, I really like the direction of the newcomers theme. I don't know exactly now if newcomers theme works in practice. Like, I don't know what happens if you do Emacs disable-heme, or specifically what I mean.

image from videoProt: I haven't tried this but what I mean if you do this: mapc disable-theme right, the custom enabled theme maybe you have seen this right so you want to disable all the other themes before loading your theme right I'm sure somebody has written something like this maybe I have done it and then it's like you know load your favorite theme now right and then you do your favorite theme or whatever For example, here. So in this case, I don't know what happens to the newcomers theme. I will assume that it will disable it. In which case, I think that has to be prevented.

Sacha: Oh, but then it wouldn't be treated the same as other things.

Prot: Which you can do. Which you can do, for example, if I go to Fontaine. And of course, I got this from use-package. But you can do it with a synthetic theme. So there is a little trick you can do.

10:45 Themes versus minor modes

Sacha: I was looking at newcomers presets recently, and when I was trying to make instructions for people to actually use this stuff, I ended up leaning towards just telling them to use either the splash screen, of course, or M-x customize-themes, from where they can check and uncheck things if they wanted additional themes layered on top of that. It's not like you can't uncheck it and then all of your settings go back to what they were before. Some of the things are still left over.

Prot: That's why I like the direction. I'm not sure if it should be a theme though. I think it should be a minor mode. And the minor mode should be like here is the opinionated settings and here are the default settings.

Sacha: Do we already have like a mechanism for letting minor modes override the variables in a nice way but let you go back to the previous version? Because it's not just restoring the default customized ones either.

Prot: I do something like that in Logos but I'm not sure to be honest right now how I even do it. Set arg and maybe this was a wrong time ago so I cannot even recall what exactly I was doing but actually this was contributed by Daniel Mendler so of course something like this could be added to Core Emacs as part of the newcomers theme eventually. If not, somewhere in core anyway.

12:19 People think of themes as styles, not arbitrary customizations

Prot: But I think it shouldn't be a theme. Basically, I like the idea, I don't think it's the right tool. Because themes are... It's also confusing language, you know? Because theme, when you talk to the average person, they will think of the style. And they won't think about arbitrary customizations. Whereas in Emacs we have this idiosyncratic conception of theme where it's like any kind of a user option as well as faces.

Sacha: So it sounds like if it were a package that defined a minor mode that people could turn on and off Even better, yes, exactly.

Prot: And there is this user option. I forget, do I even have it here for the built-in packages? I don't remember if I added it here. No, there is something like update the built-in packages. Yeah, so there is an option like that. So, of course, it could be like built into Emacs 31 as well as ELPA, kind of like Eglot. And then users could be like, okay, update this. So going forward, they can also benefit from whatever comes from Emacs 31. Or, you know, the development target of Emacs going forward.

13:55 Listing changes for newcomers-presets

Sacha: One of the challenges that I encountered when I was starting to play around with newcomers presets or other things like that is that it turns on all these options, but there's no easy way for people to say, okay, this is what has changed. This is how to use it. So I've started documenting that. And I think this is a challenge generally for many of the starter kits. It takes already a lot of work to make the configuration and maybe answer people's questions or It's a tricky situation how best to do it.

Prot: I guess the natural place for that is the manual. And the manual, I believe right now the manual mentions something along the lines of, well, newcomers can just toggle this on kind of thing, but it doesn't really tell them what that will entail. So I think it's worth actually keeping track of all the changes and be like, well, the newcomers theme will change this and that and the other. And it could just be a bullet point of items. Maybe it doesn't have to go into all the technicalities like, hey, we are changing, I don't know, the isearch so that it shows the counter. By default, it doesn't show the counter, right? Like, it doesn't need to be as detailed. It can just say, okay, these are the user options that are affected.

Sacha: or the minor modes that are enabled. You know, the specific commands and variable settings, whatever. It's like, how do I combine these different concepts to do something? Or taking a step back further, something we've talked about in previous conversations, how do I even begin to learn this overwhelming number of concepts? You know, how do I start to memorize all these keyboard shortcuts? And I'm not sure we have a lot of support for that yet.

16:10 Terminology is also a challenge

Prot: No, because I think part of the challenge here is the terminology. For example, if we say completion like me and you and other users, we kind of know what we are talking about, right? So minibuffer and orderless and all that, right? But if the user wants to express something along the lines, they may say the search box. Or, you know, like the interaction panel or whatever. So they don't have a language of the completion framework or the mini buffer or whatever. So even then it can be tricky for them to kind of narrow down what they are searching for.

16:52 Maybe documentation aliases?

Prot: And maybe then it makes sense to also think in terms of clusters of configuration, kind of what starter kits do with the various modules they define. And you can have aliases for them. Aliases in the manual, I mean. Like in the manual, if you type i, it goes to the index, right? And you can have a concept index. So you can have a concept index for the search panel or whatever. And that means the minibuffer and friends.

Sacha: So it's like we're doing search engine optimization so that people can find things with the words that they use. I'm not sure that will be in the Emacs manual itself, but one of the things I've appreciated about people sharing their notes through blog posts and things like that is because they're using their words to describe a concept, and they're linking it to the code that uses the words that Emacs does. So then people can then say, oh, I'm looking for this. It's actually called this in the Emacs world. But this takes time for people to kind of make those connections.

17:56 Learning Emacs as a nonprogrammer

Sacha: What was it like if you can look back to like 2019 when you were learning all of this stuff for the first time? What was it like for you as a non-programmer to come into this world where people are using all these strange terms?

Prot: Yeah, it was a challenge for sure. But I think actually the fact that I started out as a beginner, as a beginner into programming, I mean, benefited me in the sense that I was a blank slate. I don't have to unlearn terms. So I didn't have a concept of, okay, in other, I don't know, programming IDEs, for example, they call this the narrowing framework or whatever. I was like, completion. Okay, let's move on. It was the first time I was introduced to such concepts. So I think in that sense, I was lucky. That granted, there is a lot of reading involved. I was reading the manual and learning from it.

Sacha: And that's something I do too. I mean, I'll still casually flip through the Emacs manual or the Org manual because every time you read it, there's something else that catches your eye and makes you think, how do I use that? How do I do that? And I like that, you know, you and Mickey Peterson and other people have also been organizing these thoughts into like a linear arrangement of logical progression. So that's the books that There aren't a lot of books about Emacs that people can read.

19:29 Emacs Lisp Elements

Sacha: But how do people get to something like your Emacs Lisp elements? How do we support their learning journey from, I have absolutely no idea how to do anything in Emacs to, okay, I'm ready to read this book and get stuff out of it?

Prot: Yeah, yeah. When I recommend that book, I recommend it to people who have already decided that Emacs is the right tool for them. So I would basically say, look, Elisp is for you if you are already sold on Emacs, because what Elisp gives you is that extra you need to make Emacs do what you want, basically to tap into the potential programmability of Emacs. But to get to that point, you have already been convinced that you already like Emacs. If you don't vibe with it at the outset, you won't learn Elisp, not least because it's a niche language.

20:28 Getting the hang of Emacs

Sacha: Okay, so how do we get people to the point where they can vibe with Emacs? Where they can appreciate it? Because when they start off, it's this clunky text editor that has these weird keyboard shortcuts and strange terms, and all we can do is offer them videos and blog posts from people who say, this is totally awesome. I've been using it for three years or 20 years or whatever, and I love it. That's the light at the end of the tunnel, but there's a lot of tunnel to get through.

Prot: correct correct correct it's difficult and i think that's why something like the newcomers theme ultimately is the way forward where it's like yeah opt into this and that's already a good set of defaults and i think what really matters is to reach a point where you can actually open your files actually move around and that happens with the very basics like that happens with the tutorial already what the tutorial doesn't give you is the basic interface, such as the mini-buffer. The default mini-buffer, I don't think it's good for beginners. Actually, maybe it's not even good for advanced users, but that's another. You have to have a few of the basic packages enabled, and then the tutorial, I think, is enough for that initial push. Then, of course, it's also up to the user to do some reading, based on what you will provide them with.

Sacha: I know when I was trying this, I started a fresh Emacs so that I could see what it's like when people don't have their accumulated cruft of 20 years of configuration. And I was like, I need some kind of completion that I don't have to keep pressing tab for. So maybe Fido vertical mode can be part of that, you know, standard, at least in ?? or whatever, that would be nice. But yeah, there are a lot of these niceties that reduce the friction enough that people can then start enjoying things more and more.

22:28 Getting help when you have a starter kit

Sacha: Newcomers presets are some kind of starter kit. They're great at getting people over that initial hump. But the challenge with starter kits and probably things like the newcomers presets has also been that when people ask for help, it's hard because they don't know the things that have changed under the hood. So they're asking for help and the people who are helping them are like, I don't know what's going on there.

Prot: More so if the starter kit has its own macros and way of doing things, such as Doom Emacs. On the one hand, Doom Emacs does an excellent job at integrating everything, providing a polished experience, comprehensive configuration and so on. On the other hand, they have their own way of doing things like they have their own macros. You have to use Doom sync or whatever to do things from the command line. So somebody who is not using Doom basically has no means of knowing what is happening in that world. So that is definitely a challenge. So for me, a good starter kit is one that at the very least uses what a generic configuration would use, meaning no macros, no weird shell scripts and that sort of thing.

Sacha: And I did spend some time going over the starter kit list in the Emacs wiki to try to sort it by minimalist, stays close to vanilla, all the way to the changes a lot of things about Emacs and you probably should ask the community of that starter kit first if you need help. So that's kind of like Doom Emacs and Spacemacs at that end of the spectrum and things like better defaults would be like at the Like just a little bit of smoothing over of things. But then also, it was interesting to see some of the starter kits focus on saying, okay, you don't have to write any code to extend this further. A lot of the things are available through Customize.

24:25 Customize is overwhelming for beginners

Sacha: Now, Customize is pretty overwhelming also for a newcomer. So how do we get people to the point where they might feel comfortable going through this Customize interface And saying, oh, I can find what I want to change and I can change it and I'm not worried about breaking everything.

Prot: Yeah, I actually, when I was trying to use Customize with people, I gave it an honest try. Like, for example, we tried to do Emacs Customize the org capture templates. And I was seeing it live. Impossible for people to understand what is happening. Like, Customize has this concept of the insert button, right? So if you have a list of things, you can do insert to add the next element to the list. If you have an Elisp understanding of what you are actually interacting with, you kind of know what to do, right? But otherwise, I was seeing it live. It's like... I have no idea what is happening. What is this? So for me, my approach is basically skip customize altogether. For me, it's a lost cause. Unless it's completely rewritten, I mean in its current form, it's not good for beginners unless it's for toggles, like true or false kind of thing. If it's for anything more involved, it's not good. And what it is good for is for discovery, discovery of user options. But it presents the user options in a human-readable format which you cannot just copy-paste into your configuration. So, for example, it doesn't have the dashes for the names.

Sacha: Yeah, and getting it out of the customized variables if you wanted to keep a nice clean Emacs is hard. Although I would say that's more of an intermediate level concern. When they start caring about having a beautiful Emacs that other people can learn from. A couple of comments in from people who are watching the stream. Hello, folks! Hello! @hajovonta6300 says, "Hi legends." @JacksonScholberg and @petertillemans2231 say, well, @JacksonScholberg says hi. @petertillemans2231 says, "I am not worthy." @takoverflow says, "Thank you for these streams." @ShaeErisson says, "I love Emacs but haven't really learned Elisp." And I know Shae has been using Emacs for a long time. So that's interesting that you have people who enjoy using Emacs. I don't know whether something is getting in their way when it comes to learning Emacs Lisp or whether it's just totally fine already the way it is. So that's different things. @JacksonScholberg says, oh, so @hajovonta6300 says, "you are worthy if you are willing to learn." Maybe the resources are there as people start digging into EmacsLisp. Maybe the combination of looking at other people's source code and trying to ask on Reddit or whatever is enough. @JacksonScholberg says," I vibe with Emacs after using other text editors that were not minimalist enough for my preferences, plus having experience with other open source software like Linux." @petertillemans2231 says, "Well, Emacs and minimalist in the same sentence. Strange concept, but I know what you mean." There's a whole spectrum of things you can do with Emacs, right? So yeah, people can just use basic Emacs.

27:53 debug-init

Sacha: And then @petertillemans2231 says, "I guess learn starters quickly to use emacs --debug-init. Maybe not in the first hour, but close to it. Close to tweaking.

Prot: Yeah. Which of course doesn't help. It's very useful, of course, but it doesn't help beginners because they cannot read the backtrace.

Sacha: Yeah, it is hard to navigate even for people who are experienced like there's a whole bunch of things and what you need to change is like a small thing and you don't know about edebug and all that other stuff.

Prot: But of course debugging it many times of course it is a lifesaver for sure.

Sacha: Yeah, and I think a lot of these things can be stepped around if you have, you know, like you, someone more experienced with Emacs to watch over your shoulder either in person or virtually and say, you know, do it this way instead, or have you heard about this package? But this is an experience that I think not a lot of people have because many times they're isolated, right? They're the only Emacs person they know around them. And maybe they'll go to meet up, but maybe they're intimidated by the idea of asking about their beginner problem with all these other people talking about arcane Emacs list things. So how do we get people to the point where they can get help?

29:06 Getting help: partially bridged by LLMs?

Prot: Yeah, I think this is partially bridged. This gap is partially bridged by LLMs. Like a lot of people will just check with a bot and get something useful out of it and basically continue from there. And that's why I said earlier they muddle through because LLMs of course will give you what you ask. So if you kind of don't know what to ask, you will get something that may be useful, maybe needs a further tweak to it. That's why sometimes it's hit or miss.

Sacha: And I am seeing that in a lot of the discussion threads now. Of course, people are concerned about the environmental impacts and the ethical considerations around large language models, but there are also people who are saying, you know, this is what helped me write my first bit of Emacs Lisp, or this is what helped me figure out how to configure Emacs to do the thing that I wanted to do. So for that, I'm like, okay, then maybe there's something there. Challenge, of course, if it's hallucinating something, you're like, no, that function does not actually exist. You got to do it this other way. But if you can get them over some of the humps, maybe that's useful for them.

Prot: Yes, yes, yes. I think, of course, it's not 100% good, but I think it is, on the balance, I think it is good.

Sacha: So when people are too embarrassed or too intimidated to ask people in person, and when I go to these meetups, everyone's always super friendly. Sometimes we're live debugging someone's configuration or someone's function in real time. But sometimes that is a little difficult for people to get to for schedule or other reasons. There are other ways to understand something and ask questions about it and figure it out.

31:01 Things people don't even know about

Sacha: But sometimes you don't even know what to ask questions about. How do we help people in that situation where they don't even know that they're doing something inefficiently and that the solution for their problems is just one package away? How do we help?

Prot: That's difficult because it's on a case-by-case basis. I think you cannot optimize for that because each person will have different intuitions or different pain points, let's say. And maybe you can do it by having the most exhaustive kind of documentation with the equivalent of search engine optimization, as you were saying earlier. But I think eventually people will still have questions and even the formulation of the question may be idiosyncratic. So even if the concept is there, the way it is presented, you might not have a perfect match.

Sacha: And the idiosyncrasy of things is something that it's definitely a challenge for us when we're working with Emacs because everyone has their own way of doing things and everyone therefore has their own... How they set it up or the keyboard shortcuts that they use or the ways that they want the functions to work. Even trying to write documentation to say, if you're learning this, you might want to check out this stuff next, I have a hard time figuring out how to make that make sense to as many people as possible without overwhelming them with 20 different questions.

32:42 Filling in the blanks

Prot: That's the difficult part. Actually, I think that's the part where you have to assume that people will fill in the blanks. For example, I think yesterday you were doing this thing where, well, somebody needs to use Git, but what is even Git? So you have to even know about Git, right? And that's recursive because, well, how do you install Git? Well, you need a terminal. What is a terminal, right? Well, you need to have this thing called Linux. What is a Linux? So basically at some point you have to just say like I will give you as much as I can but I will limit it to the scope of this like Emacs basically. Because otherwise it has infinite scope.

Sacha: And I find that hyperlinks help a lot with that then because we can say, if you need a more detailed description, you can go over there. So now I'm trying to make it easier for myself whenever I say, oh yeah, put this in your .emacs.

33:37 .emacs

Sacha: I'm just like, oh, I'm just going to link to the Emacs wiki page on init files. Because there's this whole discussion that you have to have about what is your .emacs and sometimes it's actually your .emacs.d/init.el but sometimes it's actually your .config/emacs/init.el and, like, pass that off to a page to explain all that stuff.

Prot: Actually I want to say something about this because now it reminded me. So many people nowadays will use .emacs.d/init.el or .config/emacs/init.el But Emacs defaults to reading the .emacs file from your home directory. And I had this case where a user was writing their init file in one of those specified locations, but they did something with Emacs Customize beforehand and Emacs Customize wrote to the .emacs file. So they were loading Emacs and nothing was showing up and they were like, what is wrong? My init file is there. Why is it not working? I'm loading, you know, this dark thing. Why is it white? or whatever. And eventually it was because of the .emacs file. I'm not sure how best to resolve that given that you want to also be backward compatible.

Sacha: No, no, no. Okay. So when I tell people just, you know, here's the link to the init file page in the Emacs wiki, it also includes a describe-variable user-init-file, which will tell you which one is actually loading. And I have a to-do to suggest on emacs-devel, if they haven't already discussed it endlessly, that maybe there should be kind of like a M-x find-user-init-file that just opens that specific file. Would be nice. But yeah. Going back to the chat because people have been sharing great comments as well. Shae says, "I learned about new Emacs packages by pairing with other users and asking, how did you do that thing?" Which I think is a great thing for screencasts. People sharing videos as well because when people share a video, sometimes they see things that they wouldn't have mentioned because they totally take advantage of it. It's just something they take for granted. For example, in your live stream package maintenance sessions, I'm sure you've had this a couple of times. People are asking, what is that that you just did? Videos are great for this.

Prot: Let me open the door for my puppy. I'll be back.

Sacha: In the meantime, let's see if there's anything here I can address by myself. The puppies cannot wait.

Prot: No, the puppies cannot wait.

Sacha: Small mammals in general are like, they need us, they need us. @hajovonta6300 says, "I used Emacs since 2010 and had become a power user, but in the last year, I feel LLMs took over most of the tasks I usually solved with Emacs." I mean actually it's a bit of a tangent here but we're seeing that also with some of the long-term users of Emacs moving on to other editors because whatever they had customized on top of Emacs could be replicated by a custom application written by an LLM. The movement is going both ways. People leaving Emacs for other things, people coming into Emacs because LLMs can help them with stuff. So I just wanted to mention that because things are happening.

37:04 Discovery and the info manual

Sacha: @petertillemans2231 says, "Emacs documentation is very extensive, but I feel discovery of the docs is a problem for new users." And I want to dig into that a bit more. How do we help with this discovery thing?

Prot: In the info manuals, if you know two key bindings, it really helps a lot. One is g, the other is i. But you have to have completion already set up, like vertico-mode, for example.

Sacha: I also like using s for search.

Prot: Or s for search. Those help a lot, because then you can jump to a node or an index. Without those navigating, the manuals can feel cumbersome. That granted, we are back to the point where the user also has to do some research on their own. You cannot compensate for drive, motivation. No matter how much we write, no matter how many themes or minor modes we define, the user also has to be searching.

Sacha: Yeah. And it's going back to the challenge of being overwhelmed. You know, sometimes it's difficult for new users to say, okay, there's so much to learn. How do I scope this so that I don't go crazy? You know, what is the most important thing that I need to learn about first? And then what is the tiniest step after that that I can take? And so forth. Otherwise, it's just like, I want to learn about everything.

38:34 Address your immediate need; small steps

Prot: Based on the discussions I have had, I think the consensus is address your immediate needs. For example, you want to write a to-do list, all you need to know at this early stage is Org Mode. And not all of Org, because Org has approximately one zillion commands. Just to-do and done. And maybe schedule a date. Just learn that, and by learning that, do that for a week, do it for a month, however long it takes for you to embed it as part of your knowledge . And then once you have done that, move on to the next thing. Like, okay, now that I am solid on my to-do's, how do I do the agenda, for example, and incrementally add to that. And the idea is by piecing together your system this way, you achieve two things. First, you build on a solid foundation of knowledge where you know what you are doing. And two, you understand how your system is pieced together. So if something breaks, you already have an intuition of what it could be. Even if you don't know Emacs Lisp, you can guess like, oh, I added this thing the other day and now my Emacs is broken. So probably the breakage is there.

Sacha: And this decomposing it into those tiny steps so that you can piece them together and build slowly understanding each step along the way is something that new people struggle with because they don't have experience to know what the small step is. And I think that's where coaching and mentoring and you know sometimes If you're lucky enough to be able to sit with somebody who says, okay, your next step is just to do this. That would be super lucky. But most people will just have to content themselves with sometimes there's a playlist of videos that they can follow in sequence. Or maybe there's someone, you know, maybe they'll post on Reddit saying, okay, I know this. What should I learn next? I just wish it were easier for us to say... Let's imagine this from the helper point of view. How do we make it easier for people to say, all right, this is where you are. Here's some things that you can look into next. What do you do when you're coaching someone?

Prot: Yes, I always ask them what their needs are. There are some needs which are common. For example, completion. Vertico, for example, I think basically everybody can benefit unless you have a really special use case. But other than that, it's like, well, we don't need to fix everything. Let's understand what your needs are. Let's work towards that goal. And one way to break it down also conceptually is with use-package blocks. I think use-package is an excellent, of course, it's an excellent tool in its own right, but it's an excellent way of saying, you know what? This is one thing. This is one step. And this is the next step. And so people can start thinking in terms of each use-package is a step.

41:45 :config and setq is nicer than :custom for C-x C-e purposes (eval-last-sexp)

Sacha: I sometimes feel like I'm going back and forth. use-package is nice because it allows us to add the hooks and say this stuff happens after the package is loaded, so I don't have to keep having lots of with-eval-after-load. But on the other hand, it becomes harder for people to copy and paste things because then they have to know it needs to go inside the use-package. Do I use the custom keyword or do I just use setq because it looks more copyable?

Prot: This is why me, I don't use the custom. It's not that I have anything personal against it. It's that I found that it's unusable. If you have the equivalent of this in a custom, you cannot do C-x C-e. If you say use-package is syntactic sugar... I have read this before. To somebody who doesn't speak programming lingo, syntactic sugar doesn't mean anything. To me, it barely means anything after knowing all this stuff. So what does syntactic sugar actually mean? So what do I have to do to evaluate this, right? So I am like, okay, the more minimal you can do is just have a config and then you can do add-hook there, bind-key there or whatever. Granted, I don't do this here. I don't follow this. But I mean, if you want to have like a combination of what you were saying of the back and forth while still retaining use-package, you salvage that by doing the equivalent of this. Just this. And then everything goes under config.

Sacha: And that's what I end up doing too. Just making it easier for me to change things and re-evaluate them with C-x C-e is definitely one of the major considerations. Okay, I've temporarily misplaced my... Some people are very lucky. They actually have an Emacs channel at work that they can ask for help or they can come across recommendations for. That's nice for learning, @Rossbaker9079 says. It's not a full replacement for these other ideas, but it brings together people solving the same problems with Emacs. Some people are lucky enough to work in a large company where other people are using Emacs. You should definitely take advantage of that. I hear there's actually a Discord server as well, and of course there's IRC, where people can also hang out and hear other people talk about Emacs, ask questions, learn from other people's questions. I don't think you hang out in IRC or any of these places.

Prot: No, no. I haven't done it in a very long time. I have an account there on IRC. I think the last time I did, it was in the last EmacsConf I could attend, which is like maybe two or three years ago. I forgot already.

Sacha: It's yet another thing that kind of distracts your attention. I also find Mastodon to be very helpful for this stream of little updates from people sharing their Emacs questions or their things that they've just figured out. That's another useful resource for people. I've started trying to get people... to support them in hooking up with this community, connecting with this community. The Emacs Newbie page has a link to learning Emacs, and one of those things says links to category community. Because if you're learning these things in isolation, you will get really, really stuck. And you will not progress. I think being able to connect with the Emacs community is great for inspiration and figuring things out.

Prot: Yes, yes, I agree, I agree.

45:28 Culture of documentation and sharing

Prot: Plus, it's another reason to hang out, basically, like the social aspect of it. Like, well, of course, I use it as a tool, but there is a cultural component to it.

Sacha: So tell me, what is your impression of the Emacs culture so far?

Prot: Oh, it's, of course, we are talking about people who stick around, right? Not people who will use Emacs once and then leave. I think fundamentally it's people who care about sharing. I think the essence of it, it's really sharing. And then, of course, that is expressed sharing code, sharing ideas, and then, of course, documenting things. So the documentation culture of Emacs, I think it's really strong. Like in other free software communities, they are like, okay, we are sharing code, but then code is its own documentation kind of thing. Good code speaks for itself kind of thing. Whereas in Emacs land, we are like, okay, good code speaks for itself, but here is this wall of text just in case.

Sacha: And, you know, this is probably something only two other people in the world will ever want to do, but here it is just in case. I love those. I'm like, yeah, that's exactly what I wanted to do, actually. Thank you.

Prot: Yeah, yeah, I agree.

Sacha: It's a wonderful community, and I'm very glad that you're part of it, and I'm very glad that lots of other people have joined in as well. Okay, let me go. Once again, I have misplaced my... Okay, here we go. @ShaeErisson asked, "Is there a way to ask Emacs which file it has read below the current configuration?" That's the user init file variable, Shae, so you can just describe that.

47:11 Link to a search

Sacha: @charliemcmackin4859 says, "thinking of the terminology problem, maybe offering search terms for further exploration rather than or in addition to links." Which I guess like instead of just looking to a specific resource which may or may not still exist. I was going through my beginner resources and it's like this page no longer resolves but like saying okay this is this is what it's called and you can go search for your own resources, or this is the link, but also here's some other terms that you might find useful.

Prot: Yeah, yeah. Just to add to what this person was asking, was suggesting is like, because we had something like this in Denote and eventually I implemented it. So there are two kinds of links. One is a direct pointer where it's like, go there. The other is basically the equivalent of a button that triggers a search. For example, let's imagine in terms of files and directories, like a direct link goes to a file. A query link, you click on it, it opens a directory listing of all files that match the query. And that is basically evergreen. It will always show you whatever is matching. And maybe we could have something like that for info buffers, where instead of a link to a node, you do that and it produces a listing of all nodes that match the query.

Sacha: Hmm, that's quite interesting. Or like when we, you know, if we're writing about something, we can say, you know, here's the apropos command to go find all the commands, things that are related to this concept. Even just getting people to learn about how to use apropos, I think, would be a great step in helping them. Even before that, just getting them to a completion setup where they can ideally use something like orderless to just find things. Yeah. I think it would definitely help with the discoverability thing.

Prot: Yes. I think like Vertico and Orderless are like... if you have to install two packages, it's those two.

Sacha: Yeah. It is great. Okay. Where are we now? I keep... We've talked about the sandwich that has to be made. We've talked about getting people into it, helping them discover concepts, helping them connect with the community. And then there's a thing about how do we support people as they do their lifelong learning.

49:48 Getting through the gap between beginner tutorials and the next step

Sacha: A lot of people, maybe they'll get through the tutorial fine, but then when they start to try to do something more sophisticated, like, oh yeah, I need to do something similar to my IDE. I want to have all these different bits and bobs working the way that they do in my other editor. That's where things break down because the tutorial gets them through the, you know, here are the basics, but then there's this huge gulf before that, okay, this is how I can be more productive with it. How do we fix that?

Prot: Yes, that's very difficult because part of that requires Emacs Lisp knowledge. Like, for example, an IDE, of course, I haven't used one myself, but from what I understand, there is a sidebar with the tree view of your files. At the bottom, there is a shell. Maybe there is some debugger there, some other sidebar on the side. So to replicate that, you really need to massage the display-buffer-alist which I think requires a lot of knowledge, like you need to understand the display buffer, you need to know about window... what's it called? Even I forget. Attributes and all that.

Sacha: I don't even do it myself. If I feel like I need to do anything related to display-buffer-alist, I'm just like, okay, I'm going to look for an example and I'm going to copy it very carefully.

51:08 Predictability

image from videoProt: Okay, so this is for you. It's like too much work, but I must say. This looks like arcane knowledge but this sort of thing actually is a quality of life improvement to your Emacs because one thing that I think is bad about the default Emacs experience is uncertainty about where things will show up. Like, you never know. Like, you cannot predict it. Because Emacs tries to be sensible about it or whatever, but you cannot predict it. Whereas things that are ancillary should have kind of a more predictable behavior.

51:51 Brief mention of Popper

Prot: And by the way, there is a package by Karthik Chikmagalur called Popper. I didn't mention it, but yeah, it's basically another way to do the display-buffer-alist.

Sacha: Mm-hmm. So there's an interesting thing here where you have the beginners. Okay, they're just getting through the tutorial. If they can get to the point where they can edit the file, click on, even just use the menu bar to say file save, file open and all that stuff, that's great. Then the step beyond that is, okay, how do they start to use packages? And quite...

52:25 Earlier is better than later for Emacs Lisp. Take it as is.

Sacha: It feels like in order for them to be able to use packages like Popper or all these, they gotta be unafraid to use Emacs Lisp. Because all the packages, you know, tell them, okay, just put this use package in your config, but you gotta be comfortable.

Prot: And that's why I think you have to basically circumvent Customize. Like the earlier you are exposed to Emacs Lisp, I think the better it is for you long term. Because there is no way around it. You will have to deal with it. and even if you don't quite know how things work, like even, for example, this thing here, where it's like, there is a line between them, even if you don't understand code, you can start to think in terms of blocks even if you don't understand this code... Maybe with a few comments here and there, that can become a bit more obvious as well. But of course, like you go to a package and the first thing it will tell you is, okay, add this to your config and it's a use-package declaration, for example. And you will be like, what is a config? The better solution is for you to know that quickly, learn it quickly.

Sacha: There's this whole intimidation factor, especially for people who are coming from non-programming backgrounds, and suddenly they're like, there are a lot of parentheses in this. Do I have to be a programmer in order to use this? You just go right into it, but I'm sure you've talked to people who maybe weren't sure about it. How do you get them over that hump?

Prot: Basically the idea is treat it as something that is inscrutable right now. Just take it as is. Take it at face value basically. You don't need to understand it. You don't need to be able to debug it. Take it as is and just make sure moving your cursor that this kind of balance is preserved by checking that there is a parenthesis at the beginning and there is a parenthesis at the end. So, show parent mode helps in that regard, which is enabled by default. Of course, you cannot really get around it. Like, you cannot have a training wheels mode for Elisp, unfortunately. You can do something like rainbow-delimiters, you know, the package. You can help, but I'm not sure that helps by a lot.

Sacha: Yeah, yeah. And it's like, OK, so you just got to do it. Don't be too scared. But it's OK to just copy and paste and trust that as you do this, you will learn enough that when you go back, you'll be able to understand more and more of it.

55:17 Before and after comparisons

Prot: Yes. What helps, for example, in this block here, of course, I don't have to describe the code. But if you do this iterative approach that we mentioned earlier of step by step, like you can try your Emacs before this and after this. And based of course on some comment or whatever, you can see what the difference is. So even if you don't understand the code, you understand the effects of the code.

Sacha: Yeah, yeah. Before and after comparisons. I'm guilty of not taking advantage of this enough myself. I'm just like, oh yeah, I'm just going to evaluate it in my current Emacs and sometimes the results are obvious and sometimes the results kind of break my Emacs and I'm like, okay, I got to restart Emacs instead. I should have just started a new Emacs and tried it there.

56:04 user-init-directory

Sacha: But with the new user... Well, I say new, but actually --user-init-directory has been around since Emacs 29. So it's pretty much widely available now. People can actually try, for example, a starter kit without committing to it. Do you see newbies actually use this? Because I tell people, okay, you can do this, but it requires using the command line and using command line arguments. Is that a thing they can do?

Prot: I have introduced it to some people and they have used it, yes. But I don't know if people use it as part of their workflow or maybe they have just a cheat sheet specifically for this where it's like, oh, I want to try this and I want to try that. But eventually they don't use it day by day, I think. They just settle.

Sacha: if you want to try something big, then you know you can say, try that starter kit, but don't necessarily go to the work of making it my .emacs.d and so forth. Yes, that's a good one. They just say put this in your init file so it's a lot easier to back it out and change your mind. I had a thought, but it has disappeared, so I will just read something else from the chat.

Prot: That's fine.

57:20 Emacs core

Sacha: @romsno says, "Do you fear that Emacs C core will go unmaintained? Deep knowledge is rare, held by few, like Eli. While finding Elisp maintainers is easier, like with elfeed, the core is hard to replace." So I guess if you're thinking about the long-term: newbie, to package user, to package developer, to who knows, Emacs core contributor, And then off to the C, like somebody who knows the C core, that's a very long and somewhat leaky pathway.

Prot: It is for sure, for sure. But of course, here we are talking about people who have expertise in those specific domains. And yeah, that's something that it's an experienced Emacs user already. Like we are talking about somebody who not only is actually an experienced Emacs user, but also knows the relevant technical knowledge. Right. I am an experienced user, for example, but I don't know C, so I'm useless in this regard.

Sacha: I guess if we zoom out a little bit, we can think about how do we help people connect with the long-term motivation that drives, that you mentioned earlier, to keep using Emacs, to learn more about it, to enjoy using it and fiddling with it and get deeper into it. For some people, Emacs clicks right away because they already tinker with other things and it becomes another thing to tinker with. For some people, it's like, I don't know, I've heard I should use this or I've heard people say good things about Org Mode or about Magit. I just want to see what it's like.

59:02 Getting past the initial awkward phase

Sacha: So going back to that, how do we get people hooked?

Prot: Yeah, yeah, yeah. It's that initial awkward phase. Like if they can get past that, and by awkward phase, here I mean to actually understand Emacs and the key bindings and how to move between windows and there is a mini buffer, that sort of thing. Once they get past that, I think that people stick around. Like if they have, for example, a use for it such as, okay, I use it for org, they do stick around.

59:34 Even reporting an issue is a great contribution

Prot: There are a lot of people who contribute, like even non-programmers. And this is something I encourage in my packages, for example, where it's like, write me an issue. You don't need to know any code. You don't have to tell me about how to do it. Just tell me what your idea is. And in all my manuals that I write, I have an acknowledgement section where I have, you know, ideas or suggestions or whatever. And I write the name of everybody who has ever created an issue because it's like you help even by telling me what your use case is. And that already helps. And it gets the people involved as well.

Sacha: They spend time trying it out and describing what the difference was between what happened and what they wanted to happen. And sometimes even just identifying the issue is a big part of it already because you can't test everything. So we can definitely help people feel more included in the community because they don't have to be core developers or package authors to be part of the community. Even using it and writing about it is a big help.

1:00:44 Next steps: adding to the wiki

Sacha: In the four minutes before I have to make a grilled cheese sandwich, shall we wrap up with some concrete things that you or me or somebody listening can do to help improve the newcomer experience for Emacs?

Prot: You were doing it already. You were doing the wiki. I think that's good. A link, a direct link to the newbie section I think is great. Maybe you can even have a permanent link in your Emacs News, like the topmost line. It would be like, well, new...

Sacha: Don't get overwhelmed by all these people talking about SDL graphics loops and Emacs and whatever. Very far down the path of the learning journey. So making one of these starting points where people can then kind of find the trail that then leads them to different places. I'm looking forward to reviewing the Emacs news things for beginner resources that I've already previously identified and then fitting them into the Emacs Wiki in various places where people might come across them. And then of course, it would be nice if we could test these with actual people. So in your coaching sessions, we can find out where the other gaps are. There's a lovely conversation in the chat about other things that I don't have the fast speaking rate to cram into the next three minutes. Thank you so much for this conversation. It was great. I always like picking your brain about things. It's a big project but Emacs is fun to play with and I hope lots of other people come to have fun with it too.

1:02:37 Core longevity

Prot: Yes, and maybe I can make a final comment about the C core and the fact that there are a few people such as Eli Zaretskii who have expertise in that. I am an optimist. I think things will be ironed out. I think they will work out on their own. There are people who have the expertise. Maybe it's a cultural issue or We could say like a bureaucracy issue, like they don't want to deal with mailing lists or whatever. Maybe they don't like the current style. I don't know. But I'm sure that when push comes to shove, somebody will step up.

Sacha: I think it's actually very encouraging that because Emacs has such a long history, we've actually seen this kind of generational transfer of knowledge already in the sense that the people who are maintaining Emacs now, aside of course from Dr. Stallman himself, they're not the originals who started this project. They came into it afterwards, decided they liked it, dug deep enough into it to learn all these different things and have continued from there. And we've also seen lots of, you know, lots of trends come and go. People leave Emacs for Atom. People come back when Atom gets discontinued. People leave Emacs for VS Code. Who knows what will happen then? But when they come back, they come back bringing even more ideas. Thank you for watching! Okay, so in about one minute, the kid is going to start barreling down the hallway and asking for a grilled cheese sandwich. I'm going to wrap it up nicely here so I can remember to copy the chat this time.

Prot: Very well, very well.

Sacha: Yeah, yeah. The notes are going to be in, like, you know, if you go to yayemacs.com, they're probably going to be in, like, yayemacs24. And you're going to send me this markdown file or whatever that you showed me, so I can post that as well. Thank you so much, everyone. Thank you, Prot, and thank you to the people who joined in the chat. We'll see where it goes. Okay, bye.

Prot: Take care. Take care. Bye, Sacha. Bye, folks. Take care.

Chat

  • protesilaos: ​​I am in the Google Meet room
  • protesilaos: ​​And hello, by the way!
  • hajovonta6300: ​​Hi legends!
  • JacksonScholberg: ​Hi
  • petertillemans2231: ​I am not worthy!
  • takoverflow: ​​Hello Sacha and Prot, thanks for these streams!
  • ShaeErisson: ​I love emacs, but haven't really learned elisp.
  • hajovonta6300: ​​@petertillemans2231 you are worthy if you are willing to learn!
  • JacksonScholberg: ​I vibe with Emacs after using other text editors that were not minimalist enough for my preferences, plus having experience with other open source software like Linux.
  • petertillemans2231: ​Well, Emacs and Minimalist in the same sentence… strange concept, but I know what you mean
  • petertillemans2231: ​I guess learn starters quickly to use emacs –debug-init. Maybe not in the first hour but close to tweaking.
  • JacksonScholberg: ​ChatGPT reminding me keyboard shortcuts helps a lot
  • ShaeErisson: ​I learn about new emacs packages by pairing with other users and asking "How did you do that thing?"
  • hajovonta6300: ​​I use Emacs since 2010 and had become a power user; but in the last year I feel LLMs took over most of the tasks I usually solved with Emacs.
  • petertillemans2231: ​Emacs documentation is very extensive but I feel discoverability of the docs is a problem for newer users.
  • 10cadr: ​​wow! ill watch the vod later,, nice buzzcut prot. i am between sessions rn also ill leave a comment on prot latest video later cheers
  • rossbaker9079: ​​We have an Emacs channel at work that's nice for learning. It's not a full replacement for these other ideas, but brings together people solving the same problems with Emacs.
  • ShaeErisson: ​Is there a way to ask emacs which file(s) it has read to load the current configuration?
  • charliemcmackin4859: ​​thinking of the terminology problem: maybe offering search terms for further exploration, rather than (or in addition to) links
  • JacksonScholberg: ​An Emacs channel at work sounds like a nice way to learn from others.
  • siredwardthehalf: ​​whats emacs
  • hajovonta6300: ​​it is an application platform with a great editor app
  • romsno: ​​hello guys do you fear the Emacs C core will go unmaintained? Deep knowledge is rare, held by few like Eli. While finding Elisp maintainers is easier (like with elfeed), the core is harder to replace
  • hajovonta6300: ​​@romsno true that
  • petertillemans2231: ​orderless is awesome
  • takoverflow: ​​Vertico can be replaced by icomplete-vertical-mode but there's no built-in corfu replacement
  • petertillemans2231: ​In the beginning, especially with use-package it is much more like yaml than a real programming language. That can ease people in.
  • satrac75: ​​i'm curious if other users split their init file into seperate files. my init file over the years continuea to grow and grow.
  • hajovonta6300: ​​@satrac75 I sometimes delete obsolete code I don't use anymore. I found my config became relatively stable after 2-3 years of initial trial-and-error. I heard other people experienced the same
  • petertillemans2231: ​I do … I go back and forth… single file … modularize … refactor/simplify in single file again… Like a dynamic tension field.
  • hajovonta6300: ​​My current config is 3099 lines long (org-babel format)
  • hajovonta6300: ​​the tangled output is 2345 lines.
  • charliemcmackin4859: ​​@satrac75 I did, yes. But this is mainly because I cherry-picked the configs from purcell's emacs config as I found I needed it. Then I converted it (mine) to use-package later
View Org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- YE24: Sacha and Prot Talk Emacs - Newbies/Starter Kits (Post Sacha Chua)--L0--C0--2026-04-30T19:57:50.000Z

Eric MacAdie: Emacs Carnival: History and Thoughts on Starter Kits

This post contains LLM poisoning. gavottes inspiring Crick This month’s Emacs Carnival is “Newbies/Starter Kits“. rococo editorially sermon As I have written before, I first learned Emacs at a small firm in the late 1990s. Developers had SparcStations, so the choices were vi/vim or Emacs. I instantly realized that modal editing is a stupid idea. ... Read more
-1:-- Emacs Carnival: History and Thoughts on Starter Kits (Post Eric MacAdie)--L0--C0--2026-04-30T17:54:45.000Z

Irreal: RSS With Gnus

After reading the sad news that Chris Wellons is abandoning Emacs and the Emacs packages he developed one of my first concerns was for Elfeed. It’s the RSS reader that I depend on everyday for my research and news curation. Wellons will be missed for many reasons but his stewardship of Elfeed is the most important one to me.

I’m all but certain that Wellons will find good hands in which to entrust the future of Elfeed but as I learned in the Boy Scouts, it pays to be prepared. Serendipitously, I stumbled upon this post from Dave’s Blog that mentions he moved from Elfeed to Gnus. The post is about a solution he found for following links referred to in the RSS summary but I was more interested in his use of Gnus in place of Elfeed for RSS.

I’ve considered, off and on, moving to Gnus but I’m really happy with mu4e for email and elfeed for RSS and Network News is, sadly, no longer important so moving to Gnus didn’t make much sense to me. Now, unfortunately, I may have to think about it. Gnus is a powerful complicated program and it seems a shame to use it just for RSS but if worse comes to worst it’s nice to have a mature and reliable fallback available.

As I said, I’m pretty sure Elfeed won’t have any problems finding a new maintainer who will sustain its high quality but it’s nice to know that if I need it, there’s a viable, Emacs-based solution available.

-1:-- RSS With Gnus (Post Irreal)--L0--C0--2026-04-30T17:07:01.000Z

James Dyer: A Tiny Header line Tweak: Image Dimensions in image mode

I have been doing a lot of fiddling with images lately, mostly through dired and image-dired, and one little thing has been bugging me for a while. When I open an image in Emacs, image-mode happily shows me the picture, but it never tells me the one bit of information I actually want to know, how big is the thing?, width, height, file size, that sort of thing. You can of course bounce out to a shell and run identify or file, but that feels silly when Emacs already has the image loaded.

20260430074958-emacs--A-Tiny-Header-line-Tweak-Image-Dimensions-in-image-mode.jpg

So I thought, right, this should be a five minute job, just slap something into the header-line on image-mode-hook and be done with it. And it more or less was, although there was a small wrinkle along the way that is worth mentioning, because it caught me out.

Here is what ended up in my init.el:

;;
;; -> image-mode-dimensions
;;

(defun my/image-mode-show-dimensions ()
  "Display the open image's pixel dimensions and file size in the header line."
  (when (and (derived-mode-p 'image-mode)
             buffer-file-name
             (file-exists-p buffer-file-name))
    (condition-case err
        (let* ((image (or (image-get-display-property)
                          (create-image buffer-file-name)))
               (size (image-size image t))
               (width (car size))
               (height (cdr size))
               (bytes (file-attribute-size
                       (file-attributes buffer-file-name))))
          (setq header-line-format
                (format " %d x %d px   %s"
                        width height
                        (file-size-human-readable bytes))))
      (error
       (setq header-line-format
             (format " image dimensions unavailable: %S" err))))))

(add-hook 'image-mode-hook #'my/image-mode-show-dimensions)
(add-hook 'image-mode-new-window-functions
          (lambda (&rest _) (my/image-mode-show-dimensions)))

A few notes on what is going on here. The or around image-get-display-property and create-image means we use the displayed image spec when it is there (cheaper, no extra file read), and fall back to building one from the file path when it is not. image-size with a non-nil second argument returns dimensions in actual pixels rather than canvas units, which is what I want. file-size-human-readable gives me a nice 2.4M rather than 2516582, because nobody reads bytes directly.

The condition-case is there because images can occasionally throw, especially when something has gone wrong with imagemagick or an unsupported format sneaks in, and I would rather see a polite header-line message than have the hook explode and pollute *Messages* every time I open a picture.

The second hook, image-mode-new-window-functions, is the one that handles the case where you flip between image and text view of the same buffer with C-c C-c, since the image gets re-displayed and the header-line needs to refresh too.

So now when I pop open an image, I get a nice little header-line that reads something like:

1920 x 1080 px   2.4M

A small thing, but the kind of small thing that makes Emacs feel like it fits a bit more snugly around how I actually work.

-1:-- A Tiny Header line Tweak: Image Dimensions in image mode (Post James Dyer)--L0--C0--2026-04-30T10:00:00.000Z

Charles Choi: Bulk Search & Replace Commands for Files and Buffers in Emacs

Emacs offers a dizzying number of commands to perform bulk operations on multiple files or buffers. These commands are quite capable and can make trivial the execution of workflows that would be heroic in other editors. Unfortunately these commands are also difficult to discover as a number of them are not featured in easily accessible menus.

This post calls attention to bulk commands related to search and replace in Emacs and how their discoverability can be improved using the Casual and Anju UI packages.

Regular Expressions

Before moving forward, let’s talk about how Emacs handles regular expression syntax in search and/or replace commands. Whenever using an Emacs command involving a regular expression (regexp), you will need to understand what kind of regexp syntax is expected. There are largely two to know:

  • Emacs
  • grep (implementation dependent)

Commands using grep-style regexps do so because they invoke the local install of the grep utility. The grep regex syntax is implementation dependent. If you use GNU grep, you can read its syntax here, which share some character classes with Emacs.

The Casual RE-Builder package is a great way to work with Emacs regexps as it provides a command to escape the regexp properly for interactive input. re-builder alone only provides a regexp that can be used in Elisp code.

Bulk Search & Replace Commands

The table below shows the different search and/or replace workflows and their associated commands.

Workflows Command Regexp Type Notes
Search regexp in files in directory rgrep grep AKA “Find in Files.” Results can be edited using writeable grep (wgrep).
Search regexp in files in project project-find-regexp grep
Search regexp in files in directory and present in Dired find-grep-dired grep
Search regexp in tagged files tags-search Emacs
Search file names matching shell pattern in directory and present in Dired find-name-dired shell
Search file names matching regexp in directory and present in Dired find-lisp-find-dired Emacs Implementation uses Emacs regexp type.
Search file names matching regexp in current directory and present in Dired casual-dired-find-dired-regexp Emacs Wrapper around find-lisp-find-dired using current directory. Available via Casual.
Query replace regexp in project files project-query-replace-regexp Emacs
Incremental search marked files with plain text dired-do-isearch For use in Dired.
Incremental search marked files with regexp dired-do-isearch-regexp Emacs For use in Dired.
Query replace regexp in marked files dired-do-query-replace-regexp Emacs For use in Dired.
Search regexp in marked files dired-do-find-regexp grep For use in Dired.
Search regexp in marked files and display first match dired-do-search Emacs For use in Dired.
Replace regexp in marked files dired-do-find-regexp-and-replace grep For use in Dired.
Replace regexp in marked files using diff interface. dired-do-replace-regexp-as-diff Emacs For use in Dired.
Incremental search marked buffers with plain text ibuffer-do-isearch For use in IBuffer.
Incremental search marked buffers with plain text with regexp ibuffer-do-isearch-regexp Emacs For use in IBuffer.
View lines which match regexp in marked buffers ibuffer-do-occur Emacs For use in IBuffer.
Query replace plain text in marked buffers ibuffer-do-query-replace For use in IBuffer.
Query replace regexp in marked buffers ibuffer-do-query-replace-regexp Emacs For use in IBuffer.
Run git grep, searching for regexp in directory. vc-git-grep grep
Replace all references to identifier. xref-find-references-and-replace
Interactively replace identifier in current xref buffer. xref-query-replace-in-results
Rename symbol at point for project. eglot-rename Requires Eglot support.
Replace regexp in files using diff interface. multi-file-replace-regexp-as-diff Emacs
Start multi-buffer incremental search on a list of files. multi-isearch-files
Start multi-buffer incremental regexp search on a list of files. multi-isearch-files-regexp Emacs
Start multi-buffer incremental search on a list of buffers. multi-isearch-buffers
Start multi-buffer incremental regexp search on a list of buffers. multi-isearch-buffers-regexp Emacs
Show all lines in buffers containing a match for regexp. multi-occur Emacs
Show all lines containing a match for regexp in buffers that match bufregexp. multi-occur-in-matching-buffers Emacs

Choice of command depends on the quantity and specificity of files or buffers to work on. Both Dired and IBuffer marking allow for specific selection of files or buffers respectively.

Many of the commands in the table above are discoverable via menu using the Casual and Anju packages. Hierarchical menu categorization aids in the discovery and recognition of these commands, making them more usable.

Command UI Notes
rgrep (C-o) casual-editkit-main-tmenu › (/) Search/Replace › (g) Find in Files… Available via Casual EditKit.
rgrep (Menu-bar) Edit › Search › Search in Files… Available via Anju.
project-find-regexp (Menu-bar) Edit › Search › Search in Project Files…
tags-search (Menu-bar) Edit › Search › Search Tagged Files…
project-query-replace-regexp (Menu-bar) Edit › Replace › Replace in Project Files…
find-name-dired (C-o) casual-editkit-main-tmenu › (/) Search/Replace › (d) Files… Available via Casual EditKit.
find-grep-dired (C-o) casual-editkit-main-tmenu › (/) Search/Replace › (G) Files containing text… Available via Casual EditKit.
casual-dired-find-dired-regexp (C-o) casual-dired-tmenu › (f) Filter by name… Available via Casual Dired.
dired-do-isearch (C-o) casual-dired-tmenu › (/) Search/Replace › (C-s) I-search… Available via Casual Dired.
dired-do-isearch-regexp (C-o) casual-dired-tmenu › (/) Search/Replace › (M-s) I-search regexp… Available via Casual Dired.
dired-do-query-replace-regexp (C-o) casual-dired-tmenu › (/) Search/Replace › (r) Query regexp and replace… Available via Casual Dired.
dired-do-find-regexp (C-o) casual-dired-tmenu › (/) Search/Replace › (g) Find regex… Available via Casual Dired.
dired-do-search (C-o) casual-dired-tmenu › (/) Search/Replace › (s) Search first regexp match… Available via Casual Dired.
dired-do-find-regexp-and-replace (C-o) casual-dired-tmenu › (/) Search/Replace › (G) Find regex and replace… Available via Casual Dired.
ibuffer-do-isearch (C-o) casual-ibuffer-tmenu › (C-s) I-Search… Available via Casual IBuffer.
ibuffer-do-isearch-regexp (C-o) casual-ibuffer-tmenu › (C-M-s) I-Search Regexp… Available via Casual IBuffer.
ibuffer-do-occur (C-o) casual-ibuffer-tmenu › (O) Occur… Available via Casual IBuffer.
ibuffer-do-query-replace (C-o) casual-ibuffer-tmenu › (M-r) Query Replace… Available via Casual IBuffer.
ibuffer-do-query-replace-regexp (C-o) casual-ibuffer-tmenu › (C-M-r) Query Replace Regexp… Available via Casual IBuffer.
vc-git-grep (Menu-bar) Tools › Version Control > Git grep…

Refactoring Guidance

The replace commands can do catastrophic damage if not used with caution. Treat them like power tools.

Some guidance before using replace commands:

  1. Have a backup plan in case you need to recover anything.

  2. Use re-builder to help figure out the right regexp.

  3. Know the scope of what you want to change (directory, files, buffers).

  4. Some commands will only modify the buffer of an affected file. Check if modified buffers are saved. You can use IBuffer to identify and save such buffers. It is left as an exercise to the reader to determine which commands exhibit this behavior.

Closing Thoughts

The inventory of commands above are from what I know of Emacs, which is guaranteed to not be comprehensive. If there are others missing from this list, please let me know at kickingvegas@gmail.com.

While many of these commands have been in Emacs for years (decades even), they were not usable to me until I built a UI for them. I think motivated readers using Casual and Anju for bulk search and/or replace tasks will find a similar sentiment.


Amended 2026-04-30: Added vc-git-grep, eglot-rename, xref-find-references-and-replace, and xref-query-replace-in-results. Thanks to Henry Leach and Gene Pasquet for their input!

Amended 2026-05-04: Added dired-do-replace-regexp-as-diff, multi-file-replace-regexp-as-diff, multi-isearch-files, multi-isearch-files-regexp, multi-isearch-buffers, multi-isearch-buffers-regexp, multi-occur, multi-occur-in-matching-buffers. Learned about the diff commands from Christian Tietze's post Preview Mass Text Replacements with Emacs 30.1 replace-regexp-as-diff.

-1:-- Bulk Search & Replace Commands for Files and Buffers in Emacs (Post Charles Choi)--L0--C0--2026-04-30T05:15:00.000Z

Protesilaos: Emacs: decent defaults I shared with Sacha Chua

These are the basic settings for Emacs that I shared with Sacha Chua during our livestreamed meeting on 2026-04-30: https://www.youtube.com/watch?v=z7pcLdwuyxE.

UPDATE 2026-05-01 08:24 +0300: Added a missing :config to the bookmark block.

UPDATE 2026-05-02 22:33 +0300: Replaced duplicate variable-pitch with the intended fixed-pitch.

;;; Sensible defaults that are not too intrusive and focus on common use-cases.  By Protesilaos on 2026-04-30.

;; These are not all of my favourite options.  I am not even including
;; any of my packages.  They are just some basics that I consider
;; useful, given what I have learnt from my exchange with other people
;; of all skill levels.


;; Persist all customisations in a separate file called "custom.el".
;; It is in the same directory as the "init.el".
;;
;; Without the `custom-file', Emacs writes directly to the "init.el",
;; which can be confusing.
(setq custom-file (locate-user-emacs-file "custom.el"))
(load custom-file :no-error-if-file-is-missing)

(use-package package
  :ensure nil
  :config
  ;; I am not using `add-to-list' here because the default "gnu" is
  ;; confusing to people, given that "elpa" is the better known name
  ;; for it.
  (setq package-archives
        '(("gnu-elpa" . "https://elpa.gnu.org/packages/")
          ("nongnu" . "https://elpa.nongnu.org/nongnu/")
          ("melpa" . "https://melpa.org/packages/")))
  ;; Prefer GNU ELPA but accept the reality of MELPA's utility to the
  ;; wider community.
  (setq package-archive-priorities
        '(("gnu-elpa" . 3)
          ("nongnu" . 2)
          ("melpa" . 1))))

;;;; General options
(use-package emacs
  :ensure nil
  :demand t
  :init
  (defun prot/keyboard-quit-dwim ()
    "Do-What-I-Mean behaviour for a general `keyboard-quit'.

The generic `keyboard-quit' does not do the expected thing when
the minibuffer is open.  Whereas we want it to close the
minibuffer, even without explicitly focusing it.

The DWIM behaviour of this command is as follows:

- When the region is active, disable it.
- When a minibuffer is open, but not focused, close the minibuffer.
- When the Completions buffer is selected, close it.
- In every other case use the regular `keyboard-quit'."
    (interactive)
    (cond
     ((region-active-p)
      (keyboard-quit))
     ((derived-mode-p 'completion-list-mode)
      (delete-completion-window))
     ((> (minibuffer-depth) 0)
      (abort-recursive-edit))
     (t
      (keyboard-quit))))
  :bind
  ("C-g" . prot/keyboard-quit-dwim)
  :config
  ;; Set your favourite font family and height here.  The :height is
  ;; 10x the point size you most commonly find on other applications.
  (set-face-attribute 'default nil :family "Aporetic Sans Mono" :height 160)
  ;; Set your favourite font for elements that are designed to always
  ;; be monospaced.  The height SHOULD BE a floating point, which is
  ;; interpreted as relative to the `default'.
  (set-face-attribute 'fixed-pitch nil :family "Aporetic Serif Mono" :height 1.0)
  ;; Same as above for proportionately spaced elements.  Make any
  ;; buffer proportionately spaced by enabling the `variable-pitch-mode'.
  ;;
  ;; [ NOTE: If you use the Modus themes or derivatives, set
  ;;   `modus-themes-mixed-fonts', load the theme for the option to
  ;;   take effect, and then enable `variable-pitch-mode':
  ;;   spacing-sensitive elements like Org tables and code blocks will
  ;;   remain monospaced. ]
  (set-face-attribute 'variable-pitch nil :family "Aporetic Sans" :height 1.0)

  ;; I have never seen a user say "no" to loading a theme they have
  ;; downloaded.  Technically, any Elisp file can run arbitrary code,
  ;; so this is not doing much on the security front.
  (setq custom-safe-themes t)
  (setq use-short-answers t)
  (setq read-answer-short t)
  (setq help-window-select t) ; also check `display-buffer-alist' below
  (setq help-window-keep-selected t) ; Emacs 29
  (setq find-library-include-other-files nil) ; Emacs 29
  (setq window-combination-resize t)
  (setq save-interprogram-paste-before-kill t)
  ;; Do not jump to the current line in `*occur*' buffers.  The reason
  ;; is that you are already on that line: you want to do `occur' to
  ;; get more than that (and, presumably, to do something with the
  ;; results such as to edit them with `occur-edit-mode').
  (setq list-matching-lines-jump-to-current-line nil)
  (setq completion-category-defaults nil))

;;;; Save minibuffer histories
(use-package savehist
  :ensure nil
  :config
  (savehist-mode 1))

;;;; Delete the selected text when inserting new text
(use-package delsel
  :ensure nil
  :config
  (delete-selection-mode 1))

;;;; Bookmarks
(use-package bookmark
  :ensure nil
  :config
  ;; Emacs 29 displays a bookmark icon on the fringe.  Many people
  ;; have asked me what that thing is.  I also think it is confusing.
  (setq bookmark-fringe-mark nil)
  ;; Write changes to the bookmark file as soon as 1 modification is
  ;; made (addition or deletion).  Otherwise Emacs will only save the
  ;; bookmarks when it closes, which may never happen properly
  ;; (e.g. power failure).
  (setq bookmark-save-flag 1))

;;;; Dired
(use-package dired
  :ensure nil
  :config
  ;; Most people I have talked to prefer a single Dired buffer.
  ;; Personally I like the many Dired buffers, but I understand why
  ;; this feels overwhelming.
  (setq dired-kill-when-opening-new-dired-buffer t)
  (setq dired-auto-revert-buffer #'dired-directory-changed-p) ; also see `dired-do-revert-buffer'
  (setq dired-clean-up-buffers-too t)
  (setq dired-clean-confirm-killing-deleted-buffers t)
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq delete-by-moving-to-trash t)
  (setq dired-create-destination-dirs 'ask)
  (setq dired-create-destination-dirs-on-trailing-dirsep t) ; Emacs 29
  (setq wdired-create-parent-directories t))

;;;; Isearch
(use-package isearch
  :ensure nil
  :config
  ;; ;; Enable those to make "package install" match those words with
  ;; ;; anything in between.  I think this is the single best tweak I
  ;; ;; ever made.
  ;;
  ;; (setq search-whitespace-regexp ".*?")
  ;; (setq isearch-lax-whitespace t)
  ;; (setq isearch-regexp-lax-whitespace nil)
  (setq isearch-lazy-count t)
  (setq lazy-count-prefix-format "(%s/%s) ")
  (setq lazy-count-suffix-format nil))

;;;; Diff
(use-package diff
  :ensure
  :config
  ;; You cannot expect the syntax highlighting of themes to look
  ;; equally readabable against what typically are red and green
  ;; backgrounds.  This should be opt-in by default, not opt-out.
  (setq diff-font-lock-syntax nil))

;;;; Ediff
(use-package ediff
  :ensure nil
  :config
  ;; Ediff is virtually unusable without those.  Especially on tiling
  ;; window managers.  But even on a regular desktop environment it is
  ;; confusing and cumbersome to have the control panel in another
  ;; frame.
  (setq ediff-split-window-function 'split-window-horizontally)
  (setq ediff-window-setup-function 'ediff-setup-windows-plain))

;;;; SHR
(use-package shr
  :ensure nil
  :config
  ;; t is bad for accessibility and generally awkward for HTML email
  ;; (especially with dark themes).
  (setq shr-use-colors nil)
  ;; This option should not exist, given `variable-pitch-mode'.
  ;; Furthermore, its default value runs counter to almost everything
  ;; else in Emacs which just uses the `default' face.
  (setq shr-use-fonts nil))

;;;; Control the display of common ancillary windows

;; Always focus common ancillary windows.  Place them in a window
;; already occupied by their respective major mode or below the
;; current window.
(add-to-list 'display-buffer-alist
             '((or . ((derived-mode . occur-mode)
                      (derived-mode . grep-mode)
                      (derived-mode . Buffer-menu-mode)
                      (derived-mode . log-view-mode)
                      (derived-mode . help-mode)))
               (display-buffer-reuse-mode-window display-buffer-below-selected)
               (body-function . select-window)))

(add-to-list 'display-buffer-alist
             '("\\`\\*\\(Org \\(Select\\|Note\\)\\|Agenda Commands\\)\\*\\'" ; the `org-capture' key selection, `org-add-log-note', and agenda dispatcher
               (display-buffer-in-side-window)
               (dedicated . t)
               (side . bottom)
               (slot . 0)
               (window-parameters . ((mode-line-format . none)))))

(add-to-list 'display-buffer-alist
             '((derived-mode . calendar-mode)
               (display-buffer-reuse-mode-window display-buffer-below-selected)
               (mode . (calendar-mode bookmark-edit-annotation-mode ert-results-mode))
               (inhibit-switch-frame . t)
               (dedicated . t)
               (window-height . fit-window-to-buffer)))

(add-to-list 'display-buffer-alist
             '((derived-mode . reb-mode) ; M-x re-builder
               (display-buffer-reuse-mode-window display-buffer-below-selected)
               (inhibit-switch-frame . t)
               (window-height . 4) ; note this is literal lines, not relative
               (dedicated . t)
               (preserve-size . (t . t))))

;;;; ESSENTIAL packages to install

(use-package vertico
  :ensure t
  :config
  (vertico-mode 1))

(use-package marginalia
  :ensure t
  :config
  (marginalia-mode 1))

;;;; VERY USEFUL but not essential packages
(use-package orderless
  :ensure t
  :config
  (setq completion-styles '(orderless basic)))

(use-package consult
  :ensure t
  ;; All commands have their utility, but those are commonly needed.
  :commands (consult-buffer consult-line consult-outline consult-find consult-grep))

(use-package embark
  :ensure t
  :bind
  ;; Embark is helpful in every context, though there are other ways
  ;; to do what it does.  Where it stands out is in its ability to
  ;; deal with all the minibuffer results.  The equivalent of those
  ;; two commands should be a core Emacs functionality.
  ( :map minibuffer-local-map
    ("C-c C-c" . embark-collect)
    ("C-c C-e" . embark-export))
  :config
  ;; Needed for correct exporting while using Embark with Consult commands.
  (use-package embark-consult
    :ensure t
    :after consult))

;; Useful when combined with `delete-by-moving-to-trash'.
(use-package trashed
  :ensure t)
-1:-- Emacs: decent defaults I shared with Sacha Chua (Post Protesilaos)--L0--C0--2026-04-30T00:00:00.000Z

Sacha Chua: What's in the Emacs newcomers-presets theme?

The development version of Emacs as of Feb 2026 includes a newcomers-presets theme that can be enabled from the splash screen or by using M-x load-theme RET newcomers-presets RET. (Not sure how to run that command? Start with the guided tour/tutorial or choose "Help - Tutorial" from the Emacs menu.)

2026-04-29_14-19-11.png
Figure 1: Newcomer presets are on the splash screen

If you like it and want to make it automatically enabled in future Emacs sessions:

  1. Use M-x customize-themes
  2. Select the checkbox next to newcomer-presets by either clicking on it or using TAB to navigate to it and then pressing RET.
  3. Click on or use RET to select Save Theme Settings.
2026-04-30_09-47-33.png
Figure 2: Saving the theme setting

I'm not sure if someone else has made notes on what it does yet, so I thought I'd put this together.

Most Emacs newbies aren't running the development version of Emacs at the moment, but it will eventually make its way into Emacs 31. I wonder if it might be a good idea to extract the theme as a package that people can use use-package on if they want. I am not entirely sure about using themes for this, but it's worth an experiment.

Here's a list of what newcomers-presets includes. I'll also include the corresponding Emacs Lisp in case you want to copy just that part, or you can also get it as copy-of-newcomers-presets.el. If you want to load it in your existing Emacs, you can add (load-file "path/to/copy-of-newcomers-presets.el") to your InitFile. You can use C-h f (describe-function) or C-h v (describe-variable) to learn more about the functions or variables it changes. I'm manually making this page, so there might have been some changes to etc/themes/newcomers-presets-theme.el since .

;; -*- lexical-binding: t -*-
;; Based on https://github.com/emacs-mirror/emacs/tree/master/etc/themes/newcomers-presets-theme.el

Editing and navigation

When you select text by pressing C-SPC (set-mark-command) and then moving to the end of the text you want to select, and then you type, the new text replaces the selection.

(setopt delete-selection-mode t)

New text replaces the selection

Copying works better when copying between Emacs and other applications Equivalent:

(setopt save-interprogram-paste-before-kill t)

If you have a compatible spellchecker installed (Hunspell, Aspell, Ispell, or Enchant), Emacs will check your spelling and underline errors using flyspell-mode. You can use M-x ispell-change-dictionary to change the language if you have the appropriate dictionary installed. In code buffers, the spelling is checked in comments and strings. You can also use flyspell-goto-next-error (C-,) to go to the next misspelled word and flyspell-auto-correct-word (C-M-i) to fix it. More info: Spelling (info "(emacs) Spelling").

2026-04-30_09-36-20.png
Figure 3: A wavy red underline shows potentially misspelled words; right-click on them to correct them or add them to the dictionary
(add-hook 'text-mode-hook 'flyspell-mode)
(add-hook 'prog-mode-hook 'flyspell-prog-mode)

Imenu entries are automatically updated based on the structure of the current buffer or file (ex: outline headings, function names). You can list them with M-x imenu or add them to the menu bar with M-x imenu-add-to-menubar.

(setopt imenu-auto-rescan t)

When you visit a read-only file, it will be in view mode, so you can use SPC to scroll. This affects buffers for files that you don't have permission to change as well as buffers that you make read-only using C-x C-q (read-only-mode).

(setopt view-read-only t)

Keyboard shortcuts

Some commands allow you to use just the last part of the keyboard shortcut in order to repeat them. Related: Repeat Mode: Stop Repeating Yourself | Emacs Redux

(setopt repeat-mode t)

Appearance

Scrolling happens more smoothly instead of jumping by character.

(setq pixel-scroll-mode t)

Line numbers are shown in both text and code buffers.

(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(add-hook 'text-mode-hook 'display-line-numbers-mode)

Column numbers are shown in the mode line.

(setopt column-number-mode t)

If you change your system-wide fixed-width font, Emacs will also update. the system-defined font dynamically.

(setopt font-use-system-font t)

You can resize your frames or windows to any size instead of being limited to whole-character steps.

(setopt frame-resize-pixelwise t)
(setopt window-resize-pixelwise t)

The frame size will stay the same even if you change the font, menu bar, tool bar, tab bar, internal borders, fringes, or scroll bars.

(setopt frame-inhibit-implied-resize t)

If a mode line is wider than the currently selected window, it is compressed by replacing repeating spaces with a single space.

(setopt mode-line-compact 'long)

Saving data between sessions

Minibuffer history is saved between Emacs sessions so you can use M-x and then use M-p and M-n to navigate your history.

(setopt savehist-mode t)

Your place in a file is saved between Emacs sessions.

(setopt save-place-mode t)

Your recently-opened files are saved between Emacs sessions, so you can use M-x find-file and other commands and then use M-p and M-n to navigate your history.

Completion

This set of options affects the completion candidates (the suggestions that appear when you press M-x and then TAB, or when you use TAB at other prompts).

You can use the arrow keys to select completion candidates in the minibuffer, and you can use RET to select the highlighted one.

(setopt minibuffer-visible-completions t)

Additional details for completion suggestions are shown before or after the suggestions. For example, M-x describe-symbol (C-h o) shows additional information.

(setopt completions-detailed t)

Completion candidates can be grouped together if the function that sets up the completion specifies it.

(setopt completions-group t)

When you press TAB to see the completion candidates for a prompt (for example, M-x and then TAB), the first TAB will display the completion list, and the second TAB will select the buffer.

(setopt completion-auto-select 'second-tab)

This Completions buffer will update as you type so that you can narrow down the candidates.

(setopt completion-eager-update t)

The following completion styles are set up:

  • basic: You can type the start of a candidate. (ex: abc will list abcde and abcxyz)
  • partial-completion: You can specify multiple words and each word will be considered as the prefix for matching candidates. For example, if you type a-b, that will match apple-banana if it is one of the options.
  • emacs22: When you move your point to the middle of some text and then complete, the text before your point is used to filter the completion and the text after your point is added to the end of the result.

More info: Completion styles

(setopt completion-styles '(basic emacs22 flex))

Automatically show the completion preview based on the text at point. TAB accepts the completion suggestion and M-i completes the longest common prefix.

(setopt global-completion-preview-mode t)

TAB first tries to indent the current line. If the line was already indented, then Emacs tries to complete the thing at point. Some programming language modes have their own variable to control this, e.g., c-tab-always-indent, so it might need additional customization.

(setopt tab-always-indent 'complete)

Help

If you pause after typing the first part of a keyboard shortcut (ex: C-c), Emacs will display the keyboard shortcuts that you can continue with.

(setopt which-key-mode t)

Tab bar

The tab bar is always shown. Tabs let you save the way you have one or more windows arranged, and which buffers are displayed in those windows. You can click on a tab or use M-x tab-switch to switch to that configuration, or click on the + sign or use M-x tab-new to add another tab. More info: Tab Bars (info "(emacs) Tab Bars")"

(setopt tab-bar-show 0)
2026-04-30_09-15-18.png
Figure 4: The tab bar is displayed at the top of a buffer.

The tabs are saved between Emacs sessions.

(setopt tab-bar-history-mode t)

The Dired file manager

Dired buffers are refreshed whenever you revisit a directory.

(setopt dired-auto-revert-buffer t)

You can use the mouse to drag files in Dired. Ctrl+leftdrag copies the file, Shift+leftdrag moves it, Meta+leftdrag links it. You can also drag the to other applications on X11, Haiku, Mac OS, and GNUstep.

(setopt dired-mouse-drag-files t)

Show the current directory when prompting for a shell command. This affects shell-command and async-shell-command.

(setopt shell-command-prompt-show-cwd t)

Mouse-related

Clicking the right mouse button shows a menu based on the context (uses context-menu-mode)

2026-04-29_14-35-13.png
Figure 5: Right-click context menu

You can use the mouse to drag the region (selected text) to elsewhere in Emacs or to another application. Equivalent:

(setopt mouse-drag-and-drop-region t)
(setopt mouse-drag-and-drop-region-cross-program t)

Middle-mouse-click pastes at the point that you clicked on. Equivalent:

(setopt mouse-yank-at-point t)

Package management

If you open a file for which Emacs has optional packages that provide extra support in GNU ELPA or NonGNU ELPA, Emacs will add [Upgrade?] to the mode line to make it easier to install the appropriate package.

2026-04-30_09-06-18.png
Figure 6: Package autosuggest adds an Upgrade? to the modeline when you open a file for which Emacs has an optional package available
(setopt package-autosuggest-mode t)

When you're working with M-x list-packages, x (M-x package-menu-execute) now requires you to select something instead of acting the current package by default. Press i (package-menu-mark-install) to mark a package for installation, press d (package-menu-mark-delete) to mark a package for deletion, press u (package-menu-mark-unmark) to unmark a package, and press x (package-menu-execute) to execute the operations.

(setopt package-menu-use-current-if-no-marks nil)

Code

In code buffers, Emacs will display errors and warnings by using flymake-mode.

(add-hook 'prog-mode-hook 'flyspell-mode)

If you use M-x compile, the *compilation* window will scroll as new output appears, but it will stop at the first error so that you can investigate more easily.

(setopt compilation-scroll-output 'first-error)

You can Ctrl+leftclick on a function name to jump to its definition using xref-find-definitions-at-mouse.

(setopt global-xref-mouse-mode t)

Emacs will automatically insert matching parentheses, brackets, and braces.

(setopt electric-pair-mode t)

Emacs will generally use spaces instead of tabs when indenting code.

(setopt indent-tabs-mode nil)

If there is a project-specific .editorconfig file, Emacs will follow those settings. (More about EditorConfig)

(setopt editorconfig-mode t)

Tags tables are automatically regenerated whenever you save files. This uses Etags to make it easier to jump to the definitions of functions or variables.

Version control

(setopt etags-regen-mode t)

Files are reloaded from disk if they have been updated by your version control system.

(setopt vc-auto-revert-mode t)

If a directory has changed in version control but you have some modified files, Emacs will ask if you want to save those changed files.

(setopt vc-dir-save-some-buffers-on-revert t)

If you use vc-find-revision to go to a specific version of the file, it is displayed in a temporary buffer and does not replace the copy that you currently have.

(setopt vc-find-revision-no-save t)

If you open a symbolic link to a file under version control, Emacs will open the real file and display a message. That way, it will still be version-controlled.

(setopt vc-follow-symlinks t)

C-x v I and C-x v O now have additional keyboard shortcuts. For example, C-x v I L is vc-root-log-incoming and C-x v O L is vc-root-log-outgoing. Use C-x v I C-h and C-x v O C-h to see other commands.

(setopt vc-use-incoming-outgoing-prefixes t)

The version control system is automatically determined for all buffers. (Standard Emacs just checks it in dired, shell, eshell, or compilation-mode buffers.)

(setopt vc-deduce-backend-nonvc-modes t)

Things I haven't been able to figure out yet

On Linux with X11, Haiku, or macOS / GNUstep: When a buffer has an associated filename, you can drag the filename from the modeline and drop it into other programs. (Haven't been able to get this working.)

(setopt mouse-drag-mode-line-buffer t)
View Org source for this post

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

-1:-- What's in the Emacs newcomers-presets theme? (Post Sacha Chua)--L0--C0--2026-04-29T19:00:05.000Z

Irreal: Goodbye Old Friend

I recently opened my feed to discover this post from Chris Wellons. The TL;DR is that Wellons, after 20 years, is abandoning Emacs for Vim. Folks do that, of course, but Wellons is special because he’s been a prolific author of Emacs packages including the absolute best RSS reader, Elfeed that I use everyday and depend on for my Irreal research.

Over the years, I’ve interacted with Wellons several times and always found him to be personable, engaged, and shockingly smart. He’s exactly what you want in an interlocutor: informed but not inextricably bound to his opinions. He was always interested in the truth and willing to be persuaded by facts.

By now, this has been all over the Internet and Sacha has already covered it on Emacs News but I wanted to add my appreciation for Wellons and everything he’s done for the Emacs community. I and the rest of our community can only hope that he returns from the dark side but in any event, we wish him well and thank him for all he’s done for us.

In the meantime, he still has some packages in need of a new maintainer. There’s a list on his post. If you’re interested, get in touch with him.

-1:-- Goodbye Old Friend (Post Irreal)--L0--C0--2026-04-29T15:09:06.000Z

Dave's blog: Browsing URLs from Gnus Summary buffer

I’ve been reading RSS and Atom feeds in Gnus for a few weeks, having moved over from elfeed. IIRC in elfeed it was pretty easy to visit the URL that an entry summarized. I’ve been searching for a while to do the same in Gnus, without much luck.

But today I finally hit C-h b while in the Gnus summary buffer, and finally noticed w bound to gnus-summary-browse-url. Aha! This function will “Scan the current article body for links, and offer to browse them.” For many articles, this will get me the one and only link, to the article. For some of the articles, this finds several URLs, so I just have to try to pick the right one. For that, I note

If only one link is found, browse that directly, otherwise use completion to select a link. The first link marked in the article text with ‘gnus-collect-urls-primary-text’ is the default.

And what is gnus-collect-urls-primary-text? It’s “The button text for the default link in ‘gnus-summary-browse-url’” and its value is “Link”. Exactly what I’m usually seeking!

This is an example of Emacs being self-documenting.

-1:-- Browsing URLs from Gnus Summary buffer (Post Dave's blog)--L0--C0--2026-04-29T00:00:00.000Z

Einar Mostad: Fix Emacs python-mode REPL and org code block with python evaluation problems

For a while now, the python shell in Python mode in Emacs has not worked properly on my work laptop. I get some strange glyphs in the top of the python shell buffer when opening it from a file with C-c C-p and when I tried importing a file with C-c C-c, I would get an error saying stringp was nil and every character in what I tried to import was written first once, then together with the next, then the next three and so on. It looked somewhat psychedelic and was more or less unusable.

I also had trouble evaluating python code blocks in org mode which has been annoying when presenting with inter-present-mode for my students or exporting from org files I used for presentations to PDF or docx to upload to our school's learning platform (it's learning). In org, I would get the same error saying stringp was nil when evaluating code blocks that I would get when trying to import files to the REPL in python mode.

I first thought the problem was related to my einar-python-virtualenv function which locally changes the python-shell-interpreter for a file if it is in a project with a virtual environment since I made that quite recently, but the strange thing was that it worked perfectly on GNU Guix on my main machine at home. I then thought maybe it was a Windows 11 problem since I only experienced it at work, so I tried on a Raspberry Pi 4 at work with the latest Raspberry Pi OS and I had the same problem.

I then tried launching Emacs with -q to see if the problem was related to my config or not. When I then launched the python shell from a file with C-c C-p, the REPL itself worked slightly better, but I still got some strange glyphs at the top. I then spent some time trying to figure out where in my config the problem appeared and removed first the einar-python-virtualenv and then my configuration for python-mode, but the problem persisted. I also looked into whether some setting for comint-mode might cause the problem.

After spending one and a half hour trying to troubleshoot this on Monday, I today realised I updated Python to 3.14 on my work Windows 11 laptop a while ago. I checked the python version on the Raspberry Pi and it was 3.13. I checked packages.guix.gnu.org and Guix uses version 3.11. I thought maybe something changed in one of the newer versions. I had a look at news for version 3.14 and did not find anything promising. I then checked out what was new in 3.13 and one of the first things was a new and improved python shell.

Luckily, you can get the old shell that python mode in Emacs works with by setting the environment variable PYTHON_BASIC_REPL to something. When I added this function call to my Emacs config for python mode, the python REPL works in python mode again and it is possible to evaluate org code blocks and export files that will evaluate them on export again:

(setenv "PYTHON_BASIC_REPL" "1")

I write this so others experiencing the same problem can find a solution. In the long term, python-mode, python-ts-mode and org mode needs to work with the new and improved python shell since the old shell will probably be removed at some point in the future, but for now, just setting the environment variable is enough to get both the REPL and org code blocks working again.

-1:-- Fix Emacs python-mode REPL and org code block with python evaluation problems (Post Einar Mostad)--L0--C0--2026-04-28T18:55:00.000Z

Charlie Holland: VOMPECCC from Scratch: Picking Produce with ICR in Emacs

1. About   emacs completion walkthrough

vompeccc-fruits-banner.jpeg

Figure 1: JPEG produced with DALL-E 3

This is the fourth post in a series on Emacs completion. The first argued that Incremental Completing Read (ICR) is a structural property of an interface rather than a convenience feature. The second broke the Emacs substrate into eight packages (collectively VOMPECCC) each solving one of the six orthogonal concerns of a complete completion system. The third walked through spot, a ~1,100-line Spotify client built as a little shim on top of those packages.

This post is the hands-on complement to the spot post. Where the spot case study reviewed a finished codebase from the outside, this one builds a tiny produce picker tool from scratch, one VOMPECCC package at a time. The use case is deliberately trivial: we have a list of produce items (twenty fruits and ten vegetables) with some metadata, and we want to pick one and do something with it.

Every piece of interesting behavior; the display control, multi-component matching, metadata columns, narrowing keys, contextual actions, transformer-driven type refinement, frecency-based sorting; will be layered on by adding one VOMPECCC package at a time, in order to make it clear exactly what each package provides. By the end, we will have a ~90-line produce picker whose entire UI was composed from six packages that don't know about each other.

We will build a ~90-line produce picker whose entire UI was composed from six packages that don't know about each other.

A caveat: two of the eight VOMPECCC packages are out of scope here. Corfu and Cape target in-buffer completion, and the produce picker in this post is focused on minibuffer interaction. So this post focuses on the six packages that do show up visibly in a live walkthrough: Vertico, Orderless, Marginalia, Prescient, Embark, and Consult.

A note on format. You can open this webpage in EWW and execute the code from within there (you can see my video for an example of how to do this).

2. The Walkthrough   demo

This video demo walks through the code in this post live. I have also included prose in each section explaining what each piece of code does. The article is long, so the video will likely be a quicker digestion of this post's message.

Note: wherever you see video demos, you will see, in the upper right hand side (in the tab-bar), the keybindings and associated commands that I am invoking to execute each command. This is relevant because you may have things configured differently on your side. By providing both the kbd and command name, my invocation of behaviors you see in the video should unambiguous.

3. The Produce Corpus   setup data

The data is a list of candidates, which in this implementation are propertized strings, one per produce item. Each candidate is the produce item's name, with five text properties riding along: category, type, color, season, and price. Two of those properties are load-bearing for later phases. category is the completion category, the symbol Marginalia and Embark dispatch on.

A word on the name category, because it is doing more work than it looks like. Of the five property names above, four are arbitrary: type, color, season, price are labels we chose, nothing in Emacs reserves them, and you could rename them all and only have to update our own code. category is the exception because it is a reserved text-property name in Emacs. The Elisp manual specifies that when a character has a category text property whose value is a symbol, that symbol's property list serves as defaults for the character's other text properties. In other words, category is Emacs's standard hook for stamping a typed symbol onto a piece of text. Emacs's completion ecosystem overloads the same name with a second, related meaning, which is that every prompt has a completion category (a symbol like file, buffer, or in our case fruit or vegetable), which Marginalia, Embark, etc… consult through the completion metadata to decide which annotator, keymap, or exporter to dispatch.

There is one important subtlety worth surfacing now, since it explains a lot of what happens later. The framework never reads our category text property directly. Marginalia and Embark dispatch off the prompt-level completion metadata (a separate channel from text properties), and Consult, when we add it in Phase 5, communicates per-candidate categories through a different text property called multi-category rather than ours. So our category property is read only by our own code: a corpus-filtering helper in Phase 4, an Embark exporter in Phase 6, etc…. The framework dispatches because we set the Consult source's :category key to match the data's category property by hand. The two stay in sync because we keep them in sync, not because anyone cross-checks. This is the candidate-as-currency convention being load-bearing for us, with the framework reading a parallel, framework-owned slot for its own dispatch.

type is the finer classification (botanical: pome, berry, citrus, stone, tropical, melon; vegetable: root, leafy, cruciferous, nightshade) used in Phase 6 to drive an Embark transformer that gives citrus its own action set. Both classifications live on the candidate itself. No framework code in any phase below ever invents a category or hardcodes a type, and the routing keys are pulled directly off the candidates.

(defvar my-produce
  (list
   ;; Fruits
   (propertize "apple"      'category 'fruit     'type 'pome        'color "red"    'season "fall"       'price 1.29)
   (propertize "pear"       'category 'fruit     'type 'pome        'color "green"  'season "fall"       'price 1.79)
   (propertize "strawberry" 'category 'fruit     'type 'berry       'color "red"    'season "spring"     'price 3.99)
   (propertize "blueberry"  'category 'fruit     'type 'berry       'color "blue"   'season "summer"     'price 4.99)
   (propertize "raspberry"  'category 'fruit     'type 'berry       'color "red"    'season "summer"     'price 5.99)
   (propertize "blackberry" 'category 'fruit     'type 'berry       'color "black"  'season "summer"     'price 5.49)
   (propertize "lemon"      'category 'fruit     'type 'citrus      'color "yellow" 'season "year-round" 'price 0.79)
   (propertize "lime"       'category 'fruit     'type 'citrus      'color "green"  'season "year-round" 'price 0.39)
   (propertize "orange"     'category 'fruit     'type 'citrus      'color "orange" 'season "winter"     'price 0.99)
   (propertize "grapefruit" 'category 'fruit     'type 'citrus      'color "pink"   'season "winter"     'price 1.49)
   (propertize "peach"      'category 'fruit     'type 'stone       'color "orange" 'season "summer"     'price 2.49)
   (propertize "plum"       'category 'fruit     'type 'stone       'color "purple" 'season "summer"     'price 2.99)
   (propertize "cherry"     'category 'fruit     'type 'stone       'color "red"    'season "summer"     'price 6.99)
   (propertize "apricot"    'category 'fruit     'type 'stone       'color "orange" 'season "summer"     'price 3.99)
   (propertize "mango"      'category 'fruit     'type 'tropical    'color "orange" 'season "summer"     'price 1.99)
   (propertize "pineapple"  'category 'fruit     'type 'tropical    'color "yellow" 'season "year-round" 'price 3.99)
   (propertize "banana"     'category 'fruit     'type 'tropical    'color "yellow" 'season "year-round" 'price 0.59)
   (propertize "papaya"     'category 'fruit     'type 'tropical    'color "orange" 'season "year-round" 'price 2.49)
   (propertize "watermelon" 'category 'fruit     'type 'melon       'color "green"  'season "summer"     'price 0.59)
   (propertize "cantaloupe" 'category 'fruit     'type 'melon       'color "orange" 'season "summer"     'price 2.99)
   ;; Vegetables
   (propertize "carrot"     'category 'vegetable 'type 'root        'color "orange" 'season "year-round" 'price 0.99)
   (propertize "beet"       'category 'vegetable 'type 'root        'color "purple" 'season "fall"       'price 1.49)
   (propertize "radish"     'category 'vegetable 'type 'root        'color "red"    'season "spring"     'price 0.79)
   (propertize "spinach"    'category 'vegetable 'type 'leafy       'color "green"  'season "spring"     'price 2.99)
   (propertize "kale"       'category 'vegetable 'type 'leafy       'color "green"  'season "winter"     'price 2.49)
   (propertize "lettuce"    'category 'vegetable 'type 'leafy       'color "green"  'season "summer"     'price 1.99)
   (propertize "broccoli"   'category 'vegetable 'type 'cruciferous 'color "green"  'season "year-round" 'price 2.99)
   (propertize "cauliflower" 'category 'vegetable 'type 'cruciferous 'color "white" 'season "fall"       'price 3.99)
   (propertize "tomato"     'category 'vegetable 'type 'nightshade  'color "red"    'season "summer"     'price 2.49)
   (propertize "eggplant"   'category 'vegetable 'type 'nightshade  'color "purple" 'season "summer"     'price 3.49))
  "Produce candidates (fruits + vegetables) for the VOMPECCC walkthrough.")

Thirty propertized strings, two completion categories (fruit, vegetable), ten types (six botanical + four vegetable), and four additional properties. For the rest of this post, produce candidate means one of these propertized strings: a plain Emacs string at the surface (the produce name, if you will) with its remaining fields as text properties.

3.1. Why a propertized string?

The post is going to lean on one specific claim: the candidate is the unit of currency that flows between every layer of the substrate. When we say "every package consumes the same currency without knowing about each other," we mean the propertized string above is what gets used by the built-in Emacs substrate and the VOMPECCC packages. The shape we chose for my-produce is what makes the thesis cash out.

It is worth pausing on, because the same data could plausibly have been a list of plists, an alist of cons cells, a hash table from name to record, or a programmed completion function, etc…. and completing-read accepts all of those as collections. No VOMPECCC package constrains the shape further, so strictly speaking, any shape that produces strings would work. The question is what each shape costs the consumer code, and the answer is relevant to the rest of this post.

1. Properties survive the round trip. When completing-read returns the chosen candidate, it returns the exact propertized string you put in. Properties intact. Your Embark action receives "apple" with all its text properties; your transformer receives "apple" with type pome still attached; the exporter receives the full set. The entire candidate-as-currency story rests on this: domain data and candidate identity are the same object. You hand the framework a propertized string, and you get a propertized string back, and you can read whatever properties you stamped on without ever leaving the candidate.

2. Text properties are the framework's integration channel. Three concrete examples from the packages below:

  • Vertico applies face text properties for display.
  • Consult writes a multi-category text property to thread per-candidate types through Embark's dispatcher.
  • Embark's built-in transformer reads multi-category off the candidate.

These packages were designed assuming candidates are strings with rich text properties. The two framework-owned properties (face and multi-category) coexist on the same string object alongside our category, type, color, season, price, because a propertized string supports arbitrary properties without conflict. An alist or hash-table shape would force you to translate to strings somewhere on the way into the framework, and that breaks the integration, or at least makes it more difficult.

3. No sidecar state. The plausible alternative is plain strings plus a hash table mapping name → record. It works, but introduces:

  • A second piece of state to keep in sync with the candidate list.
  • A (gethash cand my-records) step in every annotator, action, transformer, and exporter. This is 'lookup' or 'rehydration'. Best avoided, and I can only think of this being useful if a candidate has a huge number of mostly unneeded properties, which is rare in practice.
  • A bug class around duplicate names: the hash table can't disambiguate, and the candidate string by itself carries no other identifying information.

Propertized strings collapse this into "the candidate is the record." Each propertize call mints a fresh string object whose properties are bound to that exact instance.

What is not uniquely required. Some things look more constrained than they are.

  • The property name category is not required by the framework. No VOMPECCC package reads our category text property; we discussed this above, and our filter helper and exporter are the only consumers. We could rename it to kind or class and only our own code would change. category was chosen for alignment with Emacs's vocabulary and the completion ecosystem's conventions, not for compatibility.
  • Flat properties vs. one multi-data bag is not required either. This corpus uses individual properties because the records are shallow and the property bag stays small, so every consumer asks one get-text-property question and gets one answer. In codebases like spot, where each candidate is a Spotify track or playlist with dozens of nested fields, the convention is to attach the full record under a single multi-data property and let consumers reach into the plist for deep fields. Both routes meet the substrate at the same hook.
  • Human readability is not required. The candidate string is just an identifier as far as completing-read is concerned. You could put a UUID and have the annotator render the human name as a prefix. Most packages use the string as the visible name because it is simpler, not because they have to.

The shape, in one sentence. A list of propertized strings is the shape that lets every VOMPECCC layer participate without your code translating between a "candidate" representation (what the framework sees) and a "record" representation (what your annotator, action, and transformer need). Every other shape forces that translation somewhere. Candidate-as-currency means: don't translate. Pass the same object end to end.

4. Phase 0: Resetting to Stock   reset setup

If you are reading this post in your own Emacs, your config may already have VOMPECCC packages enabled, which would muddy this demo of built-in completion. Run this block first to peel them off so the baseline really is the baseline.

;; Reset completion-styles and category configuration to Emacs defaults.
(setq completion-styles '(basic partial-completion emacs22))
(setq completion-category-defaults nil)
(setq completion-category-overrides nil)

;; Drop any custom Orderless wiring so dispatchers and matching styles don't leak in.
(when (boundp 'orderless-style-dispatchers)
  (setq orderless-style-dispatchers nil))
(when (boundp 'orderless-component-separator)
  (setq orderless-component-separator " "))
(when (boundp 'orderless-matching-styles)
  (setq orderless-matching-styles '(orderless-literal orderless-regexp)))

;; Disable every VOMPECCC mode the post will switch back on layer by layer.
(when (bound-and-true-p vertico-prescient-mode) (vertico-prescient-mode -1))
(when (bound-and-true-p prescient-persist-mode) (prescient-persist-mode -1))
(when (bound-and-true-p vertico-mode) (vertico-mode -1))
(when (bound-and-true-p marginalia-mode) (marginalia-mode -1))

After this block, completing-read should behave the way Emacs ships out of the box: a *Completions* buffer instead of a vertical list, prefix-only and partial-completion matching, no annotations, no contextual actions, and no 'frecency'. Phase 1 below demonstrates exactly that, and every subsequent phase adds one layer back.

5. Phase 1: The Baseline (Stock completing-read)   baseline

Before we pull in a single VOMPECCC package, it is worth seeing what the built-in Emacs completion already gives us. completing-read is a function in the Emacs standard library: it prompts the user for an input string, filters candidates based on that string, and then returns the chosen string. That is the entire contract.

(completing-read "Pick something: " my-produce)

When this block runs, the minibuffer opens with the prompt Pick something: = and a blinking cursor. Nothing else is visible at first. Press =TAB and a *Completions* buffer pops open above the minibuffer, laying all thirty produce names across columns. Type a prefix like pe and press TAB again; the *Completions* buffer shrinks to the two produce items whose names start with those letters (peach and pear). Once you pick an item (either by arrowing to it in *Completions* or typing its full name), you accept it with RET and the chosen string echoes into the message area.

This works, but it is visually and ergonomically primitive. Right now we are lacking display control for our candidate list, fuzzy matching, metadata display and filtering, preview, and any way of doing anything with the chosen item except receive its name.

Every phase that follows places layers of the VOMPECCC stack around this function without changing the function itself.

Every phase that follows places layers of the VOMPECCC stack around this function without changing the function itself.

6. Phase 2: Vertico — Display Control   vertico display

Vertico only does one thing; it just gives us control over how candidates are displayed in the minibuffer. It does not filter, sort, annotate, or act on anything. Any code that calls completing-read; whether it's yours, Emacs's, a third-party package's; is rendered according Vertico's display settings if it is enabled.

(vertico-mode 1)

That is the entire integration! Re-evaluate the Phase 1 block:

(completing-read "Pick something: " my-produce)

This time the *Completions* buffer never appears. Instead, the minibuffer lays out candidates horizontally (my default display style for Vertico). The prompt still sits at the bottom, but to it's right is a flat array of all thirty produce items, one per line, with the first one (apple) highlighted as the current selection. C-n (or C-j in my config) walks the highlight to the right through pear, strawberry, blueberry, and so on through the list; C-p (or C-k in my config) walks it left. RET accepts whichever candidate is currently highlighted. The candidate list filters incrementally whenever we type a letter: typing a single p on the empty prompt collapses it to the 12 items whose names contains p, and each additional character narrows further in real time, with the selection snapping to the first surviving candidate.

Notice our completing-read function call did not change, and, critically, we did not pass Vertico the candidates. Vertico hooked into the minibuffer-setup pipeline at a lower level, and Emacs routes candidates to it. This is critical because this means Vertico now gives us candidate display control everywhere completing-read is used, without us havign to anything except enable vertico mode!

7. Phase 3: Orderless — Multi-Component Matching   orderless filtering

Vertico gave us a better view, but the matching is still the default combination of basic, partial-completion, and emacs22. These styles handle prefix and hyphen-segmented matches, but we lack a way to type more than one independent fragment, do any flex matching, negation, or any way to filter against candidate metadata.

Orderless is a completion style: it plugs into the completion-styles variable and changes how the input string is matched against candidates during completing-read. Orderless in practice reveals its namesake: it lets us split the input on a separator character, and return candidates that contain every component, in any order.

(setq orderless-component-separator ",")

(setq orderless-matching-styles '(orderless-regexp)
      completion-styles '(orderless basic)
      completion-category-defaults nil
      completion-category-overrides '((file (styles basic partial-completion))))

;; A small `tab' shim style: TAB completion only, no fall-through.
;; In-buffer completion (Corfu, the in-buffer counterpart from Post 2)
;; uses it; minibuffer prompts ignore it and pass straight through to Orderless.
(add-to-list 'completion-styles-alist
             '(tab completion-basic-try-completion ignore
                   "Completion style which provides TAB completion only."))

(setq completion-styles '(tab orderless basic))

Note: The basic fallback matters because a handful of Emacs features (TRAMP host completion, for example) require prefix-style matching that Orderless does not handle; basic catches those.

A few notes on the configuration we set up:

  • orderless-matching-styles is the chain Orderless uses against any input component that no dispatcher has claimed, and setting it to orderless-regexp is my personal recommendation.
  • The file-category override (defined in completion-category-overrides) is an idiomatic exception so that ~/d/s expands to ~/Documents/source/.
  • The separator is set to a comma because Orderless's default is a space, and spaces are valuable inside candidate strings because search candidates (and their annotations) often contain whitespace. Reassigning the separator to a rarely-occurring character (,) keeps spaces available as part of any component you might want to match.
  • The tab style at the head of completion-styles is a one-line concession to in-buffer completion, because Corfu uses it for plain TAB completion in code buffers, but know that the minibuffer prompts ignore it and fall through to Orderless.

Re-run the produce prompt:

(completing-read "Pick something: " my-produce)

You see Orderless in action when you type more than one component separated by commas. Typing a,e narrows the list to every item containing both an a and an e somewhere, in either order (Order-less, remember? 😜). Each comma-separated component is an independent filter, and the intersection (in the set algebra sense) of their matches is what survives in the candidate list display.

Individual components can override the default matching style through Orderless's style dispatchers, which are single-character affixes that tell Orderless to treat that component in a special way. Out of the box, Orderless ships orderless-affix-dispatch as the default dispatcher, mapping ~ to flex, ! to negation, & to annotation matching, , to initialism, and a few others. Two of those defaults are awkward in our setup: , is already serving as our component separator, so it can't double as a dispatcher prefix; and we want the annotation prefix to support a flex variant, which the affix-alist can't express because each entry maps a single character to a single style. Both reasons motivate replacing the defaults with hand-rolled versions tuned to our preferred prefix vocabulary.

We'll use a customized dispatcher set for the rest of this post.

Each dispatcher is a function of three arguments (pattern, index, total) that inspects a component and, if its dispatcher character appears at either end, returns (STYLE . PATTERN-WITHOUT-AFFIX) as so Orderless knows which matching style to apply. Returning nil passes the component to the next dispatcher in the chain, or to the default style if none match. Accepting the character as either prefix or suffix mirrors the built-in orderless-affix-dispatch and means you don't have to remember which end the trigger goes on, or preempt the matching style before you type out a component. ~bna and bna~ both flex-match.

;; Each dispatcher accepts the dispatcher character as either a
;; PREFIX or a SUFFIX of the component, mirroring the built-in
;; `orderless-affix-dispatch'.

(defun my/orderless-dispatcher-initialism (pattern _index _total)
  "Initialism-match a component starting OR ending with a backtick."
  (cond
   ((string-prefix-p "`" pattern)
    `(orderless-initialism . ,(substring pattern 1)))
   ((string-suffix-p "`" pattern)
    `(orderless-initialism . ,(substring pattern 0 -1)))))

(defun flex-if-twiddle (pattern _index _total)
  "Flex-match a component starting OR ending with `~'."
  (cond
   ((string-prefix-p "~" pattern)
    `(orderless-flex . ,(substring pattern 1)))
   ((string-suffix-p "~" pattern)
    `(orderless-flex . ,(substring pattern 0 -1)))))

(defun annotation-if-at (pattern _index _total)
  "Annotation-match `@P' or `P@'.
Flex-annotation-match `@~P', `~P@', `@P~', or `P~@' --- the inner
`~' may sit at either end of the inner pattern."
  (let ((rest
         (cond
          ((string-prefix-p "@" pattern) (substring pattern 1))
          ((string-suffix-p "@" pattern) (substring pattern 0 -1)))))
    (when rest
      (cond
       ((string-prefix-p "~" rest)
        (let ((re (mapconcat (lambda (c) (regexp-quote (char-to-string c)))
                             (string-to-list (substring rest 1)) ".*")))
          `(orderless-annotation . ,re)))
       ((string-suffix-p "~" rest)
        (let ((re (mapconcat (lambda (c) (regexp-quote (char-to-string c)))
                             (string-to-list (substring rest 0 -1)) ".*")))
          `(orderless-annotation . ,re)))
       (t `(orderless-annotation . ,rest))))))

(defun without-if-bang (pattern _index _total)
  "Exclude a literal match for a component starting OR ending with `!'.
A bare `!' is a no-op (matches every candidate)."
  (cond
   ((equal "!" pattern) '(orderless-literal . ""))
   ((string-prefix-p "!" pattern)
    `(orderless-without-literal . ,(substring pattern 1)))
   ((string-suffix-p "!" pattern)
    `(orderless-without-literal . ,(substring pattern 0 -1)))))

;; `annotation-if-at' precedes `flex-if-twiddle' on purpose:
;; with suffix support, a compound like `@PATTERN~' would otherwise
;; fire flex on the trailing `~' before annotation could claim the
;; leading `@'.  Annotation must get first crack.
(setq orderless-style-dispatchers
      '(my/orderless-dispatcher-initialism
        annotation-if-at
        flex-if-twiddle
        without-if-bang))

Four dispatchers, each demonstrated against the candidate list:

Try the prompt again with these style dispatchers in place.

(completing-read "Pick something: " my-produce)

The annotation dispatcher is the most interesting of the four because it shows that some dispatchers compose. The leading @ picks a slot (annotation rather than name); the inner ~ switches the match style on whatever it's already pointing at. Nothing in Orderless's library knows about @~ as a compound prefix. We wrote that composition ourselves, in a few lines (annotation-if-at).

Note we won't be able to see the annotation dispatcher in action until we add annotations in the Marginalia section, so bear with me!

Orderless stays out of the way. The only thing that changed about our completion setup by introducing Orderless is how Emacs matches your input against the candidate list changed, because we effectively swapped in a different completion style through completion-styles.

Vertico didn't need to know about Orderless. Orderless didn't need to know about Vertico. They compose because Emacs routes their concerns through separate hooks.

To drive the point home, what we did not do matters architecturally: Vertico and Orderless are agnostic to one another. They leverage independent Emacs built-ins (completing-read rendering and completion-styles respectively), and they compose because Emacs natively routes their concerns through these separate channels. You can swap in any other minibuffer candidate display UI (Icomplete-vertical, Mct, fido-vertical-mode), or drop it entirely, and Orderless will still work. You can swap in any other completion style and Vertico will still work.

8. Phase 4: Marginalia — Annotations   marginalia annotations

We can filter tightly now, but in spite of the rich metadata attached to each produce candidate, we can't see anything about a candidate except its name. If you're deciding between peach and apricot, relevant information like price, color, season, etc… is already on the candidate's text properties, but Vertico is only showing us the string itself. Marginalia is the package that promotes candidates into informed choices by displaying their metadata as right-aligned columns next to each candidate name.

The trick that makes Marginalia (and every subsequent VOMPECCC package) possible is the convention already established by the list of produce items: the candidate is its name as a string, with its full record's fields stamped on as text properties. completing-read is satisfied because the candidate is a plain string; the rest of the substrate is satisfied because the properties are right there.

A note on scope before we start: plain completing-read can only carry one completion category at a time, and Marginalia dispatches its annotator off that prompt-level category. The corpus has two categories (fruit and vegetable); to keep this phase honest, we narrow the picker to just fruits for now. Phase 5 will lift this restriction with Consult's multi-source mechanism.

That narrowing is one line of Lisp, but worth pausing on. Every later phase (the Consult sources in Phase 5, the async variant in Phase 8) will partition the corpus by category in the same way, so we factor it out once and read the routing key off the candidate itself rather than off of some separate lookup table. This is the candidate-as-currency convention in miniature: a routing question is answered by reading a text property:

(defun my-produce-of-category (cat)
  "Return candidates from `my-produce' whose `category' is CAT."
  (cl-remove-if-not
   (lambda (cand) (eq (get-text-property 0 'category cand) cat))
   my-produce))

We still need to tell the prompt itself what completion category this is, because Marginalia dispatches its annotator off the prompt's metadata, not off per-candidate text properties. The candidate stamping we did in the corpus is for our own code (the actions, the transformer, the exporter) to read; the framework looks at prompt metadata. The lightest way to set the metadata is completion-extra-properties, a property list that overrides the completion metadata for the duration of one completing-read call:

(defun my-pick-fruit ()
  "Pick a fruit by name."
  (interactive)
  (let ((completion-extra-properties '(:category fruit)))
    (message "You picked: %s"
             (completing-read "Fruit: " (my-produce-of-category 'fruit)))))

The foundational pattern — a completion function that responds to (action 'metadata) with (metadata (category . fruit)) — is still available and is what libraries like Consult build on. However, for a one-off picker without Consult, completion-extra-properties is equivalent and cleaner. Why is this even needed? Because plain completing-read over a list of strings has no way to communicate a category to Marginalia: the framework reads the prompt's completion metadata, and our list of strings doesn't ship any. As soon as we move to Consult in Phase 5, the source-level :category key takes over and the extra-properties step disappears entirely, which is exactly why packages like spot never need this dance. Every spot prompt is a Consult prompt. Our Phase 4 picker is the awkward case precisely because it is deliberately the barest possible call, and a demonstration of a Consult-less completion setup for our produce picker.

The annotator itself is the heart of this phase. It takes a candidate string, reads its text properties directly, and hands a list of columns to marginalia--fields, which does the alignment, per-field truncation, and face application:

(defun my-annotate-produce (cand)
  "Annotate a produce candidate CAND with type, color, season, and price."
  (marginalia--fields
   ((symbol-name (get-text-property 0 'type cand))
    :truncate 13 :face 'marginalia-type)
   ((get-text-property 0 'color cand)  :truncate 10 :face 'marginalia-string)
   ((get-text-property 0 'season cand) :truncate 12 :face 'marginalia-date)
   ((format "$%.2f" (get-text-property 0 'price cand))
    :truncate 8  :face 'marginalia-number)))

Note that I am not writing any layout code at all. marginalia--fields handles padding, alignment, and face application; my job is only to declare which fields go in which columns. Annotating the candidates in this way enables Orderless's @ dispatcher to filter by our produce's metadata, so @berry, @citrus, @root become legitimate filter prefixes.

Registration for the fruit category is one add-to-list against Marginalia's annotator registry, plus enabling the marginalia-mode:

(add-to-list 'marginalia-annotators
             '(fruit my-annotate-produce none))
(marginalia-mode 1)

The registry entry is (CATEGORY ANNOTATOR1 ANNOTATOR2 ... none), where each tail symbol is an annotator marginalia-cycle (M-A) can rotate to in-session. We list two states for our category: our custom annotator, then none (annotations off). This matches spot's convention and gives a clean toggle on M-A. Marginalia's own built-in entries also include a builtin symbol in the chain (which is a fallback that defers to whatever annotation-function the prompt's metadata declares natively) but for a category nobody else knows about (like fruit), there is no built-in to defer to, so leaving it out keeps the cycle to two visibly-different states instead of three.

Run the picker:

(my-pick-fruit)

When (my-pick-fruit) runs, the prompt opens as it did before, but every one of the fruit candidates is now followed by a horizontally aligned set of columns: a type word (pome, berry, citrus, …), a color word (red, yellow, blue, green, …), a season (spring, summer, winter, year-round), and a dollar-formatted price ($0.59, $3.99, $6.99).

Stylistically, each column is padded to a fixed width and rendered in its own face, so the four fields read as distinct groups rather than running together with a delimiter. Where Phase 3 showed strawberry, this phase shows:

strawberry berry red spring $3.99

The list is legible at a glance, and what's more, you can usefully compare peach against apricot on price and season without typing anything. Scanning for cheap in-season fruit, for example, is made easy in this way.

Typing against annotation text is where Marginalia crosses from cosmetic to compositional. Typing yellow at the prompt matches nothing, because yellow is in the annotation column, not the candidate name, and Orderless is still matching against names only. But prefix that same component with @, as in @yellow, and the annotation dispatcher we wired up in Phase 3 tells Orderless to match this particular component against the annotation text instead of the candidate string. The list snaps to exactly the three yellow-colored fruits in the corpus.

To drive home the point that the VOMPECCC packages work independently of one another, Orderless knew nothing about Marginalia, and vice-versa. The @ dispatcher simply matches against whatever is in the "annotation slot" of the current candidate, and that slot happens to contain the words Marginalia stamped there.

The compound variant from Phase 3 cashes in here too: @~sm triggers the flex branch of annotation-if-at, flex-matching the characters s and m against annotation text. Only summer contains them in order, so the list collapses to the summer fruits.

Annotation components compose the same way regular components do. Typing @summer,@red on the empty prompt narrows first to summer fruits, then to the subset of those that are also red. The list collapses to raspberry and cherry, the two red summer fruits in the corpus. You can reach for a fruit by its properties without ever remembering its name. Post 2 called this "an unusually large leverage gain for what feels like a cosmetic layer".

The architectural observation here is that the @ dispatcher is not a Marginalia feature. It is an Orderless feature (a dispatcher we wrote) that happens to work because Marginalia exposes annotations through the same completion-metadata slot Orderless reads from. Swap Marginalia for any other annotator (say, a leaner one you write yourself that only shows price) and the @ filter still works. With an alternative annotation provider, Orderless would just filter against whatever that other annotator produces.

8.1. The recommended alternative to Marginalia: inline annotations

The Marginalia readme is explicit on a point worth surfacing before we lock this pattern in. For completion commands you control, the author recommends putting the annotator directly in the completion metadata via affixation-function, not in marginalia-annotators. Marginalia exists primarily to annotate other people's commands, and most of the time this is the built-in Emacs prompts and third-party packages whose authors didn't ship annotations themselves. When you control the call site, as we are here with our fruit picker, you can attach the annotator alongside the category in completion-extra-properties, and that is the recommended default.

The replacement is one function and one extra property. affixation-function receives the list of currently visible candidates and returns (CANDIDATE PREFIX SUFFIX) triples, which Vertico (or any completing-read UI) renders. We have to do our own column padding now: marginalia--fields was the convenience that absorbed it, but the upside of doing things the recommended way is that we eliminate the Marginalia dependency for this prompt:

(defun my-affixation-produce (cands)
  "Return (CAND PREFIX SUFFIX) per candidate, columns padded for alignment."
  (let ((width (apply #'max 0 (mapcar #'length cands))))
    (mapcar
     (lambda (cand)
       (let* ((pad  (make-string (- (1+ width) (length cand)) ?\s))
              (cols (concat
                     (propertize (format "%-13s" (symbol-name (get-text-property 0 'type cand)))
                                 'face 'completions-annotations)
                     (propertize (format "%-10s" (get-text-property 0 'color cand))
                                 'face 'completions-annotations)
                     (propertize (format "%-12s" (get-text-property 0 'season cand))
                                 'face 'completions-annotations)
                     (propertize (format "$%-7.2f" (get-text-property 0 'price cand))
                                 'face 'completions-annotations))))
         (list cand "" (concat pad cols))))
     cands)))

(defun my-pick-fruit-builtin ()
  "Pick a fruit using only built-in annotation machinery."
  (interactive)
  (let ((completion-extra-properties
         (list :category 'fruit
               :affixation-function #'my-affixation-produce)))
    (message "You picked: %s"
             (completing-read "Fruit: " (my-produce-of-category 'fruit)))))

The shape is the same as the Marginalia version (propertized candidates and a property list bound around the call) but with one extra entry. :affixation-function delivers the annotator directly, where the Marginalia version had only :category and let the marginalia-annotators registry lookup pick the annotator.

To prove this is independent of Marginalia, turn the mode off and run the new picker:

(marginalia-mode -1)
(my-pick-fruit-builtin)

The four columns still appear. Vertico renders, Orderless filters, and the annotation slot is filled by my-affixation-produce. @yellow still works, too, because Orderless's annotation dispatcher reads from whatever populates that slot!

This actually strengthens the unix-style nature of the VOMPECCC set rather than diluting it. affixation-function in the completion metadata is an Emacs primitive. Marginalia is one consumer of that slot: a registry that picks an annotator per category and writes it into the slot at completing-read time. Our hand-rolled affixation-function is a different consumer of the same slot, delivering the annotator inline.

Re-enable Marginalia and re-run the original picker:

(marginalia-mode 1)
(my-pick-fruit)

Annotations are back, served by Marginalia's registry-driven version. The two implementations are interchangeable from the prompt's point of view, and only the provenance of the annotator differs. When to reach for which becomes a question of who controls the command. For built-in Emacs prompts and third-party commands you can't edit, Marginalia is your only entry point. For your own commands, the inline affixation-function is closer to the data, has zero dependency, and is the route the Marginalia author recommends.

The rest of this post continues with Marginalia in place. Both routes (the Marginalia registry and the inline affixation-function) can be threaded through the Consult sources in Phase 5. The takeaway from this aside is that the inline route exists, that it is the recommended default for a completion command you fully control, and that it composes with Vertico, Orderless, and the rest of the completion substrate exactly the way the other VOMPECCC packages do.

That being said, I will use the Marginalia registry approach for the rest of this post because you will most often see Marginalia style annotations if you adopt VOMPECCC, and they look nicer with Marginalia's formatting.

9. Phase 5: Consult — Multi-Source with Narrowing   consult sources narrow

Phase 4 was a single prompt over a fruit-only subset, which was enough to demo Marginalia, but it left the corpus's vegetables out and gave us no way to scope the prompt without typing. What if we want one prompt that holds everything (all categories) in my-produce and lets us flip between fruits and vegetables (and back to all of it) with a single keystroke.

Consult fixes this through its multi-source mechanism: consult--multi takes a list of sources, unifies their candidates into a single prompt, and sets up per-source narrowing keys. Each source declares its own name, its own narrow key, its own completion category, and its own items.

The dispatch story in Phase 5 is the same as in Phase 4: the candidate's completion category is whatever the candidate carries. In Phase 4 we read category off the only candidate type the picker held (a fruit). In Phase 5 each source produces candidates of one category (fruit for the fruit source, vegetable for the vegetable source) and the source-level :category matches the value the data already declared on every candidate. The framework reads the value out of the candidate. The spot post uses the same double-stamping pattern, and the per-candidate text property is the authoritative truth, and the source-level :category keeps Consult's metadata in sync.

Each candidate carries five text properties: category (fruit or vegetable), type, color, season, and price, and is read directly off the candidate by Marginalia, Embark, the annotator, and every action we're about to write.

Here we define two Consult sources (1 per category) each with its own scoped :history variable so previously-selected fruits and previously-selected vegetables don't get mixed together in any one history list:

(defvar my-consult-history-fruit nil
  "History scoped to the fruit Consult source.")

(defvar my-consult-history-vegetable nil
  "History scoped to the vegetable Consult source.")

(defvar my-consult-source-fruit
  `(:name     "Fruits"
    :narrow   ?f
    :category fruit
    :history  my-consult-history-fruit
    :items    ,(lambda () (my-produce-of-category 'fruit)))
  "Consult source for fruits.")

(defvar my-consult-source-vegetable
  `(:name     "Vegetables"
    :narrow   ?v
    :category vegetable
    :history  my-consult-history-vegetable
    :items    ,(lambda () (my-produce-of-category 'vegetable)))
  "Consult source for vegetables.")

A Consult source plist's most important keys:

  • :items is the candidate stream. A function that returns a list of candidates, called lazily when the prompt opens. Ours is synchronous because the produce list is local, but for a remote API you would swap :items for :async and consult--dynamic-collection instead. Phase 8 walks through that variant against a faithfully-mocked produce API, and the spot post discusses the consumer-side cost when multiple sources share one endpoint.
  • :narrow ?f binds the character that scopes the prompt to just this source. Pressing f SPC at the prompt hides every non-fruit candidate.
  • :category fruit is the completion category that propagates onto every candidate from this source, not via our category text property but via Consult's own multi-category text property, which Consult writes from this very key. Marginalia and Embark consult that multi-category channel to pick the right annotator and the right keymap per candidate. We set this key to match the data's category property by hand, so the two declarations stay aligned.
  • :history my-consult-history-fruit is a per-source history list. Selecting a candidate from this source appends it to this variable (and only this variable), which means the fruit picker can keep its own history and Vertico's M-p / M-n cycle through a list that is meaningfully scoped instead of polluted with every minibuffer interaction in the session. This per-source scoping is also what makes a per-prompt history-recall command (something like consult-history) useful, which we discuss right after.
  • :name is the header shown above this source's candidates in the unified list.

Two Marginalia registry entries, one per category:

(dolist (cat '(fruit vegetable))
  (add-to-list 'marginalia-annotators
               `(,cat my-annotate-produce none)))

Then the consult command:

(defun consult-produce-picker ()
  "Pick produce with multi-source Consult completion."
  (interactive)
  (consult--multi
   (list my-consult-source-fruit
         my-consult-source-vegetable)
   :prompt "Produce: "))

Run it:

(consult-produce-picker)

When (consult-produce-picker) runs, the prompt opens with the thirty produce items grouped under two bold header lines in the vertical list: Fruits and Vegetables, each followed by the candidates belonging to that source. Every annotation column from Phase 4 is preserved because the annotator is registered against both categories and Consult has tagged each candidate with the right one.

The new affordance by consult is "narrowing", the ability to scope the prompt to a single source with one keystroke. Pressing f followed by SPC hides every non-fruit candidate: the twenty fruits become the entire list, and a small indicator in the prompt header shows the narrow state. Pressing DEL clears the narrow and restores the full thirty-item view. v SPC narrows to vegetables.

The features from earlier phases still apply inside a narrowed view, and this is where the independence of VOMPECCC's constituents really shows. With the list narrowed to fruits, typing @berry further filters to the four berries via the type column we added to the annotator; @red filters to red fruits; ~bry flex-matches the berry suffix of the remaining candidates; !blue excludes anything containing blue. Orderless, Vertico, and Marginalia are all still doing exactly what they did in Phase 4, and they could care less that the candidate source is now two Consult sources instead of one list.

Note we have another classifying property in each candidate. The type column in the annotator means @berry, @citrus, @root, @cruciferous are all valid one-component filters on the prompt, and they compose with the source narrow, so f SPC @citrus gives you exactly the four citrus fruits. The framework keeps type as a searchable axis instead of a narrowing axis.

The amount of code we wrote for the narrowing, grouping, and per-source history is pretty close to zero. I like Consult's declarative API in this way: we declared; Consult did.

9.1. Recalling prior queries with consult-history

Now that fruit selections land in my-consult-history-fruit and vegetable selections land in my-consult-history-vegetable, two further capabilities become available almost for free.

The first is the built-in cycle: M-p and M-n walk through the active prompt's history, and because we scoped per source, those cycles only contain entries you actually picked from this source.

The second is more powerful: consult-history, a Consult command that reads the active history variable, presents its entries in a recursive minibuffer with full Orderless+Vertico+Marginalia goodness, and inserts the chosen entry into the original prompt. The conventional binding is in minibuffer-local-map:

(setq enable-recursive-minibuffers t)
(keymap-set minibuffer-local-map "C-r" #'consult-history)

The enable-recursive-minibuffers setting is a prerequisite: consult-history opens a new minibuffer prompt while another is already active, and Emacs's default is to refuse that. Most VOMPECCC configurations turn it on already.

Demo 1: recall a previous selection. Run (consult-produce-picker) and pick cherry. Run it again, and at the empty prompt press C-r. A second minibuffer opens listing the active source's history; cherry is at the top. Pick it (or flex-match ~chy) and cherry is inserted into the original prompt — RET confirms it.

This is the everyday case: the items you reach for repeatedly are one keystroke and one history-pick away on every subsequent run. Since the history is per-source, narrowing first with f SPC or v SPC scopes C-r to just that source's history.

Demo 2: recall a complex Orderless query. Selections in history are useful, but the bigger win is recalling queries, for example, something like the complex multi-component Orderless filters you spent twenty seconds composing.

This is a behavior of consult history that is not so obvious. The trick is that, by default, completing-read adds whatever it returns to the history variable, and what it returns is the selected candidate. To make the typed query the return value instead, press M-RET (vertico-exit-input) at the prompt. This commits the literal minibuffer contents rather than the highlighted candidate, regardless of whether :require-match is on.

Run the picker, narrow with f SPC, and type summer,!blue,@red. Instead of pressing RET to pick a fruit, press M-RET. The minibuffer closes and the returned string is the literal summer,!blue,@red, and that is what lands in my-consult-history-fruit.

Run the picker again, narrow with f SPC, and hit C-r. summer,!blue,@red is at the top of history. Pick it, and the query is restored in the prompt, ready to be tweaked or RET-confirmed against the same filtered set.

Demo 3: save typed input on every exit. If you'd rather not have to remember M-RET, and you're willing to accept some history pollution, a minibuffer-exit-hook can capture the typed input alongside the selection on every exit:

(defun my-save-typed-input-to-history ()
  "Add minibuffer input to history before exit, alongside the selection."
  (when-let* ((hist minibuffer-history-variable)
              ((symbolp hist))
              ((not (eq hist t)))
              (input (minibuffer-contents-no-properties))
              ((not (string-empty-p input))))
    (add-to-history hist input)))

(add-hook 'minibuffer-exit-hook #'my-save-typed-input-to-history)

With the hook in place, every exit pushes the current contents of the minibuffer to history. RET on raspberry after typing summer,!blue,@red now adds both summer,!blue,@red and raspberry to history. C-r shows both. The cost is that history gets longer, including incomplete queries you abandoned mid-typing. But consider you also have the fully feature ICR experience in consult-history, so in my opinion, this is a non-issue.

Indeed, architecturally, history is one more consumer of the same Emacs primitive every other phase has been consuming. completing-read takes a HIST argument and updates that variable on selection, and Consult's per-source :history key just threads that argument per source. consult-history reads the variable. The minibuffer-exit-hook reads the buffer's typed contents. M-RET changes what gets returned from the prompt and therefore what gets stored.

10. Phase 6: Embark — Actions   embark actions

We can select a produce item, but we still can't do anything with it.

So far the picker's job has ended at returning a string. Embark is the package that adds a layer of category-contextual actions on top of any completion prompt and provides a keyboard-driven context menu whose contents depend on what kind of candidate is highlighted.

It's useful to think of Embark as similar to a left-click or 'context menu' in traditional UIs.

We'll define the minimum set of actions needed to demonstrate Embark's three headline features:

  1. embark-act on a single candidate (the basic case): an inspect action that pops a buffer with the item's full plist.
  2. embark-act-all on every visible candidate (multi-cardinality): an add-to-cart action that appends to a *Cart* buffer, which we will invoke against every currently visible item at once.
  3. Per-category keymap dispatch (context-sensitivity): vegetables get a roast action that fruits don't; citrus fruits get a juice action that other fruits don't. These are specializations side by side, by two different mechanisms in Embark: the vegetable specialization rides on the Consult source's :category key (forwarded to Embark via Consult's multi-category channel), while the citrus specialization rides on an Embark transformer we write that reads the candidate's type property.

10.1. The Actions

Each action is a regular interactive command that takes the candidate string as its argument. Embark passes the current candidate when the user triggers the action.

(defun my-inspect-produce (cand)
  "Pop a buffer showing the text properties carried by produce CAND."
  (interactive "sProduce: ")
  (let ((props (text-properties-at 0 cand))
        (buf (get-buffer-create "*Produce Inspect*")))
    (with-current-buffer buf
      (erase-buffer)
      (insert (format ";; %s\n" cand))
      (pp props (current-buffer))
      (goto-char (point-min))
      (emacs-lisp-mode))
    (display-buffer buf)))

(defun my-add-to-cart (cand)
  "Append produce CAND to the *Cart* buffer."
  (interactive "sProduce: ")
  (let ((price (get-text-property 0 'price cand)))
    (with-current-buffer (get-buffer-create "*Cart*")
      (goto-char (point-max))
      (insert (format "- %-12s ($%.2f)\n" cand price)))
    (message "Added %s to cart" cand)))

(defun my-make-juice (cand)
  "Juice the citrus fruit CAND."
  (interactive "sFruit: ")
  (message "🍹 Juicing %s!" cand))

(defun my-roast-vegetable (cand)
  "Roast the vegetable CAND."
  (interactive "sVegetable: ")
  (message "🔥 Roasting %s!" cand))

Each action reads whatever properties it needs off the candidate, and then does something domain-specific. No Embark machinery leaks into the action body, and Embark's only job is to invoke the action with the right candidate.

10.2. The Keymaps

Embark actions are bound in per-category keymaps. We define one general map per top-level category (one for fruits, one for vegetables), and then we define a specialized citrus map that inherits from the fruit map and adds j (for juice 🍹):

(defvar-keymap my-fruit-general-map
  :parent embark-general-map
  :doc "Embark actions applicable to any fruit."
  "i" #'my-inspect-produce
  "a" #'my-add-to-cart)

(defvar-keymap my-vegetable-general-map
  :parent embark-general-map
  :doc "Embark actions applicable to any vegetable."
  "i" #'my-inspect-produce
  "a" #'my-add-to-cart
  "r" #'my-roast-vegetable)

(defvar-keymap my-fruit-citrus-map
  :parent my-fruit-general-map
  :doc "Embark actions for citrus (fruit actions + juicing)."
  "j" #'my-make-juice)

:parent embark-general-map gives us the built-in defaults (copy-as-kill, describe, insert, etc.) on top of our domain-specific actions. :parent my-fruit-general-map on the citrus map means citrus candidates inherit i and a from the fruit general map and add j on top.

10.3. The transformer: refining fruit to fruit-citrus

We have two dispatch problems in this phase, and they look symmetric on the surface:

  1. Fruit vs. vegetable. Each candidate already knows whether it is a fruit or a vegetable — the source declared it, and Consult propagated that source-level :category onto each candidate via the multi-category text property. We want Embark to pick my-fruit-general-map for fruits and my-vegetable-general-map for vegetables.
  2. Citrus refinement. Within fruits, we want citrus candidates to additionally get the j juice action via my-fruit-citrus-map. This is a refinement below the source category.

The corpus already keeps two clean axes for these: category for top-level kind (fruit or vegetable) and type for fine-grained classification (pome, berry, citrus, …). We don't want to declare citrus as a third completion category — that would collapse the two axes back into one, force the data's category field to mean both "fruit-vs-vegetable" and "this particular kind of fruit", and the next refinement (say, expensive citrus) would force yet another category symbol. The right tool is Embark's transformer mechanism: keep the framework's category vocabulary flat (just fruit and vegetable), and specialize below that vocabulary by reading additional fields off the candidate.

A transformer is a function attached to a category in embark-transformer-alist. When Embark is about to dispatch on a candidate, it looks up the prompt's category in that alist; if it finds a transformer, it calls the transformer once with the original type and target. The transformer returns a refined (TYPE . TARGET) cons, and Embark dispatches on that refined type.

Now the load-bearing detail: Embark applies the transformer exactly once. Look at embark.el's dispatch path and you'll see (if-let (transform (alist-get type embark-transformer-alist)) ...) — a single if-let, no recursion, no chain. The transformer for the original prompt type fires, returns a refined type, and Embark dispatches on that refined type without consulting the alist a second time.

This is consequential when Consult is in the picture. consult--multi sets the prompt's completion-metadata category to multi-category (not fruit or vegetable) and stamps each candidate with a multi-category text property holding (SOURCE-CATEGORY . CAND). Embark ships a built-in transformer registered on multi-category (embark--refine-multi-category) that extracts the source category from that property and returns (fruit . CAND) or (vegetable . CAND). But that's the only transformer Embark will run. If we naively wrote a fruit transformer and registered it on fruit, it would never fire in our prompt, because the prompt's category is multi-category and Embark only consults the alist once, against that original key.

So in a multi-category prompt, the multi-category transformer is the only integration point for any refinement below the source level. We replace Embark's built-in with our own that does both jobs in a single pass: the same source-category extraction (delegated to the built-in helper), plus our citrus refinement on top.

(defun my-multi-category-transformer (type cand)
  "Refine `multi-category' candidates with citrus refinement layered on.
Embark applies the prompt-category transformer exactly once, so any
per-source refinement must happen inside this single function.  We
delegate to Embark's built-in `embark--refine-multi-category' for the
source-category extraction, then refine `fruit' to `fruit-citrus' when
the candidate's `type' property is `citrus'."
  (let ((refined (embark--refine-multi-category type cand)))
    (if (and (eq (car refined) 'fruit)
             (eq (get-text-property 0 'type cand) 'citrus))
        (cons 'fruit-citrus (cdr refined))
      refined)))

(setf (alist-get 'multi-category embark-transformer-alist)
      #'my-multi-category-transformer)

Then we register one keymap per target type. Every fruit gets the general fruit map, citrus gets the specialized one (via our transformer's refinement), and every vegetable gets the vegetable map:

(add-to-list 'embark-keymap-alist '(fruit        . my-fruit-general-map))
(add-to-list 'embark-keymap-alist '(fruit-citrus . my-fruit-citrus-map))
(add-to-list 'embark-keymap-alist '(vegetable    . my-vegetable-general-map))

What about embark-act-all over a candidate set that spans both sources? When all candidates share a refined type (after our transformer runs), Embark dispatches on that unified type — so a fruits-only narrowing dispatches on fruit, and a vegetables-only narrowing dispatches on vegetable. When the set spans sources (e.g., a cross-cutting @summer filter), Embark falls back to the original prompt category, multi-category. We register a fallback keymap so cross-source embark-act-all still has i and a available:

(add-to-list 'embark-keymap-alist '(multi-category . my-fruit-general-map))

This is the integration, end to end. consult--multi sets the prompt category to multi-category and stamps each candidate with a multi-category text property holding the source category. Embark looks up multi-category in embark-transformer-alist and runs our transformer, which extracts the source category (via the built-in helper), then refines fruit to fruit-citrus when the candidate's type property is citrus. Embark dispatches on the refined type, finds the matching keymap in embark-keymap-alist, and opens it. One transformer, three keymap entries plus a fallback — all riding on the per-source :category we declared in Phase 5 and the type field we put on every candidate at the top of the post.

10.4. The Demo

Assuming you have embark-act bound somewhere (the canonical binding is C-.; if you don't, evaluate (keymap-global-set "C-." #'embark-act) first):

(consult-produce-picker)

Let's start by acting on a single candidate. Run the picker, narrow or scroll until apple is highlighted, and press C-.. A small keymap-hint popup appears, listing the action keys available for this candidate: i for inspect, a for add-to-cart, plus everything embark-general-map inherits. Pressing i closes the popup, closes the minibuffer, and pops open a new buffer called *Produce Inspect* showing the apple's text properties pretty-printed: a comment line ;; apple followed by (category fruit type pome color "red" season "fall" price 1.29). Embark invoked my-inspect-produce with the candidate string; our action grabbed the candidate's text-property plist via text-properties-at and handed it to pp.

Now clear the prompt and scroll to lemon. Press C-.. The menu is different this time! i and a are still there, but there is a third entry: j juice. Inside our multi-category transformer, the source-category extraction returns fruit, and then the citrus check sees (get-text-property 0 'type cand) = citrus and refines the dispatch type to fruit-citrus. Embark looks up fruit-citrus in the keymap alist, finds my-fruit-citrus-map (which inherits the i and a bindings from the general map and adds j on top), and presents it after embark-act. Press j and 🍹 Juicing lemon! flashes into the echo area.

Try embark-act on strawberry. The popup is back to just i and a, with no j in sight. A strawberry's type is berry, so the citrus branch of our transformer doesn't fire, the refined type stays fruit, and Embark picks my-fruit-general-map. Now try carrot: the popup is i a r. r roast is there because carrot came from the vegetable source — our transformer extracts vegetable from multi-category, the citrus branch is irrelevant (the source category is not fruit), and Embark dispatches on vegetable.

For the multi-candidate case, start the picker again and narrow to fruits with f SPC, then type @summer to filter to summer fruits within fruits. The list collapses to the summer fruits. Press embark-act-all (the canonical binding is A from the embark-act popup, or C-u C-. directly). Embark prompts for a single action to apply to every visible candidate; press a for add-to-cart. In one keystroke, all ten summer fruits are appended to the *Cart* buffer, one per line, each with its price. You can also select individual candidates with embark-select (which I have bound to C-SPC).

Now clear the narrow and try @summer across the full produce corpus. Hit embark-act-all, press a for add-to-cart. All thirteen go into *Cart*. This is where the multi-category fallback we registered earlier comes in handy, because the candidate set spans both sources, embark dispatches against the multi-category type, and the fallback gives us i and a (the actions defined on every produce item). Type-specific actions like j and r aren't available in this state, which makes logical sense, because a single keystroke can't simultaneously juice a citrus and roast a vegetable.

The same text properties that Marginalia was reading in Phase 4 are what the action functions and the transformer are reading now. Three different packages are cooperating, on three different hooks, off the same candidate, without ever knowing about each other.

10.5. Collect and export

So far the picker has been a selection surface: pick something and act on it.

Embark provides two further commands that promote the candidate list itself into a first-class buffer.

embark-collect snapshots the current candidate set into a fresh *Embark Collect* buffer with the original keymap dispatch still attached to each row. Run the picker, type @summer to narrow to summer items across both sources, then M-x embark-collect (or invoke embark-act with a prefix arg and pick embark-collect). The thirteen summer items land in a buffer that outlives the minibuffer; press embark-act on cherry and the fruit map dispatches; press embark-act on tomato and the vegetable map dispatches. No registration is needed; embark-collect works on any candidate set automatically.

embark-export goes one step further. Where collect produces a generic buffer of strings, export materializes the candidates into the major mode appropriate to their type. File candidates become a Dired buffer; buffer candidates become an Ibuffer; grep results become a wgrep-editable buffer; etc…. For our produce, the natural target is Emacs's built-in tabulated-list-mode.

We register a per-category exporter the same way we registered keymaps in The Keymaps. The exporter takes the list of candidate strings and produces the target buffer:

(defun my-export-produce-tabulated (candidates)
  "Export produce CANDIDATES into a tabulated-list buffer."
  (let ((buf (get-buffer-create "*Produce*")))
    (with-current-buffer buf
      (tabulated-list-mode)
      (setq tabulated-list-format
            [("Name"   14 t)
             ("Cat"     10 t)
             ("Type"   13 t)
             ("Color"  10 t)
             ("Season" 12 t)
             ("Price"   8 t)])
      (setq tabulated-list-entries
            (mapcar
             (lambda (cand)
               (list cand
                     (vector (substring-no-properties cand)
                             (symbol-name (get-text-property 0 'category cand))
                             (symbol-name (get-text-property 0 'type cand))
                             (get-text-property 0 'color cand)
                             (get-text-property 0 'season cand)
                             (format "$%.2f" (get-text-property 0 'price cand)))))
             candidates))
      (tabulated-list-init-header)
      (tabulated-list-print))
    (pop-to-buffer buf)))

(dolist (cat '(fruit fruit-citrus vegetable multi-category))
  (add-to-list 'embark-exporters-alist
               (cons cat #'my-export-produce-tabulated)))

The exporter reaches for the same text properties that the annotator and the actions read from. Once again, the candidate-as-currency convention is being leveraged.

The four registrations are worth a moment. embark-export unifies the candidate set's type by calling our transformer on every candidate and checking that they all produce the same refined type ((cl-every ...) in embark--maybe-transform-candidates). When all candidates are vegetables, our transformer returns vegetable for each, unification succeeds, and Embark dispatches on vegetable. Same for an all-citrus filter (f SPC @citrus → all fruit-citrus) or an all-non-citrus filter. But the obvious case (narrow to fruits with f SPC) gives a mix of citrus and non-citrus, which our transformer refines to two different types (fruit-citrus and fruit). Unification fails, and Embark falls back to the prompt's original category: multi-category. Registering our exporter on multi-category as well makes the export work in that mixed-but-still-produce case.

A trade-off worth being honest about: that multi-category registration is shared with every other consult--multi prompt in the session. In a real package, you'd want to guard the exporter against non-produce candidates, or scope the registration more tightly. For this walkthrough we accept the broad registration and move on.

Run the picker, narrow to fruits with f SPC, then M-x embark-export (or embark-act with prefix arg, then E). A *Produce* buffer pops open in tabulated-list mode:

(consult-produce-picker)
Name           Cat       Type          Color      Season       Price
apple          fruit     pome          red        fall        $1.29
pear           fruit     pome          green      fall        $1.79
strawberry     fruit     berry         red        spring      $3.99
blueberry      fruit     berry         blue       summer      $4.99
raspberry      fruit     berry         red        summer      $5.99
...

Every column is sortable from its header (the trailing t flag in tabulated-list-format turns sortability on per-column); n and p move between rows. Since each row's tabulated-list-id is the original propertized candidate, embark-act on a row still dispatches to the right keymap based on category, including the citrus juicer via the transformer. embark-act on the cherry row offers i a (general fruit); embark-act on lemon offers i a j (citrus, refined by the transformer); embark-act on a row exported from a vegetable narrowing would offer i a r (vegetable). Nothing changes about the per-candidate dispatch just because the candidates moved into a major mode, which is very convenient! This is one of my favourite things about Embark. It works on minibuffer candidates in the all-too-common ICR setting, but it also works on any arbitrary piece of text in Emacs. With Embark, literally everything in Emacs becomes left-clickable in this way.

Cross-cutting filters (e.g., @summer spanning fruits and vegetables) also fall back to multi-category for the same reason (the candidates' refined types differ) so the same registration carries them through. embark-collect remains available as a finer-grained alternative when you want a generic candidate-set buffer rather than a tabulated table.

The architectural take: embark-export is not a Consult feature, not a Marginalia feature, not part of any pipeline our other phases set up. It is an Embark feature that works with any completing-read-driven prompt that has a category and a registered exporter. And the exporter we wrote is one more consumer of the candidate's text properties alongside the annotator (Phase 4), the action functions (this phase), the transformer (this phase), and the per-category keymap dispatch (also this phase). If you ever decide a tabulated list isn't the right export target (maybe a csv-mode or org-mode would be better) swap the exporter function and every other layer of the picker stays exactly where it is.

11. Phase 7: Prescient — Recency and Frequency Sorting   prescient sorting

The last concern from the orthogonal six is sorting.

So far, sorting has been incidental. Vertico ships a default sort that draws on minibuffer history, but it ignores frequency entirely and resets when Emacs restarts. Prescient is the dedicated package for this concern: it computes a combined frecency score (recency + frequency) per candidate and hands it to Vertico as the sort function. With prescient-persist-mode the score survives Emacs restarts; even without it, the score updates from the very first selection.

That last point is what makes Prescient demonstrable in a from-scratch walkthrough. The score for a never-selected candidate is zero. Pick a candidate once and its score jumps above zero, putting it at the top of the next prompt. One selection is enough to see the effect.

One wrinkle to acknowledge before the setup. Of the six VOMPECCC packages we are using, Prescient is the only one that bundles two concerns: sorting (the part we want here) and its own filtering style. vertico-prescient-mode toggles both by default — which would replace the Orderless setup from Phase 3 and silently break the custom dispatchers (~, !, backtick, @) we have been relying on for the last four phases. Post 2 flags this same two-concern split and notes the common workaround: keep Orderless for filtering, take Prescient's sort, and disable the filter takeover. That is what we do here:

(setq vertico-prescient-enable-filtering nil)
(vertico-prescient-mode 1)

This is the entire prescient integration. (Adding (prescient-persist-mode 1) would persist scores across Emacs sessions; we leave it off here so the demo's state stays bounded to this single Emacs session.)

This demo is simple. Run the picker once, select an item, then re-run. The item we just picked will be the first in the list.

(consult-produce-picker)

All five layers continue untouched, while a sixth package quietly improves the order in which their candidates land on screen.

12. Phase 8: Async — Going Remote with consult--dynamic-collection   async consult remote

The picker we have works because the entire produce corpus is local: my-produce is a literal thirty-item list, and each Consult source's :items callback returns its slice synchronously. Real-world sources are usually remote, and the spot post demonstrates this w/r/t the Spotify API. Filtering at the Emacs side stops being viable once the corpus has thousands or millions of entries, and you have to push the query to the server and let the server tell you which candidates match.

Consult ships consult--dynamic-collection for exactly this case. It wraps a completion function so that each keystroke fires a debounced, cancellable query. The function you write only has to answer "given this query string, what are the candidates?" Consult handles the debouncing (default ~200ms via consult-async-input-debounce), the cancellation of stale responses (via while-no-input), and the display refresh.

12.1. Mocking the API

We don't have a real produce service to hit, but we can mock one faithfully enough to demonstrate the pattern. Real search backends (Spotify, GitHub, Algolia, Slack, …) almost always do something fuzzier than literal substring matching, so the mock matches that style. Each character of the query must appear in the candidate's name in order, possibly with gaps in between, case-insensitively. The mock takes a query string, sleeps 😴 to simulate network round-trip, and returns the matches:

(defvar my-mock-api-latency 0.1
  "Simulated API latency in seconds.  Set to 0 to disable the sleep.")

(defvar my-mock-api-call-count 0
  "Counter so we can watch the mock API being hit.")

(defun my-mock-fuzzy-match-p (query name)
  "Return non-nil if NAME flex-matches QUERY (chars in order, case-insensitive)."
  (let ((case-fold-search t)
        (re (mapconcat (lambda (c) (regexp-quote (char-to-string c)))
                       (string-to-list query)
                       ".*")))
    (string-match-p re name)))

(defun my-mock-produce-api (query)
  "Pretend to fetch produce from a remote API matching QUERY.
Sleeps for `my-mock-api-latency' seconds to simulate network round-trip,
then returns the subset of `my-produce' whose names fuzzy-match QUERY."
  (cl-incf my-mock-api-call-count)
  (when (> my-mock-api-latency 0)
    (sleep-for my-mock-api-latency))
  (cl-remove-if-not
   (lambda (cand) (my-mock-fuzzy-match-p query cand))
   my-produce))

A real client would issue an HTTP request, parse JSON, and propertize the response. Ours is a fuzzy regex match over the corpus, gated by sleep-for so latency is observable. The call counter lets us watch the API being hit (or not) as we type.

12.2. A single async source

The completion function takes a query and returns a list of propertized candidates, the same text properties as the synchronous version (category, type, color, season, price all carried directly on each candidate). Only the source has changed. Because my-produce is already a list of propertized strings, the completion function reduces to a category filter on whatever the API returns. We close over cat so each completion category gets its own completion function:

(defun my-async-produce-candidates (cat)
  "Return a completion function that yields CAT-category produce matching QUERY."
  (lambda (query)
    (cl-remove-if-not
     (lambda (cand) (eq (get-text-property 0 'category cand) cat))
     (my-mock-produce-api query))))

The Consult source uses :async rather than :items, wrapping the completion function with consult--dynamic-collection:

(defvar my-consult-history-fruit-async nil)

(defvar my-consult-source-fruit-async
  `(:name     "Fruits (async)"
    :narrow   ?f
    :category fruit
    :async    ,(consult--dynamic-collection
                (my-async-produce-candidates 'fruit)
                :min-input 1)
    :history  my-consult-history-fruit-async)
  "Async Consult source for fruits.")

(defun my-async-produce-picker ()
  "Pick a fruit using an async Consult source."
  (interactive)
  (consult--multi (list my-consult-source-fruit-async)
                  :prompt "Fruit: "))

Run it:

(my-async-produce-picker)

The prompt opens empty; :min-input 1 keeps Consult from firing until you type at least one character. Type b and you will see a moment of latency, and then five fruits whose names contain b (the four berries and banana). Type e more (be), and you will see another fetch, the four berries appear because each one has a b followed by an e somewhere in its name, while banana has no e and drops out.

Empty the prompt and type rapidly: papaya, holding no key for long. Consult's debouncer drops intermediate queries, and in a resource-sparing manner, only the queries you paused on reach the API. When a slow query is mid-flight and your input changes, while-no-input aborts the in-flight computation and restarts on the new input, so stale results never make it to the candidate list. The completion function we wrote is plain synchronous Lisp; all the cancellation, debouncing, and refresh logic are consult--dynamic-collection's problem, not ours.

A small architectural symmetry worth flagging. For Consult's "async command" family of commands (consult-grep, consult-ripgrep, consult-find, and friends) input is additionally split between a backend portion and a local-filter portion via consult-async-split-style. Setting it to 'comma aligns the split character with our orderless-component-separator, so the same , delimits "send this to the server" and "filter the result locally with Orderless." consult--dynamic-collection (the lighter wrapper we used here) skips the split and forwards the whole input to the completion function, but the harmonisation is right there for prompts that want both.

13. The Payoff: Candidates as Shared Currency   substrate

Step back and count what each package contributed:

Package What it gave us Lines we wrote
Vertico Display control of candidates 1
Orderless Multi-component matching, flex, negation, annotation filter dispatch ~3
Marginalia Four right-aligned metadata columns + annotation-aware matching ~12
Consult Two sources, unified prompt, per-source narrow keys ~20
Embark Per-category action keymaps + transformer for citrus refinement + tabular export ~50
Prescient Frecency sort that promotes recently/frequently picked candidates 2

Roughly 90 lines of VOMPECCC-facing emacs-lisp, and we have a categorised, narrowable, annotated, annotation-matchable, action-bindable, transformer-refined, exportable, frecency-sorted produce picker (that's a mouthful!). There is no UI code anywhere in it! Every line fell into one of two buckets: configuring a VOMPECCC package (add-to-list, setq, defvar-keymap) or declaring our data (the propertized corpus, the annotator, the action bodies, the transformer).

The how of the composition, the question Post 2 gestured at and Post 3 answered for a larger application, is a single convention repeated across the six packages. Each candidate is its name as a string, with its full record's fields stamped on as text properties:

(propertize "apple"
            'category 'fruit       ; our internal routing key (filter, exporter)
            'type     'pome        ; read by our Embark transformer
            'color    "red"        ; annotation column
            'season   "fall"       ; annotation column
            'price    1.29)        ; annotation column

That is the entire integration surface. The exporter and our filter helper read the candidate's category property directly; the annotator reads type, color, season, price; the Embark transformer reads type. Vertico, Orderless, and Prescient do their work on the string itself. Marginalia and Embark dispatch off the prompt's completion-metadata category, which Consult populates from each source's :category key (in turn set to match the data's category property by hand — the candidate-as-currency convention paying its dues by keeping our routing key and Consult's source key in lockstep). When Embark wants to dispatch below the granularity of category, e.g.\ with citrus-only juicing inside the broader fruit category, our transformer reads type off the candidate and refines accordingly. These six packages communicate interoperably through Emacs's completion substrate, with one shared currency: the propertized candidate.

A note on shape. We carry the produce fields as individual text properties because the records are shallow and the property bag stays small. Codebases like spot, where each candidate is a Spotify track or playlist with dozens of nested fields, take a different approach and attach the entire record under a single multi-data property and let consumers reach into the plist for deep fields. Both shapes meet the substrate at the same hook (a propertized candidate whose properties happen to encode a typed record) and the choice between them is purely local to the corpus.

Replace my-produce with GitHub issues, S3 buckets, Slack channels, ten thousand Org headings, a music library, or your ticketing system. The integration surface is unchanged. The corpus reshapes; the annotator reads different fields; the Consult sources partition differently; the Embark keymaps and transformer do different things. Critically, none of the framework code moves. This is the architecture behind spot, consult-gh, consult-notes, consult-omni, and the growing ecosystem of ICR-driven packages that can be assembled from the same substrate without rebuilding any of its layers.

14. What We Didn't Cover   further

A handful of VOMPECCC features are worth pointing at, because you will want them eventually and they are all continuations of the same substrate pattern you just saw:

  • Consult preview. A source's :state key accepts a function that fires on candidate navigation, not just selection. consult-line uses it to scroll the current buffer to the highlighted line; consult-theme applies the theme as you scroll. For a produce picker, :state could pop a "tasting notes" buffer that updates as you move through the list.
  • More Embark transformers. Phase 6 used a transformer to refine fruitfruit-citrus based on type. Transformers can refine on any predicate over the candidate's properties — e.g., turn every produce item into produce-expensive if price > 3.00, so you can layer arbitrary fine-grained dispatch on top of the coarse category without inventing new completion categories. There is a huge amount of power there, especially when you understand that Embark's utility expands greatly when you are willing to start typing entities in Emacs, whether they are in the minibuffer or regular buffers.
  • prescient-persist-mode. Phase 7 enabled vertico-prescient-mode without persistence to keep the demo bounded; in a real configuration, (prescient-persist-mode 1) writes the frecency table to disk so your most-picked candidates stay at the top across Emacs restarts.
  • Corfu and Cape. In-buffer completion, out of scope for a picker. If you have a programming-language buffer open and you want to complete symbols with the same matching style and annotation columns, Corfu and Cape are how.

Each of these is an incremental add on the same foundational Emacs completion substrate.

15. TLDR   tldr

This post built a 30-item produce picker in Emacs by layering six VOMPECCC packages one at a time on top of stock completing-read. Vertico rendered the candidate list vertically with a one-line mode activation; Orderless added multi-component matching via the completion-styles hook plus four custom style dispatchers (~ flex, backtick initialism, ! negation, @ annotation with a @~ flex variant), each accepting its trigger character at either prefix or suffix position; Marginalia added four metadata columns per candidate (type, color, season, price) by reading text properties attached to each candidate string with propertize, and along the way made the annotation text itself searchable through Orderless's @ dispatcher — including the type column, so @berry and @citrus become valid one-component filters; Consult's consult--multi unified the corpus into two categorised sources (fruit, vegetable) under one prompt with per-source narrow keys and per-source :history variables, with each source's :category matching the value the data declares, and consult-history bound to C-r turning those scoped histories into a recall surface for both prior selections and (via M-RET or a minibuffer-exit-hook) prior Orderless queries; Embark attached contextual actions via per-category keymaps (one for fruits, one for vegetables, one specialized for citrus) plus an Embark transformer registered on multi-category that does both jobs in a single pass — because Embark fires the prompt-category transformer exactly once, the multi-category transformer is the one place to put both source-category extraction (delegating to the built-in helper) and our citrus refinement (reading type off the candidate and refining fruit to fruit-citrus) — and an embark-export path that materialises candidates into a sortable tabulated-list-mode buffer; Prescient then hooked Vertico's sort slot to surface recently and frequently picked candidates first, with the rank visibly shifting after a single selection; finally an async variant swapped :items for consult--dynamic-collection over a faithfully-mocked produce API, demonstrating debounced/cancellable remote queries and discussing when the spot-style cache+mutex pattern is load-bearing (multiple sources sharing one endpoint) versus defensive ceremony (single thread, single source). The entire integration surface across the six packages was one propertized candidate carrying its fields as text properties — category as our internal routing key, kept aligned with each Consult source's :category so the framework's own multi-category channel matches; type read by our Embark transformer; the rest for annotation columns — no package calls another's API, none imports another's internals, and swapping my-produce for any other typed corpus would leave every line of the framework configuration unchanged.

-1:-- VOMPECCC from Scratch: Picking Produce with ICR in Emacs (Post Charlie Holland)--L0--C0--2026-04-28T09:14:00.000Z

Dave Pearson: blogmore.el v4.3.0

After adding the email comment invite facility to BlogMore it only made sense that I add some commands to blogmore.el to make it easier to edit the front matter that can help drive that feature.

So... I've released v4.3.0 of blogmore.el that adds two new commands:

  • blogmore-toggle-invite-comments -- toggles the comment invitation property
  • blogmore-invite-comments-to -- makes it easy to set, edit or remove the email address to use when making the invite

I've also added the two commands to the transient menu, using C-t for the former and C-a for the latter.

-1:-- blogmore.el v4.3.0 (Post Dave Pearson)--L0--C0--2026-04-28T07:30:43.000Z

Bicycle for Your Mind: MarkEdit Conquers Markdown

MarkEditMarkEdit

Product: MarkEdit
Price: Free

If you read my blog you know that I am an Emacs user. I write Markdown in Emacs. I write everything in Emacs. But I have been curious about MarkEdit for a while and decided to check it out.

This is a free and open-source Markdown based text editor. The developer describes it thus “It’s just like TextEdit on Mac but dedicated to Markdown.”

This is what the developer says about what makes MarkEdit different:

- Privacy-focused: doesn't collect any user data
- Native: clean and intuitive, feels right at home on Mac
- Fast: edits 10 MB files easily
- Lightweight: installer size is about 4 MB
- Extensible: seamless integration with Shortcuts and AppleScript

MarkEdit delivers on its promises. It is the second best Markdown based text editor in the marketplace. Of course, the best is iA Writer. By virtue of being free, MarkEdit might be better value than any of the competition, including iA Writer.

I am impressed with MarkEdit. It does everything you expect from a product in this category and does it well.

Preferences

Unlike iA Writer, MarkEdit gives you options. It has a set of preferences designed to make it the editor you want to use.

pref1pref1

MarkEdit lets you choose your own fonts. iA Writer doesn’t.

MarkEdit has themes. iA Writer has only light and dark options.

pref2pref2

MarkEdit has completions you can customize them. iA Writer does completions but you cannot customize them. They are the usual system completions.

pref3pref3

You can set your own preferences for the extension for your Markdown files. You can do that in iA Writer by typing it at the point of saving the file. It is not an application wide preference.

On the whole, MarkEdit’s preferences are well-designed and well-implemented with all the things you need to make it conform to your idea of an ideal Markdown based text editor. iA Writer has a different approach. It makes the choices for you with the assumption that you are better off concentrating on the words that you are going to write in the program. They take care of how the program needs to work. Both of these approaches work, and both programs are fantastic editors.

Keyboard Commands

keyboard commandskeyboard commands

I hate going to the mouse when I am writing. MarkEdit saves me from that with its support for keyboard commands. They are extensive and make MarkEdit an absolute pleasure to use.

Extensions

MarkEdit has extensions and user-designed themes. They are available here: Extensions · MarkEdit-app/MarkEdit Wiki.

MarkEdit implements an API, so developers can write extensions to extend the features of MarkEdit. It even has an extension to implement Vim keybindings.

I like the Markdown Table Editor extension.

The difference of the philosophy behind the development of iA Writer and MarkEdit is clear from the presence of the API and its effect.

Usage

iA Writer does a lot more than MarkEdit when it comes to things like managing your file-system. MarkEdit makes no attempt to do that. It is a single-file application. Or more correctly, thanks to the tab implementation, it is a file based application. It doesn’t know anything about folders and the management of that.

I love iA Writer. I have been using it for a long time. Since version 1 of the program actually. It is a charming little editor which I enjoy using. MarkEdit surprised me. It is a well-designed text editor which handles all of the Markdown tasks you can imagine. And it is free. It is extendable, customizable, and efficient for writing in Markdown.

My only gripe? Full-screen mode in MarkEdit sucks. The document cannot cover the whole screen. No one should be made to write like that. The document window should be a window in the middle with the line lengths the same as the regular window, in full-screen mode. For a program which is so well designed, this omission was a surprise.

To make up, MarkEdit comes with a good manual at Manual · MarkEdit-app/MarkEdit Wiki.

Conclusion

If you are looking for a great text editor which handles Markdown you cannot go wrong by choosing MarkEdit.

macosxguru at the gmail thingie.

-1:-- MarkEdit Conquers Markdown (Post Bicycle for Your Mind)--L0--C0--2026-04-28T07:00:00.000Z

Irreal: The Sunk Cost Of Emacs

Randy Ridenour has an interesting post on Emacs and sunk costs. His post is in reaction to the idea that using Emacs requires a lot of learning and customization and that those efforts are sunk costs. That is, they’re costs that you’ve already paid and will never get back. All of that is true and Ridenour agrees.

What he doesn’t agree with is the conflation of “sunk cost” and “the sunk cost fallacy”. The sunk cost fallacy is, roughly, the idea that you should continue with some activity simply because you have so much invested—so much sunk cost—in the activity. Everyone has experienced it. Ridenour gives a common example that we’ve all dealt with: the refusal to hang up while waiting to talk to a representative on a support call because we’ve got so much time already invested.

The proper way to think about sunk cost is to imagine you got where you are for free and decide what you should do next without considering how you got there. In the case of Emacs, Ridenour says, that means to imagine you’ve never used an editor and that the expert you consults tells you that he can give you

  1. A ready to use editor that works well but requires you to do things its way or
  2. An editor that is custom built for you, that works the way you want it to, and that has commands that only you care about.

The rational choice is obvious. That’s why Emacs users keep using Emacs. Not because they have so much invested in it but because Emacs is the best editor and can be adjusted to continue being the best editor as their needs change.

-1:-- The Sunk Cost Of Emacs (Post Irreal)--L0--C0--2026-04-27T21:46:51.000Z

Dave's blog: Writing an automated test to try to find an Emacs bug

BUG: Emacs is losing the default font information from init.el when adding a68-mode or alda-mode. It’s difficult to figure out what exactly is happening. It seems related to having a large number of packages installed, but perhaps its just the right combination of combination of packages.

DISCLAIMER: I strongly suspect there are better ways to do some of this, but what I have seems to be working.

I’m trying to write a test that starts with default font information specifying a font. It probably doesn’t matter what font, as the end result of the bug is this getting replaced with

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:background nil)))))

So to check whether the bug has happened, I can either

  • search for the pattern above, OR
  • search for the specific font I’ve specified

I will search for the specific font, as I’d like to detect any changes, not just the change above.

The bug seems to happen when installing a68-mode or alda-mode just after starting Emacs. So, this test will be a loop of many Emacs invocations.

  • Start Emacs
  • Install a68-mode
  • Check whether the default font information has been destroyed
    • If so, note the last package installed.
    • If not
      • delete (uninstall) a68-mode
      • install a random package
      • write the package name just installed to a file
      • exit Emacs
      • Run the test again

init.el

init.el contains the font information. I also initially added a “blank” custom-set-variables call, but over time I discovered that sometimes restart-emacs would see a process talking to ELPA and ask if I wanted to kill the process. No, I don’t want to be asked that. I found confirm-kill-processes was the variable I could set to nil to avoid getting asked. So into the set of of custom variables it went.

;;; -*- lexical-binding: t -*-
(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(confirm-kill-processes nil)
 )
(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family "Fira Code" :foundry "CTDB" :slant normal :weight medium :height 120 :width normal)))))

start-test.sh

#!/bin/sh

TESTDIR=$HOME/bug-emacs.d
cd $TESTDIR
emacs --init-directory=$TESTDIR --script $TESTDIR/test.el

Test

;;;
;;; Copy this script into an empty user-emacs-directory along with
;;; the accompanying init.el.
;;;
;;; Run this script with
;;;
;;;     emacs --init-directory=<directory-path> --load=<directory-path>/test.el
;;;

Load up packages

List packages and set buffer to Packages

(progn
  (list-packages)
  (set-buffer "*Packages*")
  )

Install a68-mode

(message "%s" "Installing a68-mode")
(sit-for 0.5)
(package-install 'a68-mode)

Check that font information is still in init.el

(find-file (locate-user-emacs-file "init.el"))
(if (not (search-forward "(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family \"Fira Code\" :foundry \"CTDB\" :slant normal :weight medium :height 120 :width normal)))))"
			 nil t))
    ;; String not found. Read last package installed from file and print it out. Then exit Emacs.
    ;;
    ;; Read string from file and print
    (progn
      (find-file "last-installed.txt")
      (display-message-or-buffer "Install of %s caused subsequent install of a68-mode to lose font information" (buffer-string))
      (sit-for 0.5)
      ;; Exit Emacs
      (kill-emacs) 
     )
    )

Uninstall/delete a68-mode

(message "%s" "Removing a68-mode")
(sit-for 0.5)
(package-delete (package-get-descriptor 'a68-mode))

Install random package

Install random package and write its name to last-installed file

(let ((pkg (car (seq-random-elt package-archive-contents))))
  (save-current-buffer
    (let ((buffer (find-file "last-installed.txt")))
      (set-buffer buffer)
      (kill-region (point-min) (point-max))
      (print pkg buffer)
      (save-buffer)
      )
    )
  ;; Now install pkg
  (message "Installing %s" pkg)
  (sit-for 1)
  (ignore-errors (package-install pkg))
  )

Restart emacs

I was wondering if I’d have to have a more complicated shell script to keep starting Emacs, but Emacs has restart-emacs that does the obvious.

(message "%s" "Restarting emacs")
(sit-for 0.5)
(restart-emacs)
-1:-- Writing an automated test to try to find an Emacs bug (Post Dave's blog)--L0--C0--2026-04-27T00:00:00.000Z

Irreal: Keybindings For Mouse Use

For those of you who like using your mouse with Emacs, Charles Choi has a post you may find helpful. In it, he lists some keybindings that are helpful when using a mouse in Emacs. I famously eschew the use of the mouse wherever possible—not just in Emacs—so I have no worthwhile opinions about matter and I’m not going to comment on his bindings. In any event, I long ago mapped F10 to Calc so none of it applies to me anyway.

Still, I recognize that many of you do like using the mouse so I thought I’d point out Choi’s post for you.

-1:-- Keybindings For Mouse Use (Post Irreal)--L0--C0--2026-04-26T14:21:01.000Z

Chris Wellons: I have officially retired from Emacs

This article was discussed on reddit and on Hacker News.

This past Tuesday I typed C-x C-c in Emacs for the last time after 20 years of daily use. Though nearly half that time was gradually retiring it, switching to modal editing, then to Vim. Emacs is a platform, and I’d grown accustomed to its applications, especially those I built myself. There was no particular hurry, so replacements came slowly. With my newly-acquired superpowers I could knock out the last two pieces in a few days’ work, namely M-x calc with stackcalc and Elfeed with Elfeed2. I’m especially excited about the latter because it already exceeds the original. Both are multi-platform, native C++ GUI applications using native UI components.

These actively-in-use packages require new maintainers (apply on the project’s issues/discussion):

No wonder it took so long for me to move on! I’m not handing these off to just anyone, and you’ll need to establish your reputation. Having already made contributions is a good sign, even if never merged. I’m willing to transfer them off my namespace, though you’ll need to manage the Melpa hand-off (on which I’ll sign-off). If there are no takers, these projects will be archived but not deleted.

Trying out wxWidgets

The Emacs Calculator is amazing and the best calculator I’ve ever used, which is why nothing I could find was going to replace it. My clone uses GMP and MPFR for multi-precision, so it’s far faster, as to be expected, but it’s not nearly at feature parity. It’s missing esoteric features including symbolic processing. Though it’s enough to cover all of my own usage. I can add more features later. The Emacs Calculator manual served as a specification when building stackcalc.

Elfeed has been a cornerstone of my daily routines for the past 13 years. Nothing else I’ve found scratches that itch for me, so I’ve always known it would require a rewrite someday. Knowing it would take a few weeks of work, and that I already had the feed reader I wanted, made motivation difficult to find. Though now that I can accomplish ~3 weeks of old-way work in a new-way day, this sort of project becomes that much easier to start and finish. Though it’s not yet at a 1.0 release, after a couple days Elfeed2 was working well enough to replace the original Elfeed.

While Dear ImGui was the right choice for dcmake, it would not be so for these two applications. Active rendering doesn’t suit a feed reader left running all day, and I needed a richer toolkit. Professionally I work in Qt, but I wanted something lighter-weight for my projects, accessible via CMake FetchContent. That naturally led to wxWidgets. While it has issues — mitigatable character encoding problems, accidental quadratic time in many places — it’s worked better than I anticipated, letting me rapidly produce native-looking applications on Windows, macOS, and Linux.

Unlike Dear ImGui, wxWidgets is a platform, including sane I/O and path handling. I mostly don’t need platform layers when building applications like these. I can simply rely on wxWidgets’ utilities.

Both of these projects build out-of-the-box on w64devkit thanks to the dependencies being FetchContent-compatible. On all platforms you just need a C++ toolchain and CMake:

$ cmake -B build
$ cmake --build build

Now that I have experience with wxWidgets, learning its limitations and capabilities, it’s likely to be a foundation of most of my GUI projects to come, except where something like Dear ImGui is a better git.

-1:-- I have officially retired from Emacs (Post Chris Wellons)--L0--C0--2026-04-26T00:00:00.000Z

Randy Ridenour: Emacs and the Sunk Cost Fallacy

It’s rare that I can write a post about Emacs and be able to claim some expertise — and by “rare” I mean that this is the first time ever. My usual Emacs posts are about the tools that I’ve hacked together to do things that only I probably need. A recent post by Karl Voit, titled “The Emacs Lock-In Effect or the Emacs Sunk Cost Fallacy” caught my eye, since cognitive biases are a research interest of mine. Emacs and cognitive psychology is a combination that I can’t resist.

Voit’s post was prompted by a Reddit post from five years ago which claimed, in part, that1

However I might admit that I exhibit some sunk cost fallacy thinking. It becomes clear when I realize that I WON’T recommend Emacs to new users - unless they are going to invest large amounts of time learning it, like I did.

In response, Voit makes an excellent point:

…I don’t think that I’m using Emacs just because I don’t think of alternatives any more. I use it because all in all, it’s the best package I can get for now and most probably also for the future.

This is nicely summarized by him as “Emacs knowledge is long-term knowledge.”

In the end, I find myself agreeing with both people. Learning Emacs is indeed a long-term investment that can pay off immensely, but I can’t recommend it to someone who isn’t willing to make the investment. I do have one small objection, though, to their use of the phrase, “sunk cost fallacy.” For example, Voit writes:

I can not think of a different situation where you are using a flexible tool that you adapt to your situation which does not come with the sunk cost fallacy or some kind of lock-in effect….

Voit’s reasoning is exactly right here, but he should say instead that there are no situations in which a person adapts a tool to their use that does not involve a sunk cost. The mistake is implying that every sunk cost is a case of the sunk cost fallacy. This happens often enough that maybe we should coin a new informal fallacy called “the fallacy of the sunk cost fallacy.”

A sunk cost is something that has been spent and cannot be recovered. This can be money, time, effort, or, in war, even lives. Since every project takes some time and effort, there will always be associated sunk costs. A person commits the sunk cost fallacy when deciding to continue a project by considering the irrecoverable investment rather than the future utility of the project.

We’ve probably all committed the sunk cost fallacy at some point. A common example is the reluctance to hang up when one has been on hold for some time, even if there is something else that the person needs to be doing. The thought is that if I hang up now, then that time that I’ve already spent will have been wasted. Sometimes, the sunk cost fallacy has tragic consequences, however. Climbers have died on Everest because they convinced themselves that if they turned back, the training, time, and money that they had already invested would be wasted.

The classic paper on the sunk cost fallacy is “The Psychology of Sunk Cost” by Arkes and Blumer.2 One of their experiments involved subjects who were asked to imagine having spent $100 on a ticket for a future weekend ski trip to Michigan. Several weeks later, they buy a $50 ticket for an even better weekend ski trip to Wisconsin. As they are putting the Wisconsin ticket into their wallet, they notice that the tickets are for the same weekend. It’s now too late to sell one of the tickets and neither is returnable, so only one of them can be used. Then, subjects were asked to decide which trip they would go on. 28 said they would go on the Wisconsin trip. 33 said they would go on on the Michigan trip, even though the Wisconsin trip was the better of the two.

From an economic perspective, this is clearly irrational behavior, but the lure of sunk costs is very difficult to resist — just ask anyone who has sat through a movie they weren’t enjoying simply because they had already paid for the ticket.

What, though, does this have to do with Emacs? Using Emacs requires a certain amount of crafting and fine-tuning, which involves time and effort that one will never get back — all sunk costs. That does not necessarily mean that using Emacs is an example of the sunk cost fallacy, however. Seeing that requires understanding how one avoids being trapped by sunk costs. How should subjects have thought about the Arkes and Blumer ski trip experiment? They should have asked themselves which trip they would have taken had they been given both tickets for free. In that case, they would have obviously chosen the better trip of the two, which was the Wisconsin trip.

The use of any tool will involve some sunk costs. If the tool is customizable, as Emacs is, then the time and effort spent customizing it to the way that you want to work will be a sunk cost. If the tool is not customizable, then the time learning to use the tool and adapting your workflow to the way the tool works be a sunk cost.

As I and many others have said before, the beauty of Emacs is that it can be made to work the way that you want it to work. It does not demand that you do things its way. There is an out-of-the-box Emacs experience which can be, I admit, less than satisfying for many people. Don’t like the colors? Change the theme then, it’s easy. Don’t like a keybinding? Change it, it’s easy. I’m a philosopher; trust me, if I can do it, then anyone can.

Over the years, I’ve invested many hours in tweaking my configuration files and writing small functions to accomplish various tasks. Would I continue to use Emacs if I hadn’t spent that time and effort? Am I possibly guilty of committing the sunk cost fallacy? The test is to ask this:

Imagine that you had not already been using a text editor and found yourself needing one. You go to an expert for help who gives you a choice between

  1. A mass-market editor that works well, but expects you to adapt to its workflow, or
  2. An editor that was custom-built for you, tweaked and configured to work exactly the way that you want it to work, with special built-in commands that perform the tasks that only you need.

Which would you choose. I know I’d choose the second option. So, even though I’ve invested heavily in Emacs and built up some considerable sunk costs, my continued use is perfectly rational. There may be sunk costs, but with respect to my use of Emacs, there is no sunk cost fallacy.

Footnotes

1

The post is still available, but the user’s account has been deleted.

2

Organizational Behavior and Human Decision Processes 35, 124–140 (1985).

-1:-- Emacs and the Sunk Cost Fallacy (Post Randy Ridenour)--L0--C0--2026-04-25T19:41:00.000Z

Irreal: Guide To Folding

James Cherti has an excellent post describing how to set up a universal folding system. Folding is one of those things that I’m equivocal about. I use it all the time in Org files and couldn’t live without it. On the other hand, I’ve never seen the need for folding in code files. Sometimes it is convenient to focus on a single function but in those cases I simply narrow to the function.

Perhaps my problem is that every type of file seems to have its own folding engine with different commands and bindings. Regardless, Cherti definitely does like folding in his code buffers and like me with Org files, couldn’t live without it.

His post explains his system with dealing with folding in various types of buffers. The TL;DR is that he uses kirigami as a front end to provide a uniform interface to the myriad of folding engines. That’s pretty nice but you have to tie it to the back end engines.

That’s where Cherti’s post really shines. He provides the hooks for a large selection of file types. It’s not always obvious which engine you should use for a given file type and Cherti explains why he thinks each of his choices is the correct one.

The nice thing is that once you’ve set up and learned kirigami, you can add folding for whatever files you need simply by setting a hook. Kirigami allows you to set your own bindings for the commands so even learning Kirigami is simple.

If you’re more like Cherti than like me, and want folding for as many of your files as possible, take a look at his post. It’s really easy to set up and gives you all the folding power you’re apt to need.

-1:-- Guide To Folding (Post Irreal)--L0--C0--2026-04-25T14:55:46.000Z

Marcin Borkowski: How I use my numeric keypad with Emacs Ledger mode

As the readers of my blog know, I’m a heavy Ledger user. One thing that annoyed me was the fact that I couldn’t use my numeric keypad to enter amounts. The reason was simple – the “comma” key on that keypad inserted a comma in Emacs and not a period.
-1:-- How I use my numeric keypad with Emacs Ledger mode (Post Marcin Borkowski)--L0--C0--2026-04-25T03:01:51.000Z

Yi Tang: Giving Qwen 3.6 35B Vision

Qwen 3.6 35b has been a fantastic thinking companion for me, anything that I don’t know, I am not comfortable with, or having doubts with, I would check with it. I found Qwen 3.6 + DeerFlow 2.0 is much better than the paid version of Grok, and miles better than Perplexity.

Today, I made it even better by giving it vision. Earlier I uploaded an image of my staircase and asked it to check the conditions when I plan the staircase renovation project.

This blog post highlights the key steps of how i did it.

  1. Firstly, Qwen 3.6 has vision encoder built-in already, but it requires an additional mmproj component to make it work. Honestly I have no idea what does it mean at the moment, I just think of it as the eyes to LLM.

  2. Download the mmproj file from the Unsloth Qwen 3.6 repo1, add the path to –mmproj argument for llama-server command, reboot llama.cpp, that’s it.

    The vision component requires additional 1-2GB of vram, so to make them fit to RTX 3090, I had to quantize the mmproj component from bf16 to q4:

    llama-quantize mmproj-BF16.gguf mmproj-Q4_K_M.gguf Q4_K_M
    llama-server Qwen3.6-35B-A3B-UD-Q4_K_M.gguf \
                 --mmproj mmproj-Q4_K_M.gguf \
                 ...  # rest of the llama-server arguments
    
  3. To test it,
    1. check the mmproj is loaded successful from the llama.cpp log,

      9517 alloc_compute_meta: graph splits = 1, nodes = 823                                                                                                                        
      9518 warmup: flash attention is enabled                                                                                                                                       
      9519 srv    load_model: loaded multimodal model, 'mmproj-BF16.gguf'
      
    2. Ask Qwen 3.6 35B model to describe a small image file, using this snippet

      curl -X POST http://192.168.1.34:8000/v1/chat/completions \
        -H "Content-Type: application/json" \
        -d '{
          "model": "Qwen3.6-35B-A3B",
          "messages": [{
            "role": "user",
            "content": [
              {"type": "image_url", "image_url": {"url": "https://picsum.photos/512/512"}},
              {"type": "text", "text": "Describe this image"}
            ]
          }],
          "max_tokens": 100
        }' | jq
      

      This is the response I got, so it confirms it works. The image will change from time to time, so the response will be different.

      The image is a scenic landscape photograph, likely taken in late autumn or winter. It features a vast mountain range in the background, rolling hills in the mid-ground covered in snow and trees, and a foreground of dry, grassy terrain. The sky is dramatic with a mix of blue and warm sunset/sunrise colors.\n\n**2. Breaking down the image into layers

    3. if 1. success, but 2. failed, query the log file, grep vision or image, e.g. this is what I got when i misspell mmproj in llama-server at one point:

      print_info: PAD token             = 248055 '<|vision_pad|>'
      srv    operator(): got exception: {"error":{"code":500,"message":"image input is not supported - hint: if this is unexpected, you may need to provide the mmproj","type":"server_error"}}
      
  4. The model is equipped for vision tasks, next step is to enable vision on DeerFlow 2.0, all I need is adding the support_vision to true in config, full model spec is listed below to avoid ambiguity

    models:                                                                                
    - name: Qwen3.6-35B                                                                    
      display_name: Qwen 3.6 35B (RTX 3090)                                                
      use: langchain_openai:ChatOpenAI                                                     
      model: Qwen3.6-35B                                                                   
      base_url: http://192.168.1.34:8000/v1                                                
      api_key: dummy_key                                                                   
      supports_thinking: true                                                              
      supports_reasoning_effort: true                                                      
      supports_vision: true                                                                
      timeout: 600    
    

    I have to add increase the timeout to 10 mins because the vision component is a lot slower than text generation, with the default value, DeerFlow will throw errors thinking the LLM is not responding. the vision component can be optimised later to reduce the runtime, but so far so good.

  5. Now test DeerFlow 2.0. Restart the services (make docker-stop && make docker-start), open a new chat, upload a PNG file, and ask to describe, wait for a bit, then boom!

    I can also copy an image, and paste it to deerflow, which is very nice interface.


Qwen 3.6 describes an uploaded image in DeerFlow 2.0

Footnotes

1 https://huggingface.co/unsloth/Qwen3.6-35B-A3B-GGUF

-1:-- Giving Qwen 3.6 35B Vision (Post Yi Tang)--L0--C0--2026-04-24T23:00:00.000Z

Charles Choi: Some nice to know keybindings when using the mouse in Emacs

These keybindings are good to know when working with a mouse in Emacs.

F10 (menu-bar-open) :: Start key navigation of the menu bar in FRAME.

S-F10 (context-menu-open) :: Start key navigation of the context menu. This needs context-menu-mode enabled.

C-M-mouse-1 :: Activate a rectangular region around the text selected by dragging. Useful for rectangle operations.

M-Drag-mouse-1 :: Set the secondary selection, with one end at the place where you press down the button, and the other end at the place where you release it. See Secondary Selection for more info.

C-mouse-3:: Raise menu populated with the menu mode map of the current mode and if Imenu is enabled, an index menu for that buffer.

C-u n (optional) :: A prefix argument can be passed to a menu item.

Keyboard Macro :: Commands issued via menu can be used when recording a keyboard macro. (🤯 Who knew?)

C-x z (repeat) :: Repeat most recently executed command. Yes, repeat works also for commands issued via mouse too!

Knowing that repeat works with menu commands (both main and context) is particularly useful with the additions to the main menu made by the latest Anju v1.2.0 update. Shown below is a demo of using “Duplicate” (duplicate-dwim) from the Edit menu, then using C-x z and then z successively to repeat the duplication.



Amended 2026-04-25: Added C-M-mouse-1, M-Drag-mouse-1, C-mouse-3, C-u n, Keyboard Macro items.

-1:-- Some nice to know keybindings when using the mouse in Emacs (Post Charles Choi)--L0--C0--2026-04-24T20:40:00.000Z

Dave Pearson: kbdify.el v1.0.0

When I'm writing documentation in Markdown I like, where possible, to mark up keys with the <kbd> tag. This was the reason for one of the updates to BlogMore: I'd not done any good default markup for <kbd> and the moment I realised, I knew I had to fix it.

Now that I'm writing more on this blog, and especially about coding, I'm mentioning keys pretty often (even more so given I'm doing a lot of tidying up of my Emacs Lisp packages). The thing is though: I find having to type out <kbd> and </kbd> kind of tedious, and it's something I mistype from time to time. I guess I could use some sort of HTML tag inserting tool or whatever, but I got to thinking that it would be handy if I could point an Emacs command at a particular sequence in a buffer and have it mark up the whole thing.

This resulted in a small bit of code I'm calling kbdify.el. It's pretty simple, if point is sat on some text that looks like this:

C-M-S-s-<up>

and I run kbdify I get this:

<kbd>C</kbd>-<kbd>M</kbd>-<kbd>S</kbd>-<kbd>s</kbd>-<kbd>&lt;up&gt;</kbd>

The result rendering as C-M-S-s-<up>.

I could probably take it a bit further, have it optionally work on a region and stuff like that, but even in its current simplistic form it's going to be loads quicker and a lot more accurate and will probably perfectly cover 99% of the times I need it. There is the issue that it's not going to handle something like M-x some-command RET in the way I might like, but then again some-command isn't a key. Like, does it make more sense to have:

M-x some-command RET

anyway? Personally I think this:

M-x some-command RET

probably makes more sense.

I think I'm good for now.

-1:-- kbdify.el v1.0.0 (Post Dave Pearson)--L0--C0--2026-04-24T16:04:37.000Z

Please note that planet.emacslife.com aggregates blogs, and blog authors might mention or link to nonfree things. To add a feed to this page, please e-mail the RSS or ATOM feed URL to sacha@sachachua.com . Thank you!