Another short post today but one that some of you may find useful. Over at the Emacs subreddit, James Cherti offers a set of commit hooks for Elisp programmers. There are hooks that check for parenthesis consistency, byte compile to check for compile errors, native compile to check for errors, and even a hook to check for conformance to Elisp indentation conventions.
The project repository is here. It has a little more information on installing and using the hooks but essentially everything you need to know is in the reddit announcement.
If you’re an Elisp programmer and like to run some rudimentary checks before committing your work, you may find Cherti’s project useful. If, like me, you hate being constrained by arbitrary coding format conventions, you can choose to ignore the indentation checks. In any event, if you’re an Elisp programmer, you should take a look at Cherti’s post.
One of the things that has always slightly bothered me about chatting with a local LLM is that it only knows what it was trained on (although I suppose most LLMs are like that) . Ask it about your own codebase, your org notes, your project docs - and it’s just guessing. Well, not anymore! Ollama Buddy now ships with proper Retrieval Augmented Generation support built-in
What even is RAG?
If you haven’t come across the term before, the basic idea is simple. Instead of asking the LLM a question cold, you first go off and find the most relevant bits of text from your own documents, then you hand those bits to the LLM along with your question. The LLM now has actual context to work with rather than just vibes. The “retrieval” part is done using vector embeddings - each chunk of your documents gets turned into a mathematical representation, and at query time your question gets the same treatment. Chunks that are mathematically “close” to your question are the ones that get retrieved.
In this case, I have worked to keep the whole pipeline inside Emacs; it talks to Ollama directly to contact an embedding model, which then returns the required information. I have tried to make this as Emacs Org-friendly as possible by storing the embedding information in Org files.
Getting started
You’ll need an embedding model pulled alongside your chat model. The default is nomic-embed-text which is a solid general-purpose choice:
ollama pull nomic-embed-text
or just do it within ollama-buddy from the Model Management page.
Indexing your documents
The main entry point is M-x ollama-buddy-rag-index-directory. Point it at a directory and it will crawl through, chunk everything up, generate embeddings for each chunk, and save an index file. The first time you run this it can take a while depending on how much content you have and how fast your machine is - subsequent updates are much quicker as it only processes changed files.
Supported file types (and I even managed to get pdf text extraction working!):
Emacs Lisp (.el)
Python, JavaScript, TypeScript, Go, Rust, C/C++, Java, Ruby - basically most languages
Org-mode and Markdown
Plain text
PDF files (if you have pdftotext from poppler-utils installed)
YAML, TOML, JSON, HTML, CSS
Files over 1MB are skipped (configurable), and the usual suspects like .git, node_modules, __pycache__ are excluded automatically.
The index gets saved into ~/.emacs.d/ollama-buddy/rag-indexes/ as a .rag file named after the directory. You can see what you’ve got with M-x ollama-buddy-rag-list-indexes.
The chunking strategy
One thing I’m quite happy with here is the chunking. Rather than just splitting on a fixed character count, documents are split into overlapping word-based chunks. The defaults are:
(setq ollama-buddy-rag-chunk-size 400) ; ~500 tokens per chunk(setq ollama-buddy-rag-chunk-overlap 50) ; 50-word overlap between chunks
The overlap is important - it means a piece of information that sits right at a chunk boundary doesn’t get lost. Each chunk also tracks its source file and line numbers, so you can see exactly where a result came from.
Searching and attaching context
Once you have an index, there are two main ways to use it:
M-x ollama-buddy-rag-search - searches and displays the results in a dedicated buffer so you can read through them
M-x ollama-buddy-rag-attach - searches and attaches the results directly to your chat context
The second one is the useful one for day-to-day work. After running it, your next chat message will automatically include the retrieved document chunks as context. The status line shows ♁N (where N is the number of attached searches) so you always know what context is in play. Clear everything with M-x ollama-buddy-clear-attachments or C-c 0.
You can also trigger searches inline using the @rag() syntax directly in your prompt and is something fun I have been working on to include an inline command language of sorts, but more about that in a future post.
The similarity search uses cosine similarity with sensible defaults (hopefully!)
(setq ollama-buddy-rag-top-k 5) ; return top 5 matching chunks(setq ollama-buddy-rag-similarity-threshold 0.3) ; filter out low-relevance results
Bump top-k if you want more context, lower the threshold if you’re not getting enough results.
A practical example
Say you’ve been working on a large Emacs package and you want the LLM to help you understand something specific. You’d do:
M-x ollama-buddy-rag-index-directory → point at your project directory
Wait for indexing to complete (the chat header-line shows progress)
M-x ollama-buddy-rag-attach → type your search query, e.g. “streaming filter process”
Ask your question in the chat buffer as normal
The LLM now has the relevant source chunks as context and can give you a much more grounded answer than it would cold.
And the important aspect, especially regarding local models which don’t often have the huge context sizes often found in online LLMs is that it allows for very efficient context retrieval.
That’s pretty much it!
The whole thing is self-contained inside Emacs, no external packages or vector databases, you index once, search as needed, and the LLM gets actual information rather than hallucinating answers about your codebase or anything else that you would want to ingest and it will hopefully make working with local LLMs through ollama noticeably more useful and accurate.
A few years ago I wrote about setting up Emacs for OCaml
development.
Back then the recommended stack was tuareg-mode + merlin-mode, with Merlin
providing the bulk of the IDE experience. A lot has changed since then – the
OCaml tooling has evolved considerably, and I’ve been working on some new tools
myself. Time for an update.
The key shift is from Merlin’s custom protocol to LSP.
ocaml-lsp-server has matured
significantly since my original article – it’s no longer a thin wrapper around
Merlin. It now offers project-wide renaming, semantic highlighting, Dune RPC
integration, and OCaml-specific extensions like pattern match generation and
typed holes. ocaml-eglot is a lightweight Emacs package by
Tarides that bridges Eglot with these OCaml-specific
LSP extensions, giving you the full Merlin feature set through a standardized
protocol.
And neocaml is my own TreeSitter-powered OCaml major mode – modern, lean,
and built for the LSP era. You can read more about it in the 0.1 release
announcement.
The Essentials
First, install the server-side tools:
1
$opam install ocaml-lsp-server
You no longer need to install merlin separately – ocaml-lsp-server
vendors it internally.
Then set up Emacs:
1
2
3
4
5
6
7
8
9
10
11
12
;; Modern TreeSitter-powered OCaml major mode(use-packageneocaml:ensuret);; Major mode for editing Dune project files(use-packagedune:ensuret);; OCaml-specific LSP extensions via Eglot(use-packageocaml-eglot:ensuret:hook(neocaml-mode.ocaml-eglot-setup))
That’s it. Eglot ships with Emacs 29+, so there’s nothing extra to install for
the LSP client itself. When you open an OCaml file, Eglot will automatically
start ocaml-lsp-server and you’ll have completion, type information, code
navigation, diagnostics, and all the other goodies you’d expect.
Compare this to the old setup – no more merlin-mode, merlin-eldoc,
flycheck-ocaml, or manual Company configuration. LSP handles all of it
through a single, uniform interface.
The Toplevel
neocaml includes built-in REPL integration via neocaml-repl-minor-mode. The
basics work well:
C-c C-z – start or switch to the OCaml toplevel
C-c C-c – send the current definition
C-c C-r – send the selected region
C-c C-b – send the entire buffer
If you want utop specifically, you’re still better off using
utop.el alongside neocaml. Its
main advantage is that you get code completion inside the utop REPL within
Emacs – something neocaml’s built-in REPL integration doesn’t provide:
This will shadow neocaml’s REPL keybindings with utop’s, which is the
intended behavior.
That said, as I’ve grown more comfortable with OCaml I find myself using the
toplevel less and less. These days I rely more on a test-driven workflow –
write a test, run it, iterate. In particular I’m partial to the workflow
described in this OCaml Discuss
thread –
running dune runtest continuously and writing expect tests for quick feedback.
It’s a more structured approach that scales better than REPL-driven development,
especially as your projects grow.
Give neocaml a Try
If you’re an OCaml programmer using Emacs, I’d love for you to take
neocaml for a spin. It’s available on
MELPA, so getting started is just an M-x package-install away. The project is
still young and I’m actively working on it – your feedback, bug reports, and
pull requests are invaluable. Let me know what works, what doesn’t, and what
you’d like to see next.
Every now and then someone asks me how to learn Vim.1 My answer is always the
same: it’s simpler than you think, but it takes longer than you’d like. Here’s
my bulletproof 3-step plan.
Step 1: Learn the Basics
Start with vimtutor – it ships with Vim and takes about 30 minutes. It’ll
teach you enough to survive: moving around, editing text, saving, quitting. The
essentials.
Once you’re past that, I strongly recommend Practical
Vim by Drew
Neil. This book changed the way I think about Vim. I had known the basics of
Vim for over 20 years, but the Vim editing model never really clicked for me
until I read it. The key insight is that Vim has a grammar – operators
(verbs) combine with motions (nouns) to form commands. d (delete) + w
(word) = dw. c (change) + i" (inside quotes) = ci". Once you
internalize this composable language, you stop memorizing individual commands
and start thinking in Vim. The book is structured as 121 self-contained tips
rather than a linear tutorial, which makes it great for dipping in and out.
You could also just read :help cover to cover – Vim’s built-in
documentation is excellent. But let’s be honest, few people have that kind of
patience.
Other resources worth checking out:
Advent of Vim
– a playlist of short video tutorials covering basic Vim topics. Great for
visual learners who prefer bite-sized lessons.
vim-be-good – a Neovim
plugin that gamifies Vim practice. Good for building muscle memory.
Step 2: Start Small
Resist the temptation to grab a massive Neovim distribution like
LazyVim on day one. You’ll find it overwhelming if
you don’t understand the basics and don’t know how the Vim/Neovim plugin
ecosystem works. It’s like trying to drive a race car before you’ve learned how
a clutch works.
Instead, start with a minimal configuration and grow it gradually. I wrote
about this in detail in Build your .vimrc from
Scratch
– the short version is that modern Vim and Neovim ship with excellent defaults
and you can get surprisingly far with a handful of settings.
I’m a tinkerer by nature. I like to understand how my tools operate at their
fundamental level, and I always take that approach when learning something new.
Building your config piece by piece means you understand every line in it, and
when something breaks you know exactly where to look.
Step 3: Practice for 10 Years
I’m only half joking. Peter Norvig’s famous essay Teach Yourself Programming
in Ten Years makes the case that mastering
any complex skill requires sustained, deliberate practice over a long period –
not a weekend crash course. The same applies to Vim.
Grow your configuration one setting at a time. Learn Vimscript (or Lua if
you’re on Neovim). Read other people’s configs. Maybe write a small plugin.
Every month you’ll discover some built-in feature or clever trick that makes
you wonder how you ever lived without it.
One of the reasons I chose Emacs over Vim back in the day was that I really
hated Vimscript – it was a terrible language to write anything in. These days
the situation is much better: Vim9 Script is a significant improvement, and
Neovim’s switch to Lua makes building configs and plugins genuinely enjoyable.
Mastering an editor like Vim is a lifelong journey. Then again, the way things
are going with LLM-assisted coding, maybe you should think long and hard about
whether you want to commit your life to learning an editor when half the
industry is “programming” without one. But that’s a rant for another day.
Plan B
If this bulletproof plan doesn’t work out for you, there’s always Emacs. Over
20 years in and I’m still learning new things – these days mostly how to make
the best of evil-mode so I can have the
best of both worlds. As I like to say:
The road to Emacs mastery is paved with a lifetime of M-x invocations.
That’s all I have for you today. Keep hacking!
Just kidding – everyone asks me about learning Emacs. But here we are. ↩︎
I’ve developed a new random software testing technique that I believe will become a staple of my software testing going forward. It is both helpful for agentic AI (LLM) software development, where robust software verification is more important than ever, and implemented with the help of LLMs.
I’m training to ride RAGBRAI LIII in Iowa in July 2026. RAGBRAI
provides a training plan which, if followed, helps riders get ready
for the ride.
Being an Emacs Org geek, I grabbed the provided Excel spreadsheet,
exported it as a CSV (comma separated values) file, then converted
that to an Org table.
The spreadsheet shows two lines per week, one with the planned
mileages, and another to record the actual miles ridden. There’s a
total mileage in column 6, which is filled in for the planned rides,
and you fill in yourself for the actuals.
The first few lines of the spreadsheet look like
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| | 2026 RAGBRAI® Training Plan | | | | |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Week of: | Weekday 1 | Weekday 2 | Saturday | Sunday | Week Total |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| February 9 | 5 miles | 5 miles | 10 miles | - | 20 miles |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Actual Ridden | | | | | |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| February 16 | 10 miles | 10 miles | 10 miles | - | 30 miles |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Actual Ridden | | | | | |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
I wanted to do two things:
make column 6 the sum of columns 2 through 5, which include two
weekday and two weekend rides
create a column 7 that’s a percentage of the actual vs. planned
mileage
Org tables let you specify formulas for cells, columns, and rows.
Getting the sum of columns 2 through 5 for column 6 can be done a
couple of ways:
$6=$2+$3+$4+$5
$6=vsum($2..$5)
I used the former for quite a while, until I realized I could use the
latter. Either way, it works.
I had difficulty getting the formula for column 7 working how I wanted
it. Org tables can do formulas using Calc syntax, which is what I used
for column 6, or using Emacs Lisp forms as formulas. I used Calc
syntax to get this working. I wanted to calculate $7 as 100 times the
value of @0$6, the current row’s sum of miles ridden, divided by the
value of the previous row’s sum of planned miles. It’s easy enough to
just do something like $7=100*@0$6/@-1$6, but the values on
the planned rides rows were 100 times the planned miles for that week
divided by the actual miles for the previous week, which is usually
some number over 100. And not that interesting to me.
I also got rid of all instances of “miles” in the table. I ended up
with weird things like 70.3 miles / miles, and couldn’t figure out how
to get Org to simplify this. Getting rid of “miles” in the spreadsheet
made things look much better.
I wanted to key on the “Actual Ridden” string in $1. If $1 is “Actual
Ridden”, then use the calculation we’ve been discussing. Otherwise,
use 100 or show nothing. I had great difficulty making this work with
Calc syntax.
So, I tried Emacs Lisp forms as formulas. I ended up with
This is a pretty straightforward translation of the percentage
formula, along with using format to only show a couple
of decimal places. In the case of other lines, just return an empty
string. The final formula works great.
I started out on a journey to try to get sway to create a new
workspace for me, with a “probably” unique name. How could I do that?
sway, like it’s ancestor i3, has an IPC mechanism that
allows you to query and control sway from other programs. You
can play with this with swaymsg. swaymsg "workspace foo" will switch your current display to show workspace “foo”,
creating “foo” if it doesn’t exist. So one interesting way to achieve
my goal is
swaymsg "workspace $(xkcdpass -n 1)"
xkcdpass -n 1 prints a single random word from
xkcdpass’ word file.
One problem with random words is that we don’t typically have nice key
bindings in sway to switch to these. You can use the mouse and
click on the workspace name on your bar, but if you’d prefer to only
use your keyboard, too bad.
So I thought to use rofi to show a list of workspaces and let
you select one. rofi has no builtin mode for this, but it does
have a way to add modes using scripts. Such a script has two modes
itself:
with no arguments, list the items in question. In this case, list
the workspaces
with one argument, do something with that item. In our case, switch
to that workspace
rofi allows you to type a new item, so in our case that would
be another way to switch to a new workspace.
So, how do you get the list of workspaces? Again, the sway IPC
mechanism. You can run swaymsg -t get_workspaces to get the
list of workspaces. By default this will pretty print the list of
workspaces. But if you specify -r or --raw or pipe
swaymsg to another program, it outputs JSON. Here’s an
example:
Phew, that’s a lot for 3 workspaces! The only part I care about in
this case is the “name” for each. We could do some weird stuff with
grep and sed to get the names, or use jq to
extract what we want.
swaymsg -t get_workspaces | jq '.[] | .name'
generates
"1"
"2"
"4"
Perfect!
Putting this all together, here’s a shell script
sway_list_workspaces that we can use with rofi to
switch workspaces without using the mouse.
#!/bin/shif[ x"$1"= x ]then
swaymsg -t get_workspaces | jq '.[] | .name'else
swaymsg "workspace $1">/dev/null
fi
: Added m-x.app examples, moved el-init to AI category, added retrospective link.
Org Mode is a big part of why I enjoy Emacs, so I'm delighted that there's a new release out (Org 9.8). Thanks to all who contributed! If you would like to help out, Ihor is looking for several volunteers who can try to reproduce bugs and do initial feedback on the new patches.
Ihor Radchenko writes to tell us about the release of Org Mode 9.8. It’s a pretty big release as you can see from the Changes file. It’s much too long to list the changes here and, of course, not everyone will be interested in all the changes so be sure to take a look to see what’s of interest to you.
As always, we should pause and give thanks to Ihor, Bastien, and the others—see the announcement for a list of those contributing to this release—who work so hard on our behalf without any recompense but that thanks. These guys are heroes and, as I always say, we owe them a beer whenever he find ourselves in the same bar as any of them.
The announcement has a short list of significant updates so if you don’t want to read the whole changes file, that’s a good place to start.
Note: As I was preparing tomorrow’s post, I noticed that I forgot to publish today’s. Sorry. Here it is, better late than never.
Over at Meta Redux, Bozhidar Batsov has announced the release of Flycheck 36. It has, he says, been a long time coming but he’s really happy with it.
He’s added a couple of new language checkers—javascript-oxlint and org-lint—fixed a security issue, and added some features to ShellCheck. There are also quality of life improvements and bug fixes. Additionally, he’s updated the app’s Web site.
Take a look at Batsov’s post for all the details. Batsov notes that many people have assumed that Flycheck is essentially dead because of the builtin Flymake’s integration with Eglot but he says that Flycheck is alive and well and continuing to evolve.
If you’re a Flycheck user or interested in it, all this is good news. Batsov has put a lot of work into improving it and has plans for more. He explicitly asks those who want additional features to let him know or better yet to submit a PR. In any event, you should take a look at Batsov’s post to see all the new features and improvements.
The past year has brought a lot of changes, not the least of which is that I'm now working primarily in C after a few years of web development in NodeJS/React/general JavaScript land. There's some irony to this given that in my first PyCon talk back in 2022, I proudly declared that I wanted to write as little C/C++ as possible when reimplementing a constraint library in Python using Cython.
However, I've never been particularly happy with high levels of abstraction and black-box implementations going back to my days of frustration with structural engineering software, so maybe it's fitting now that I'm working at a lower level and pursuing performant programming in C. Of course, the switch has meant working with a completely different toolset than I've used in the past, and learning a lot about Emacs along the way.
I use Emacs 30.2 installed via the Emacs Plus Homebrew tap with native compilation, along with Doom Emacs to configure most of the features that I want to use.
Some of the packages not included in Doom Emacs that I find particularly useful for general development are:
git-link - allows you to copy a direct link to Github at the current line (useful for quickly pinpointing code for discussion)
rainbow-delimiters - highlights parentheses, brackets, braces in different colors (useful for nested input files)
wgrep - makes a grep buffer editable and allows you to write changes to all files modified in that buffer
My main mode when working in C is c-ts-mode, which is the C editing mode powered by tree-sitter
I believe installing tree-sitter per language now is much easier than it used to be, as I only needed to install the C grammar as outlined in this Mastering Emacs article per the "Compiling and Installing with the builtin method in Emacs" section. While the notes say that it doesn't work well unless you're using GCC and running Linux, I didn't have any problems with Clang and MacOS.
I still don't think I'm using Tree-sitter to its full capabilities in Emacs, but it has been fun to explore the code base using treesit-explore-mode and treesit-inspect-mode. I'd like to do more with its code navigation and highlighting, as I'm still relying primarily on projectile for code navigation. While researching some of the links for this blog post, I discovered the Combobulate package (disclaimer, from the same author as Mastering Emacs), which looks like it could be helpful.
Unfortunately, I can't use .clang-format, as there is an unorthodox set of formatting requirements that don't fit into any of the common C standards. As a result, I needed to add the following .clang-format to my base directory to avoid significant frustration.
One of the biggest boons for navigating a large, unfamiliar codebase (in a new language!) has been utilizing org mode to its fullest. It's how I write out work plans, write code snippets for investigation or debugging, and add in-line images to track current application state.
Early on, I added a function to copy an org mode link to the current file and line number (appropriately named copy-org-link-to-line). This has made it easy to navigate to functions I need to modify in my work plan and include additional context as my understanding of the codebase improves.
(defun copy-org-link-to-line ()
"Copy an Org mode link to the current file and line number.
Format: [[file+emacs:/absolute/path/to/file.txt::$line_number][file.txt:$line_number]]"
(interactive)
(if-let ((file (buffer-file-name))
(line (line-number-at-pos)))
(let ((org-link (format"[[file+emacs:%s::%d][%s:%d]]" file line (file-name-nondirectory file) line)))
(kill-new org-link)
(message"Copied %s" org-link))
(message"Buffer is not visiting a file")))
I've also struggled with tab/space presentation conflicting across different minor modes (probably an artifact from Doom, which has electric-indent-local-mode and ws-butler-mode), so I have a custom display setting for tabs in c-ts-mode:
I initially loaded and unloaded debugging files drafted in org-mode and loaded just into the command line lldb. This was largely because I couldn't get debugging with dap-mode to work at all with C. Surprise! This was because I use eglot instead of lsp-mode, as eglot seems to have won out for using language servers within Emacs, and dap-mode seems to be tied to lsp-mode.
In the interest of KISS, I opted to just use dape since it uses eglot out of the box.
I have two 'dape-configs set up in my config.el. One for launching/debugging the GUI, and another for launching/debugging a single test suite, which look something like this:
After my frustration trying to configure dap-mode, dape just worked! By default, the run adapter fields are easy to figure out before transferring that information to a dape-config as I did.
The dape-many-windows buffer layout is intuitive and easy to work with, providing immediate insight with 3 smaller buffers for Local/Global/Registers/Watch variables, the Stack/Modules/Sources, and Breakpoints/Threads, as well as positioning the dape-repl in a buffer at the bottom of the screen.
lldb shortcuts do not work in the provided dape-repl. This can get annoying with having to write out breakpoint --file file.c --line $line_number --condition "i == 200" over and over, rather than the shorthand brlc.
Watch variables or conditional statements set in the repl do not appear in the Breakpoints/Threads window, where you can easily delete them, which can be confusing while debugging.
It also seems to be impossible to "reset" breakpoint counts set using the dape command M-x M-a b (These are the ones that populate in the Breakpoint/Threads buffer) wwithout manually visiting the file. I've had to navigate to the breakpoint location and then toggle on/off to achieve this.
Editing watch variables via dape-info-watch-edit-node is really weird. I can't figure out how to get it to commit my changes despite the hints indicating it should just be C-c C-c.
Eglot as a process disconnects a lot. I don't know if this is due to dape or just in general while working in my project. I'd say 80% of the time I reach for xref-find-definition, I get a warning that eglot is not connected, which is very frustrating. (I don't get an error upon firing eglot-reconnect though!).
Especially in the age of LLMs, I'm becoming more and more leery of trusting online tutorials or resources. Since I'm working in C99, it seemed like a good investment to get some C standard texts, since the language is small enough to fit into pretty slim volumes.
Before starting my new job I read:
Effective C - though this was a bit of a challenge because it includes updates to cover all the way to C23!
I’ve finally published Gnosis version 0.7.0 that brings some much
needed changes and integration with org-gnosis. You can get the
latest version of gnosis via GNU ELPA or directly from upstream.
New features
Review topic from dashboard nodes view (r), with configurable
forward-link and backlink depth (R or C-u r).
gnosis-review-topic accepts separate forward/backlink depth to
include themata from related nodes.
gnosis-save-hook runs after saving a thema, called with the
thema ID.
Link integrity detection and repair commands
(gnosis-links-check, gnosis-links-sync).
Dashboard maintenance menu for syncing/rebuilding nodes and link
health checks.
Dashboard shows themata with orphaned links.
View due org-gnosis nodes linked to themata from dashboard.
Bulk replace string in keimenon with org-gnosis node link.
I just pushed a new version for org-gnosis, the note taking module of
gnosis, which you can find here. The update should be available to
all users via GNU ELPA.
Important Changes
Database Changes
Database version bumped to 3, tracking mtime and hash for both
nodes and journal files.
Automatic database migration prompts when schema is outdated.
Lazy database initialization - database connections are created only
when needed, improving startup time.
Added org-gnosis-database-file custom variable to specify custom
database file location.
Performance Improvements
Major performance improvements for org-gnosis-db-sync with GC
optimization during sync.
Two-tier incremental sync: fast mtime check, then accurate hash
comparison.
Only processes files where both modification time and content have
changed.
Dramatically improves performance for .gpg encrypted files and
large repositories.
Both node files and journal files now use incremental sync with
progress reporting showing changed/total file counts.
To force full resync, call org-gnosis-db-sync with prefix arg
C-u. e.g C-u M-x org-gnosis-db-sync RET.
Journaling
By default, today’s journal entries are created in
org-gnosis-journal-file as level 1 headings.
When set to nil, creates separate files for each date entry.
Creating new entries (e.g., via org-gnosis-journal-find for an
event, non-date) creates separate files in org-gnosis-journal-dir
regardless of this setting.
Single journal file is only used for today’s date entries.
Added org-gnosis-journal-as-gpg that when non-nil journal files
will be created as gpg encrypted files.
Journal todos now only retrieve checkboxes from today’s heading when
using a single journal file, improving performance.
Tag Management
Automatic cleanup of orphaned tags (tags with no associated nodes)
when updating files.
Improved tag-based node selection with dedicated functions.
Improvements
Simplified parsing algorithm for better performance and
maintainability.
Better handling of topic titles - only includes parent headings when
they have IDs.
Added org-gnosis-get-nodes-data for retrieving node information
with backlink counts.
Improved test coverage for parsing, title processing, and journal
todos.
When I write blog posts containing several links (usually to Emacs documentation, sometimes to my other posts and sometimes to other websites), one of the things I do is to check if all the links are correct. Usually I just middle-click on every one of them in the browser and see if they lead to the right place. I thought, however, that it would be useful to be able to see where an Org link points to without actually going there.
The fates are still punishing me for leaving home for travel. Today I had two errands to run:
Renew a driver license
Return an unused TV set top box to my provider
The DMV is, of course, a red flag but it turned out taking 3 hours. Three hours at the DMV is no one’s idea of a good time. After I staggered out of the DMV I went to my provider to drop off the TV set top box. That should take about 5 minutes, right? Silly you and me. That took an hour and a half.
This is a perfect example of where Prot’s advice would be useful. The user could easily build and install a temporary init.el to eliminate everything but what’s needed to produce the problem. Doing so might even allow the user to discover the problem on their own.
It’s been a while since Flycheck 35, but Flycheck
36 is finally here!
This is a pretty big release with a lot of new features, bug fixes, and some
much-needed cleanup of legacy checkers.
In fact, I’d say it’s the biggest and most important Flycheck release since I
became the project’s primary maintainer a couple of years ago. For a while I had
mostly gone with the flow (adding/improving linters and fixing bugs), but by now
I feel more confident to make bigger and bolder changes.
Anyways, you’re probably interested to learn more about Flycheck 36, so let me
walk you through the highlights.
New Checkers
We’ve added a couple of new checkers:
javascript-oxlint – a checker using oxlint, the
blazing-fast JavaScript/TypeScript linter written in Rust. If you haven’t
tried oxlint yet, you really should – it’s impressively fast.
org-lint – an Org mode checker using Emacs’ built-in
org-lint. It detects issues
like invalid links, dead links, and duplicate IDs. This one went through a
rewrite mid-cycle to run in the current Emacs process instead of a --batch
subprocess, which eliminates false warnings for source block languages
provided by external packages.
Security
We’ve mitigated
CVE-2024-53920 in the
emacs-lisp checker by disabling local eval directives and restricting local
variables to safe values during byte-compilation. This was an important fix –
byte-compilation involves macro expansion, which means untrusted Elisp files
could potentially execute arbitrary code during syntax checking. A follow-up fix
for Emacs 30+ using trusted-content is in the works.1
ShellCheck Improvements
The sh-shellcheck checker got some love in this release:
New flycheck-shellcheck-infer-shell option to let ShellCheck auto-detect the shell dialect
flycheck-shellcheck-args for passing extra command-line arguments
flycheck-shellcheck-enabled-checks to enable optional checks via --enable
Quality-of-Life Improvements
A bunch of small but welcome improvements:
flycheck-command-map now works as a prefix command with keymap-set and friends – no more need for the define-key workaround
Buffers are automatically re-checked after revert-buffer, so global-auto-revert-mode users get up-to-date diagnostics without manual intervention
The mode-line indicator now includes info-level errors (format: errors|warnings|infos)
The python-ruff checker got an error explainer, so you can quickly look up what a rule means
Pyright rule names (like reportGeneralTypeIssues) now show up as error IDs
Bug Fixes
This release includes a massive number of bug fixes – over 25 of them. Some highlights:
Fixed error pattern matching in non-English locales by forcing LC_ALL=C for checker processes
Fixed the rust checker on Windows (no more /dev/null errors)
Fixed flycheck-navigation-minimum-level being ignored in some cases
Fixed compilation warnings on Emacs 30
Fixed several issues with the python-ruff, tex-chktex, emacs-lisp, and awk-gawk checkers
sass, scss, sass/scss-sass-lint, scss-lint (Ruby Sass is dead, use Stylelint)
eruby-erubis, eruby-ruumba (Erubis abandoned since 2011)
css-csslint (abandoned since ~2017, use Stylelint)
coffee-coffeelint, protobuf-prototool, nix-linter
If you’re still using any of these tools… well, it’s probably time to upgrade!
Documentation Site
flycheck.org got a visual refresh – we’ve switched
from the old Alabaster Sphinx theme to Furo, which
gives the site a clean, modern look with proper dark mode support and better
mobile responsiveness. We’ve also added prominent GitHub links at the top of
every page, making it easier to jump to the source or file issues.
I hate the old theme, so this one is a major win in my book! I’ve also took some
effort to make sure the documentation accurately reflects the current state of project,
as here and there things were out-of-sync.
Flycheck is Alive and Well
I know that many people wrote Flycheck off when Flymake got tight integration
with Eglot (they share a maintainer, after all), and I understand why – it’s
hard to compete with something that’s built into Emacs and “just works” out of
the box. But Flycheck continues to advance and evolve. We have excellent Eglot
support via flycheck-eglot, a
massive library of built-in checkers,2 and a feature set that Flymake still
can’t match in many areas.
More importantly, Flycheck’s biggest strength has always been that it’s
community-driven and easy to contribute to. Adding a new checker is
straightforward, PRs get reviewed quickly, and you don’t have to go through the
FSF copyright assignment process. That matters, and it’s why the project keeps
growing.
Epilogue
I’m pretty happy with this release. The combination of new features, security
hardening, and cleanup of dead checkers puts Flycheck in a much healthier
state. Looking ahead to Flycheck 37, I’m thinking about tree-sitter integration,
more new checkers (OCaml and Swift are already in the pipeline), and potentially
dropping support for Emacs 27 so we can take advantage of newer APIs.
The plan is still hazy, but it’s starting to take some shape…
As always, contributions are very welcome! If there’s a lint tool you use that
Flycheck doesn’t support yet, PRs are the fastest way to make it happen.
As mentioned above, Flycheck’s biggest strength remains its community, and are
the community.
Almost 3 years after the rebirth of adoc-mode I’m happy to announce
that adoc-mode 0.8 is finally out! This is a massive release
that has been cooking for way too long, but I think it was worth the wait.
Let me walk you through the highlights.
Native Code Block Highlighting
This was the flagship feature I teased at the end of my 0.7 announcement, and
it’s easily the most impactful change in this release. Source code blocks now get
fontified using the appropriate language major mode – Ruby code looks like it
does in ruby-mode, Python code looks like python-mode, and so on.
The feature is enabled out-of-the-box with a sensible default – only code blocks
of 5000 characters or fewer are fontified natively, to avoid performance
issues with very large blocks. You can set adoc-fontify-code-blocks-natively
to t if you want unlimited fontification, or to nil to disable it entirely.
Inline Image Preview
You can now see images directly in your buffer instead of just staring at
image::path/to/screenshot.png[]. Image previews are displayed automatically
when you open a file (controlled by adoc-display-images), and you can toggle
them on and off with adoc-toggle-images.
Right-clicking on an image link gives you a context menu to generate or remove
the preview for that specific image. Remote images are supported too – set
adoc-display-remote-images to t if you want to fetch and display images from
URLs.
Navigation
A couple of welcome additions here:
adoc-follow-thing-at-point (bound to C-c C-o and M-.) lets you follow
URLs (opens in browser), include:: macros (opens the referenced file), and
cross-references (jumps to the anchor). It’s one of those features that once
you have it, you can’t imagine living without.
adoc-goto-ref-label (C-c C-a) jumps to an anchor by ID, with a smart
default pulled from the xref at point.
The imenu index is now hierarchical – headings are nested under their parent
sections, which makes navigating large documents much more pleasant.
Asciidoctor Inline Macros
adoc-mode now highlights Asciidoctor-specific inline macros like kbd:[],
btn:[], menu:[], pass:[], stem:[], latexmath:[], and asciimath:[].
These were previously ignored by font-lock and would just blend in with regular
text.
I’m a heavy user of Antora, and I really appreciate this one.
Bug Fixes Galore
This release squashes a lot of long-standing bugs:
Fixed a noticeable lag when typing in code blocks (this one was particularly annoying).
Fixed an Emacs hang caused by escaped curly braces in attribute references.
Fixed multiline font-lock for inline formatting (bold, italic, etc. spanning multiple lines).
Prevented auto-fill-mode from breaking section title lines.
Prevented Flyspell from generating overlays for links and other non-prose regions.
Fixed table delimiter highlighting (was limited to 4 columns).
Fixed unconstrained monospace delimiters.
And quite a few more – check the release notes for the full list.
Internal Improvements
I’ve also spent some time cleaning up the internals:
The minimum Emacs version is now 28.1 (up from 26).
Deprecated AsciiDoc backtick-apostrophe quote styles (not supported by
Asciidoctor) have been removed.
Two-line (Setext) titles are now disabled by default, since Asciidoctor has
deprecated them.
Image display and tempo templates have been extracted into separate files
(adoc-mode-image.el and adoc-mode-tempo.el), making the main file more
manageable.
The codebase is in much better shape than it was 3 years ago, but there’s still
some room for improvement.
One More Thing: asciidoc-mode
I did eventually act on that idea I mentioned in my 0.7 post – I created
asciidoc-mode, a tree-sitter-based
AsciiDoc major mode for Emacs 30.1+. It’s a very different beast from adoc-mode
– focused on the essentials (highlighting, navigation, folding) and leveraging
tree-sitter for accurate and performant parsing.
If you’re on Emacs 30+ and prefer a lighter-weight editing experience,
asciidoc-mode might be a good fit. If you want the full kitchen sink –
image previews, tempo templates, native code block highlighting – adoc-mode is
still the way to go. Both packages are maintained by me and they complement each
other nicely.
Epilogue
adoc-mode 0.8 is available on NonGNU ELPA, MELPA Stable,
and MELPA. Upgrading is just a M-x package-install away.
I’d like to thank everyone who contributed bug reports, pull requests, and
feedback over the past 3 years. Open-source is a team sport and this release
wouldn’t have happened without you. You rock!
That’s all I have for you today. Keep hacking! And keep writing… in AsciiDoc, of course!
Most Emacs Org users would concur that Org mode is a magnificent tool for capturing and communicating thought. That said, Org mode’s vast set of features can be daunting to master. A common guidance for new users is to take it slow: incrementally learn a subset of Org’s features as you need them.
A big reason for Org mode’s steep learning curve is that it adopts Emacs’ unfortunate culture of compelling users to memorize keybindings. Learning a distinct keybinding for each Org command (remember I said vast feature set?) is onerous, so a different tack is made: reuse the same keybinding but have it possess different behavior based on context. This context is usually tied to the type of structure the point (aka cursor) is in. For example, if the point is in a source block, the binding C-c C-c would execute it, but if the point is on a checkbox item, then C-c C-c would toggle its checked state. Taking this approach lowers the effort to recall a keybinding at the cost of recalling what its contextual behavior would be. In practice, using such overloaded keybindings is…okay. But I’d argue that we could have a more usable interface, leading to the point of this post:
Readers of this blog will know that Casual is my project to re-imagine the primary user interface for Emacs using keyboard-driven menus. If this is new to you, I highly recommend reading this introduction to it.
Primary to the design of the Casual Org menus is to be context-sensitive, only showing a subset of Org mode commands that are relevant. This set of context-sensitive commands is opinionated, but seeks to provide utility to users both new and experienced with Org.
Shown below is a demo of Casual Org at work:
While the design of this UI has been months in the making, there is nothing like real-world use and feedback. Constructive input is appreciated, especially if you are relatively new to using Org.
A great deal of thanks goes out to the maintainers of and contributors to Org mode and to the community that uses it. If you are able, please support it. Also, if you find Casual useful, I’d appreciate a coffee as well.
The standard advice people get when they complain about some bug or difficulty with Emacs is to restart it with -q or -Q to see if the problem persists. The problem with that advice is that the difficulty may be with a package. In that case you want to load at least the offending package and its configuration. That used to involve moving or renaming your init.el so that you could install the minimal init.el that produced the problem. As of Emacs 29 that got easier with the introduction of the --init-directory parameter that tells Emacs to load it’s configuration from a different directory.
Protesilaos Stavrou (Prot) has a nice post that explains how to use this feature when you experience a problem and want to file a bug report. The main problem, of course, is what to put in your new init.el. Prot recommends using use-package with :ensure t (and whatever configuration you’re using) to load the package.
Sometimes you might have to build the package from source rather than simply loading it from one of the ELPA repositories. Prot discusses how to do this using package-vc-install to grab the source directly from its git repository. He has some sample code showing how to do this.
Finally, he has some general recommendations that you can use to make the developer’s life a bit easier. On the other hand, if you are the package developer, the --init-directory trick can save a lot of time in locating bugs. In either case, take a look at Prot’s post. There’s a lot of good information in it.
Last week I wrote about Álvaro Ramírez’s new Winpulse package. The purpose of the package is to flash the window receiving focus when you use the other-window command. I wrote that I chose a different solution—changing the color of the mode line—that I felt was better because it provided a lasting indication of the active window.
In his latest Bending Emacs video, Ramírez expounds a bit more on why he chose the solution he did. The main reason appears to be that he sometimes turns off the mode line in some of his windows so my solution obviously wouldn’t work in that situation. Take a look at his video to see his complete argument.
In my previous post on Winpulse, I noted that you could, of course, use both his and my solution. I don’t think that would be worthwhile for me but in his video, Ramírez mentions Pulsar, a package similar to Winpulse that flashes the current line of the buffer getting focus instead of the entire window.
That seems to me a be something I might want to do since it helps locate the point in the new window. As always, Emacs lets you have things exactly the way you want them.
Ramírez and I tend to like the same sorts of things so our disagreement on this issue initially puzzled me. The answer, I think, is that when he switches windows, he just cycles through all the windows in the frame. I, on the other hand, use ace-window that lets me choose the window I want to change to directly. Again, this is a matter of choice but it helps explain why he and I chose different solutions.
The Bending Emacs 11 video is 8 minutes and 45 seconds so it such be easy to find time for it. If you’d like an enhanced way of seeing what window you’re switching to, take a look
Speaking of things that don’t matter at all , over at the Emacs reddit, reddit_enjoyer_47 wants to know how big our init.el files are. Despite my dismissal of the question as of no importance, it turns out that the answers are interesting. The answers range from about 20 lines to about 7,000 lines. For the record, mine is 2527,
Most of the folks with large init files say they have been using Emacs for a long time. That make sense because the files just naturally accrete by adding small pieces that may outlive their usefulness. More importantly, though, long term users tend to adapt Emacs to their individual needs and the usual way of doing that is by adding packages and changing default options.
More interesting to me are users with tiny configurations. It’s hard for me to fathom how you can make good use of Emacs with a configuration that small. A couple of the responders with small inits either listed them or provided a link to it. They appear to be using Emacs as a single purpose editor, usually for coding in a specific language.
Others take pride in having a small config or perhaps consider it some sort of moral necessity. One even set himself a (small) limit an requires himself to refactor his config if it exceeds that limit. That’s fine, I suppose, but it seems to me that it merely serves to limit the usefulness of Emacs.
Much of the maintenance work I do for my packages involves
correspondence with users about potential bugs. Sometimes, a user will
encounter a problem that I cannot reproduce on my end. I thus try to
recreate the bug in a pristine environment and ask my correspondent to
do the same.
This has become easier since Emacs 29, which introduced a command-line
flag called --init-directory. It is responsible for loading the
init.el that is present in the given directory. For example:
# From a terminal or shell, run something like this:
emacs --init-directory=/tmp/test-emacs/
In other words, you can keep your regular configuration intact while
launching Emacs with another set of options.
Create the test init.el file
Have a directory that is unrelated to your regular Emacs
configuration. Then write the init.el inside of it.
Because I do this frequently, I prefer to use the standard Linux path
/tmp/. Its files get deleted as soon as I switch off the computer,
which is exactly what I want in this case.
As such, if there is a bug with, say, the modus-themes, I will work
with this file path /tmp/modus-themes/init.el.
But the exact location of the directory does not matter, so choose
what makes sense to you.
Write the minimum necessary code
In that init file, include only the code that is needed to reproduce
the bug.
Since you want to have the package installed, it makes sense to write
a use-package declaration for it. Include the :ensure t directive
as it instructs the built-in package manager to install the package if
it is not already available.
;; Contents of the init.el...(use-packagemodus-themes:ensuret:config(setqmodus-themes-common-palette-overrides'((fringeunspecified)(border-mode-line-activeunspecified)(border-mode-line-inactiveunspecified)))(setqmodus-vivendi-palette-overrides'((bg-main"#1e1f22")(fg-main"#bcbec4")))(load-theme'modus-vivendit))
Install from source, if necessary
If you are using an alternative to package.el like straight or
elpaca, then the aforementioned :ensure t will likely not suffice:
you need to build the package from source. To this end, Emacs has the
function package-vc-install. Some of my recent packages have sample
code that relies on this approach. For instance:
(use-packagegnome-accent-theme-switcher:demandt:init;; Then upgrade it with the command `package-vc-upgrade' or `package-vc-upgrade-all'.(unless(package-installed-p'gnome-accent-theme-switcher)(package-vc-install"https://github.com/protesilaos/gnome-accent-theme-switcher.git")):bind(("<f5>".gnome-accent-theme-switcher-toggle-mode)("C-<f5>".gnome-accent-theme-switcher-change-accent)):config(gnome-accent-theme-switcher-mode1))
In the above snippet package-vc-install will pull the latest commit
from the main branch, though it can even get a specific commit. Read
its documentation with M-x describe-function.
What matters is that you fetch the version which you are running in
your personaly configuration.
Launch Emacs with this configuration
From the command-line, run something like the following:
emacs --init-directory=/tmp/test-emacs/
This will launch a new instance of Emacs. The use-package you placed
there will do the work to install the package. After that you are
ready to reproduce the bug in this clean setup.
Write down all the steps
To help the maintainer identify the source of the trouble, keep a
record of all the steps you followed. Some bugs show up when the
package is loaded, but others are triggered only after a specific
action is performed.
Normally, Emacs will pop up a *Backtrace* buffer when it encounters
an error. Copy its contents and send them to the maintainer, together
with the init.el you used, and the list of the steps you followed.
Sometimes you just need to re-install the package
It sometimes happens that you install a package and it is completely
broken. Although this looks bad, it may not even be a bug, but an
issue with the old bytecode you had on your system from the previous
version of the package.
Do M-x package-delete, select the package, restart Emacs, and then
M-x package-install to install the package anew. If everything
works, then the problem is gone and you do not need to tell the
maintainer about it.
Make it easier for maintainers to help you
With this knowledge, you can provide high quality bug reports for the
packages you rely on. Good luck!
At times, even purchased music excludes album covers in track metadata. For those instances, ready-player-mode offers M-x ready-player-download-album-artwork, which does as it says on the tin. The interactive command offers a couple of fetching providers (iTunes vs Internet Archive / MusicBrainz) to grab the album cover. The thing is, I often found myself trying one or the other provider, sometimes without luck. Today, I finally decided to add a third provider (Deezer) to the list. Even then, what's the point of manually trying each provider out when I can automatically try them all and return the result from the first successful one? And so that's what I did.
In addition to offering all providers, M-x ready-player-download-album-artwork now offers "Any", to download from the first successful provider. Now, why keep the option to request from a specific provider? Well, sometimes one provider has better artwork than another. If I don't like what "Any" returns, I can always request from a specific provider.
While on the subject, I also tidied the preview experience up and now display the thumbnail in the minibuffer. In any case, best to show rather than tell.
Enjoying your unrestricted music via Emacs and ready player mode? ✨sponsor✨ the project.
I've used projectile ever since I created my own Emacs config. I have a vague
memory choosing it because some other package only supported it. (It might have
been lsp-mode, but I'm not sure.) Anyway, now that I'm trying out eglot, again,
I thought I might as well see if I can switch to project.el, which is included
in Emacs nowadays.
A non-VC project marker
Projectile allows using a file, .projectile, in the root of a project. This
makes it possible to turn a folder into a project without having to use version
control. It's possible to configure project.el to respect more VC markers than
what's built-in. This can be used to define a non-VC marker.
Since I've set vc-handled-backends to nil (the default made VC interfere
with magit, so I turned it off completely) I had to add ".git" to make git
repos be recognised as projects too.
Xref history
The first thing to solve was that the xref stack wasn't per project. Somewhat
disappointingly there only seems to be two options for xref-history-storage
shipped with Emacs
xref-global-history
a single global history (the default)
xref-window-local-history
a history per window
I had the same issue with projectile, and ended up writing my own package for
it. For project.el I settled on using xref-project-history.
Projectile has a function for jumping between implementation and test. Not too
surprisingly it's called projectile-toggle-between-implementation-and-test. I
found some old emails in an archive suggesting that project.el might have had
something similar in the past, but if that's the case it's been removed by now.
When searching for a package I came across this email comparing tools for
finding related files. The author mentions two that are included with Emacs
ff-find-other-file
part of find-file.el, which a few other functions and
a rather impressive set of settings to customise its behaviour.
find-sibling-file
a newer command, I believe, that also can be
customised.
So, there are options, but neither of them are made to work nicely with
project.el out of the box. My most complicated use case seems to be in Haskell
projects where modules for implementation and test live in separate (mirrored)
folder hierarchies, e.g.
src
└── Sider
└── Data
├── Command.hs
├── Pipeline.hs
└── Resp.hs
test
└── Sider
└── Data
├── CommandSpec.hs
├── PipelineSpec.hs
└── RespSpec.hs
I'm not really sure how I'd configure find-sibling-rules, which are regular
expressions, to deal with folder hierarchies like this. To be honest, I didn't
really see a way of configuring ff-find-other-file at first either. Then I
happened on a post about switching between a module and its tests in Python.
With its help I came up with the following
The order of rules in ff-other-file-alist is important, the first match is
chosen.
(buffer-file-name) can, and really does, return nil at times, and
file-name-directory doesn't deal with anything but strings.
The entries in ff-search-directories have to be relative to the file in the
current buffer, hence the rather involved varlist in the when-let*
expression.
With this in place I get the following values for ff-search-directories
Over the last year or so I’ve ended up with various SVG functions that I think may be of some general utility, so I’ve separated them out into its own file and put it on Microsoft Github.
svg-opacity-gradient
svg-outline
svg-multi-line-text
svg-smooth-line
Some are pretty involved, but most are pretty straightforward. Of course, one can add an infinite number of these, and the more flexible the functions are, the more you can just end up “open-coding” them for your specific project, but I think these have some utility, so there you go.
I am happy to announce that my command line program for binding plain text writing files is live on my GitHub, with full documentation here, and I could not be happier with how it turned out.
But, you may ask, what is it, and why does it exist?
Quoting from the docs:
Binder applies source/build separation to writing—you author in plain text files and compile to a formatted manuscript.
This upends the word processing (or “WYSIWYG” editor) model that requires the writer to edit a build file (delivery format) as a source file. So you can write with the precision of a software engineer rather than the brute force labor of a printer’s devil.
There’s nothing wrong with using word processors like Microsoft Word, but these tools were designed for print formatting first and foremost. That’s why you have so many options for font sizes, page styles, and flow parameters, but very few text navigation or file organization features. Using such antiquated tools for writing today is like composing a novel by hand-setting movable type on a letterpress printer.
Composing in plain text frees you from those constraints and opens you up to the wide world of text editors with advanced text manipulation features, file and chapter organization, portability, and version control.
For my readers already familiar with Emacs and Org Mode, separating source files from build files is quite common. We can do that by exporting Org Mode documents to whatever format we’d like.
However, I wanted a tool that was text editor agnostic and focused more specifically on fiction writing and manuscript generation. So now, it doesn’t matter if you like Emacs, Vim, VS Code, Sublime, Gedit, or whatever, you can make good use of this program.
I particular like the --stats feature that outputs not only the total word count of all files, but also paragraph, sentence count, and a Flesch reading ease score.
I recently built a little package to flash Emacs windows as you switch through them, so I might as well showcase it in a new Bending Emacs episode, so here it goes:
Last week, I wrote about Theena Kumaragurunathan’s post on writing Science Fiction with Emacs and how Emacs was the inspiration for the brain computer interface in his latest SF novel. In a followup post, Kumaragurunathan discusses the philosophical and design choices that make Emacs such a great tool for writers like him. He says,
Good UX comes from good design. The former is the harvest that comes
about from sowing in the latter.
He describes the manifestation of that principal in Emacs as,
Emacs feels timeless to me because it made a simple, stubborn choice
early on: let the user shape the tool.
He goes on to describe this as “giving the user agency” and explains how he’s exploited that agency to integrate Emacs with other tools, such as Org-roam and Hyperbole, to build himself a writing environment tailored specifically for him.
As well as being a novelist, Kumaragurunathan is also a screenwriter so he relies on two distinct Emacs modes for his writing:
There is, he says, no other writing environment that can support both long-form writing and screenwriting.
It’s a nice article and well worth spending a few minutes on. His discussion of how Emacs meets his writing needs and the writing environment he’s evolved from it is instructive and entertaining.
I noticed that in all terminal emulators I tried (iTerm, Ghostty, kitty), I rely on a system-global shortcut to show me a session that I use whenever I need to terminalize something right now, right there, like ffmpeg something or bulk delete with a pattern or convertmagick an image. I had this for ages, and I miss it when it’s not there.
I’ve had iTerm show this in the style of a classic Quake Console – yes, from the game; a console that slides in from the top of the screen, and which in games I mostly used for cheat codes. I used this a lot in Unreal to make the intro cinematic interactive and play in that cool castle. (Ghostty has settings that made the sizing a bit trickier but workable. But eventually Kitty’s settings convinced me to try a centered window instead of one sticking to the top. Kitty also convinced me of making the console not hide automatically when it loses focus, which makes pasting into it much nicer.)
A couple of week after trying terminal emulators, I inevitably wonder why I’m spawning a window that only shows zsh when so many file operations and image manipulation and … well, basically everything is nicer in Emacs.
Wouldn’t it be possible to dedicate a GUI window just to this purpose?
Screenshot of a dark themes Emacs frame in front of the Zen browser; note that the Emacs frame has no window controls and is just a rectangular window into the abyss
So here we are. It’s possible, and now it’s a package.
It took me a few hours to over-engineer a spec-driven approach to make this possible with the help of LLM’s, Claude Code in particular. I archived the whole history of the thought-process of planning and then let Claude Code orchestrate an implementation with subagents in ~50min while I went groceries shopping with my daughter. The whole process taught me how to make named Emacs daemon (aka server) sessions and use them in end-to-end testing without interfering with my own setup. I learned a bit about how Emacs frames can be configured. And it renewed my love for Emacs and its malleability – there were so many cool alternatives to implement the same feature along the way that it sometimes was genuinely hard to pick a path!
Having a stochastic apparatus come up with fringe ideas to use and configure the package made me curious to try a couple of these myself, too. So all in all, the Claude Code driven experience was a success for me.
Note: I only tested this on macOS because I don’t have a Linux with any GUI ready at the moment.
Extensive documentation in the readme will tell you how to configure things, and it comes with recipes to get you started quickly.
Unlike other visor-style packages that existed already, this one is not animating anything. You could get transparency if you want with a simple tweak.
I still stick to the center location with a slight offset. I believe there’s value in caring for small UI details like this, to nudge the window a couple of percentage points off center. The window sizing should just work and make sense and do the right thing.
The customize group is self-documenting and covers all the settings.
You can reset the frame when you dismiss it, or restore whatever was inside. You could force it to always show your favorite eshell session.
In the end, it’s just managing a GUI window for you so that you can bind a system-global hotkey to it. (I use Keyboard Maestro on Mac.)
Hire me for freelance macOS/iOS work and consulting.
I've switched back to lsp-mode temporarily until I've had time to fix a few
things with my eglot setup. Returning prompted me to finally address an
irritating behaviour with lsp-ui-doc.
No matter what I set lsp-ui-doc-position to it ends up covering information
that I want to see. While waiting for a fix I decided to work around it. It
seems to me that this is exactly what advice is for.
I came up with the following to make sure the frame appears on the half of the
buffer where point isn't.
The various demos relating this were seriously getting out of hand. I
hope to properly demo them, and provide some background.
I will try to keep improving and updating this page regularly.
In-tree PALE days (tiling)
PALE stands for “Picture and Animation Library for Emacs”, it grew out
of struggles with rendering in Emacs Reader. Wherein quickly changing
the page image would lead to memory leaks– the reason for the delay
in implementing text-features (like selection and friends). We
eventually decided that ‘tiling’ that is, dividing the page into
smaller images would be a good solution, ( though we ended up finding
an easier to implement solution later).
PALE rendering a bouncing square.
PALE rendering gradient animations
Pale rendering 20 bouncing squares inside an Emacs buffer.
Emacs playing the video of RMS singing the hacker song using PALE.
Separate PALE repository (tiling)
Divya was so excited by PALE's video playing that he just extracted
PALE into a separate repository.
PALE rendering smooth in 30 FPS without major flickering.
bad apple video playing in an emacs buffer.
Canvas API
Minad (Daniel Mandler) saw Divya's posts, and basically said,
wow, that is so much work, actually I had an idea to add a canvas
into Emacs long ago, let me just do that: here is a patch.
funny animation with a lot of colored rectangles and emacs logos appearing everywhere, with a red falling star-like streak appearing in the background, chaos.
funny animation with a lot of colored rectangles and emacs logos appearing everywhere, with a red falling star-like streak appearing in the background, chaos. With a FPS counter at around 400 at the bottom (minibuffer).
Minad ported Doom to Emacs.
doom running inside an emacs buffer.
Simple cube rendered by OpenGL inside Emacs
Divya ported SuperTuxKart to Emacs.
SuperTuxKart inside Emacs at 60FPS
PALE
Divya ported PALE to Canvas (no more tiling hacks).
A 1080p video playing inside Emacs
Emacs killing a video being played inside a canvas and yanking it in different buffers.
ob-canvas
Daniel added shaders to org-babel.
Emacs displaying an Org mode buffer with an Org babel block generating an animated graphic.
Lots of cool stuff this week! I'm looking forward to checking out the new futur library for async programming, and the developments around embedding graphics in a canvas in Emacs look interesting too (see the Multimedia section). Also, the discussion about making beginner configuration easier could be neat once the wrinkles are ironed out. Enjoy!
A few weeks ago I wrote about my toy train timetable clock. I used it with my son a few times since then, and it worked great. However, some things that I imagined would be a good idea turned out to be a bit worse UI-wise.
Tool calling has landed in ollama-buddy!, it’s originally not something I really thought I would end up doing, but as ollama has provided tool enabled models and an API for this feature then I felt obliged to add it. So now LLMs through ollama can now actually do things inside Emacs rather than just talk about them, my original “do things only in the chat buffer and copy and paste” might have gone right out the window in an effort to fully support the ollama API!
What is Tool Calling?
The basic idea is simple: instead of the model only generating text, it can request to invoke functions. You ask “what files are in my project?”, and instead of guessing, the model calls list_directory, gets the real answer, and responds with actual information.
This creates a conversational loop:
You send a prompt
The model decides it needs to call a tool
ollama-buddy executes the tool and feeds the result back
The model generates a response using the real data
Steps 2-4 repeat if more tools are needed
All of this is transparent - you just see the final response in the chat buffer.
The new ollama-buddy-tools.el module ships with 8 built-in tools:
Safe tools (read-only, enabled by default):
read_file - read file contents
list_directory - list directory contents
get_buffer_content - read an Emacs buffer
list_buffers - list open buffers with optional regex filtering
search_buffer - regex search within a buffer
calculate - evaluate math expressions via calc-eval
Unsafe tools (require safe mode off):
write_file - write content to files
execute_shell - run shell commands
Safe mode is on by default, so the model can only read - it can’t modify anything unless you explicitly allow it, I think this is quite a nice simple implementation, at the moment I generally have safe mode off but always allowing confirmation for each tool action, but of course you can configure as necessary.
Example Session
With a tool-capable model like qwen3:8b and tools enabled (C-c W):
>> PROMPT: What defuns are defined in ollama-buddy-tools.el?
The model calls search_buffer with a regex pattern, gets the list of function definitions, and gives you a nicely formatted summary. No copy-pasting needed.
Custom Tools
You can register your own tools with ollama-buddy-tools-register:
The registration API takes a name, description, JSON schema for parameters, an implementation function, and a safety flag. The model sees the schema and decides when to call your tool based on the conversation.
A ⚒ symbol now appears next to tool-capable models everywhere - header line, model selector (C-c m), and model management buffer (C-c M). This follows the same pattern as the existing ⊙ vision indicator, so you can see at a glance which models support tools.
That’s it. Pull a tool-capable model (qwen3, llama3.1, mistral, etc.) or use an online tool enabled model from ollama and start chatting. Next up is probably some web searching!, as again the ollama API supports this, so you will be able to pull in the latest from the interwebs to augment your prompt definition!
People from the University of Oxford are organising events related to
free software: FLOSS @ Oxford.
On Thursday, the 12th of March 2026, at 6 PM United Kingdom time (GMT)
I will give a talk titled Computing in freedom with GNU Emacs.
My intention is to introduce Emacs to a wide audience. Participation
is open to everyone.
I will post a reminder as we get closer to the date of the event.
Looking forward to it!
I've been using Emacs for a while now, and if it has taught me anything, it's not to settle.
Every time I open my configuration, I discover something I can do better, a shortcut I didn't know about, a package that changes the way I work. It has taught me to develop patience, not to take the easy path, because nothing works on the first try: I read, I test, I break, I fix. And in that process, without realizing it, I'm building something that is uniquely mine. I will die without having the perfect configuration, and that's a good thing.
This has changed my perception of software itself. For me, Emacs stopped being just an editor (or an Elisp interpreter, no need to get technical) a long time ago. I experience it as a protocol between my tools and myself. A virtual machine that adds a layer of isolation that allows me to work the way I want, regardless of the operating system. I create utilities in Elisp, not in the language compatible with my platform. I'm in a development environment that is also my working environment. It's strange and wonderful at the same time.
Then there's the community. I've met more people thanks to Emacs, or Org Mode, than any other platform or programming language. It's full of talent and people in love with the editor. I recommend exploring the mailing lists, IRC channels, or Org Social itself.
I don't want to leave you with any deep reflection, just to encourage you. It's not easy or fast, but it is rewarding.
Regarding to Emacs crashing: AI found that my Drawin version of Emacs might be the cause. I’ve been using Emacs for Mac OS which ran darwin21.6.0 - old… (current version on macOS is 25.3.0)
The question is, is this really the problem, or more like the AI making stuff up? It makes sense to me that an Emacs build is based on a slightly older Darwin version, though I’m not an expert. Another reason for this suspicion is that the AI assistant recommended emacs-plus without considering emacs for macOS. I know both are solid based on my own research, and I used Emacs-plus (available with Homebrew) for a while. I’ve been using Emacs for macOS for about a year, and it’s been fine until I believe I updated to the latest macOS, which everyone seems to hate, so I wouldn’t be surprised if it has to do with macOS 26 more than anything else.
For now, I installed the latest version of Emacs for macOS, which was built on darwin23.2 - still behind, but not as bad. If this is what’s running Emacs 30.2 for so many Mac users, it further confirms my suspicion that the Darwin version is not really the issue. But maybe I’m wrong.
I posted my question on Reddit to get some feedback, and out of curiosity, see what the Darwin version is for Emacs-plus (don’t want to install it right now). Guess we’ll see.
Batsov’s post lays out what he feels was wrong with the existing implementations. You can read the details in his post but the TL;DR is that they’re either old and not well maintained or they use the old style font-locking and indentation mechanisms that have been replaced by TreeSitter.
Batsov says that tuareg still has more features than Neocaml but that Neocaml has all the features you’re apt to need. Batsov is, of course, still working on adding additional features including support for more file types and improvements to structured navigation using TreeSitter. He is, of course, very interested in bug reports and pull requests.
neocaml 0.1 is finally out! Almost a year
after I announced the
project,
I’m happy to report that it has matured to the point where I feel comfortable
calling it ready for action. Even better - neocaml recently landed in
MELPA, which means installing it is now as easy
as:
1
M-x package-install <RET> neocaml <RET>
That’s quite the journey from “a fun experimental project” to a proper Emacs
package!
Why neocaml?
You might be wondering what’s wrong with the existing options. The short answer -
nothing is wrong per se, but neocaml offers a different set of trade-offs:
caml-mode is ancient and barely maintained. It lacks many features that
modern Emacs users expect and it probably should have been deprecated a long time ago.
tuareg-mode is very powerful, but also very complex. It carries a lot of
legacy code and its regex-based font-locking and custom indentation engine show
their age. It’s a beast - in both the good and the bad sense of the word.
neocaml aims to be a modern, lean alternative that fully embraces
TreeSitter. The codebase is small, well-documented, and easy to hack on. If
you’re running Emacs 29+ (and especially Emacs 30), TreeSitter is the future
and neocaml is built entirely around it.
Of course, neocaml is the youngest of the bunch and it doesn’t yet match
Tuareg’s feature completeness. But for many OCaml workflows it’s already more
than sufficient, especially when combined with LSP support.
I’ve started the project mostly because I thought that the existing
Emacs tooling for OCaml was somewhat behind the times - e.g. both
caml-mode and tuareg-mode have features that are no longer needed
in the era of ocamllsp.
Let me now walk you through the highlights of version 0.1.
Features
The current feature-set is relatively modest, but all the essential functionality
one would expect from an Emacs major mode is there.
TreeSitter-powered Syntax Highlighting
neocaml leverages TreeSitter for syntax highlighting, which is both more
accurate and more performant than the traditional regex-based approaches used by
caml-mode and tuareg-mode. The font-locking supports 4 customizable
intensity levels (controlled via treesit-font-lock-level, default 3), so you
can pick the amount of color that suits your taste.
Both .ml (source) and .mli (interface) files get their own major modes with
dedicated highlighting rules.
TreeSitter-powered Indentation
Indentation has always been tricky for OCaml modes, and I won’t pretend it’s
perfect yet, but neocaml’s TreeSitter-based indentation engine is already quite
usable. It also supports cycle-indent functionality, so hitting TAB repeatedly
will cycle through plausible indentation levels - a nice quality-of-life feature
when the indentation rules can’t fully determine the “right” indent.
If you prefer, you can still delegate indentation to external tools like
ocp-indent or even Tuareg’s indentation functions. Still, I think most people
will be quite satisfied with the built-in indentation logic.
Code Navigation and Imenu
neocaml provides proper structural navigation commands (beginning-of-defun,
end-of-defun, forward-sexp) powered by TreeSitter, plus imenu integration
definitions in a buffer has never been easier.
The older modes provide very similar functionality as well, of course,
but the use of TreeSitter in neocaml makes such commands more reliable and
robust.
REPL Integration
No OCaml mode would be complete without REPL (toplevel) integration. neocaml-repl-minor-mode
provides all the essentials:
C-c C-z - Start or switch to the OCaml REPL
C-c C-c - Send the current definition
C-c C-r - Send the selected region
C-c C-b - Send the entire buffer
C-c C-p - Send a phrase (code until ;;)
The default REPL is ocaml, but you can easily switch to utop via
neocaml-repl-program-name.
I’m still on the fence on whether I want to invest time into making the REPL-integration
more powerful or keep it as simple as possible. Right now it’s definitely not a big
priority for me, but I want to match what the other older OCaml modes offered in that regard.
LSP Support
neocaml works great with Eglot and
ocamllsp, automatically setting the appropriate language IDs for both .ml and
.mli files. Pair neocaml with
ocaml-eglot and you get a pretty
solid OCaml development experience.
The creation of LSP really simplified the lives of a major mode authors like me, as now
many of the features that were historically major mode specific are provided by
LSP clients out-of-the-box.
That’s also another reason why you probably want to leaner major mode like neocaml-mode.
Other Goodies
But, wait, there’s more!
C-c C-a to quickly switch between .ml and .mli files
Prettify-symbols support for common OCaml operators
Automatic installation of the required TreeSitter grammars via M-x neocaml-install-grammars
Compatibility with Merlin for those who prefer it over LSP
The Road Ahead
There’s still plenty of work to do:
Support for additional OCaml file types (e.g. .mld)
Improvements to structured navigation using newer Emacs TreeSitter APIs
Improvements to the test suite
Addressing feedback from real-world OCaml users
Actually writing some fun OCaml code with neocaml
If you’re following me, you probably know that I’m passionate about both Emacs
and OCaml. I hope that neocaml will be my way to contribute to the awesome
OCaml community.
I’m not sure how quickly things will move, but I’m committed to making neocaml
the best OCaml editing experience on Emacs. Time will tell how far I’ll get!
Give it a Try
If you’re an OCaml programmer using Emacs, I’d love for you to take neocaml for
a spin. Install it from MELPA, kick the tires, and let me know what you think.
Bug reports, feature requests, and pull requests are all most welcome on
GitHub!
With my boring productivity system storing any read later links I wanted a faster way to open the link on the current line. Pressing M x brought up search and then I could trigger org-agenda-open-link which would open the link in my default browser.
Too many keystrokes, so I mapped the same command to SPC l g which roughly translates in my head as link go.
Add this to config.el if you want a similar setup.
(map! :leader
:desc "Org open link at point"
"l g" #'org-agenda-open-link)
My institution-calendar package for Emacs displays term+week
indicators in the *Calendar* buffer (which, by default, is produced
by the calendar command). This is useful, for example, for schools
who organise their work by terms, such as winter, spring, and summer,
with each having a set number of weeks. This is how the University of
Oxford will look like:
The package supports the universities of Oxford and Cambridge
out-of-the-box, though users can define their own institutions. I have
two institutions there to provide concrete examples. I am happy to add
more, but the idea is for users to maintain their own data.
Below I show a complete example using data for a school in Austria.
Write the calendar data
To make this work, you first need to specify the data. This has the
same structure as institution-calendar-oxford-university-dates and
thus passes the test of institution-calendar-valid-data-p. Look at
the code for Oxford to get an idea. Here is a sample:
This is an Austrian school that has two terms for the academic year
starting in 2025: wintersemester and sommersemester. The symbols
for those terms can be anything. Internally, the package uses the
first letter to form the week indicator, followed by the number of the
week within the given term.
Each term defines a start date and an end date as a list of integers
of the form (MONTH DAY YEAR). I picked a form that is consistent
with the way calendar.el represents the date, otherwise I would have
used a different standard.
Register your institution
The variable institution-calendar-user-entities contains all
user-defined institutions and their corresponding calendar data. Each
entry is a cons cell of the form (ENTITY . CALENDAR-DATA), where
ENTITY is an arbitrary symbol and CALENDAR-DATA is the symbol of a
variable that holds the data, as shown in the previous section.
In this example, I am calling the newly registered institution
austrian-school, which is how I can refer to it elsewhere. I am
associating this austrian-school with the calendar data of the
variable my-austrian-school-dates.
Make the institution-calendar-mode work for your institution
With the aforementioned in place, the user option institution-calendar-entity
can be set to the value of austrian-school:
If the institution-calendar-mode is enabled, then the regular
calendar command will display week indicators for this school. This
is good if you only need one calendar. But if you work with many
institutions and thus need to switch between their calendars, then
ignore this step and move to the next one. Or ignore it anyway if you
prefer to keep the M-x calendar intact.
Define a custom command for your institution
The macro institution-calendar-define-convenience-command makes it
trivial to define a command that produces a calendar buffer for the
given institution. This is like M-x calendar with the minor mode
institution-calendar-mode enabled, except it does not alter the
output of the calendar—so you can use them both (or, anyhow, use
as many as the institutions you care about).
;; This defines the command `institution-calendar-austrian-school'.;; Call `institution-calendar-austrian-school' with M-x or bind it to a key.(institution-calendar-define-convenience-commandaustrian-school)
Once you evaluate this macro call, you will get the command
institution-calendar-austrian-school. Use that to produce a calendar
that works with the austrian-school in particular. If you are
curious, M-x institution-calendar-oxford-university will still do
the right thing for the University of Oxford. Same for the command
institution-calendar-cambridge-university.
Use an intermonth header
The user option institution-calendar-include-intermonth-header adds
a header above the week numbers. By default, this only works with the
universities of Oxford and Cambridge. Though you can extend the
package to support your institution by adding to the value of the
variable institution-calendar-intermonth-headers. Thus:
You can skip this step if you do not plan to display the intermonth
header. Those are not shown by default.
Putting it all together
This is how your configuration of the institution-calendar may look like:
(use-packageinstitution-calendar:ensurenil; not in a package archive:init;; Install it from source.;; Then upgrade it with the command `package-vc-upgrade' or `package-vc-upgrade-all'.(unless(package-installed-p'institution-calendar)(package-vc-install"https://github.com/protesilaos/institution-calendar.git")):config(defvarmy-austrian-school-dates'((2025(wintersemester(982025)(262026))(sommersemester(2162026)(7102026)))))(add-to-list'institution-calendar-user-entities(cons'austrian-school'my-austrian-school-dates));; This defines the command `institution-calendar-austrian-school'.;; Call `institution-calendar-austrian-school' with M-x or bind it to a key.(institution-calendar-define-convenience-commandaustrian-school)(setoptinstitution-calendar-include-extra-week-numberst)(setoptinstitution-calendar-include-intermonth-headernil);; These are optional, if you want `M-x calendar' to work for your institution.(setoptinstitution-calendar-entity'austrian-school)(institution-calendar-mode1))
Aimé Bertrand has an nice post on how he exports scheduled items in his org agenda to his macOS calendar. He mostly lives in Emacs and Org mode but, like, me also likes to have his appointments and other scheduled events in his macOS calendar. The main attraction for doing this to me is that it will sync all your appointments to all your devices as well as share it with appropriate other people. Sadly, Emacs doesn’t run on iOS and given Apple’s no interpreters policy, probably never will.
Bertrand wasn’t concerned with keeping the two calendars synced; he only wanted to export his Org scheduled events to the macOS calendar. This turned out to be harder than you might think. The existing solutions didn’t quite do what he needed so he decided to roll his own.
You can read his post for the details. He has a couple of different ways of approaching the problem. The details aside, the takeaway for me is the flexibility of Emacs and its ability to interact with other applications that may never have heard of Emacs.
If you’re using Emacs on a Mac and would like to ensure that all your Org scheduled events are reflected on the system calendar, take a look at Bertrand’s post for one way of solving the problem.
In this short video I demonstate a new package for GNU Emacs that
synchronises the Emacs theme with the GNOME settings for accent color
and light/dark mode. Git repository here:
https://github.com/protesilaos/gnome-accent-theme-switcher.
Lin is a stylistic enhancement for Emacs’ built-in hl-line-mode. It
remaps the hl-line face (or equivalent) buffer-locally to a style that
is optimal for major modes where line selection is the primary mode of
interaction.
The idea is that hl-line-mode cannot work equally well for contexts
with competing priorities: (i) line selection, or (ii) simple line
highlight. In the former case, the current line needs to be made
prominent because it carries a specific meaning of some significance in
the given context: the user has to select a line. Whereas in the latter
case, the primary mode of interaction does not revolve around the line
highlight itself: it may be because the focus is on editing text or
reading through the buffer’s contents, so the current line highlight is
more of a reminder of the point’s location on the vertical axis.
This is the first tagged release since 2024-08-05. The package is in a
stable state: it does everything it is meant to. This version makes
some small refinements, mostly in how parts of the code are written.
Though there also are some nice user-facing changes:
The lin-gnome-accent-color-mode synchronises the accent colour of
the GNOME desktop environment with Lin’ own lin-face. This happens
live, so any buffers that are already using the lin-mode (directly
or via lin-global-mode) will get the updated colour.
The user option lin-gnome-accent-color-override-foreground
controls whether the faces that correspond to GNOME accent colours
should override the underlying text colour or not. This is useful
for improved colour contrast. The default is to not override the
foreground. Setting lin-gnome-accent-color-override-foreground to
non-nil changes that so, for example, the lin-face will be set
to lin-red-override-fg instead of lin-red (of course, faces can
be modified by users/themes to override the foreground anyway, so
this is about the default behaviour).
New faces to style the current line when lin-mode is enabled
include lin-purple, lin-orange, and lin-slate. Those do not
override the underlying foreground colours by default. Whereas
lin-purple-override-fg, lin-orange-override-fg, and
lin-slate-override-fg apply their background while also setting
the foreground (remember that you always control which face to use
by changing the user option lin-face).
The lin-global-mode skips all private buffers. These are buffers
that users normally do not interact with directly. Their names are
prefixed with a space and, by default, are hidden from the view of
switch-to-buffer and related commands.
The default value of the user option lin-mode-hooks now includes
the world-clock-mode-hook and xref--xref-buffer-mode-hook. The
former relates to the command world-clock, while the latter is
used by any command that produces Grep-like results via the built-in
Xref infrastructure (for example, my Denote package does that for a
few of its commands). The lin-mode-hooks is a list of hooks for
major modes that should use the Lin style for the selection line
highlight.
If you’ve ever enabled a new theme and noticed your tab-bar faces stubbornly hanging onto old colours or custom tweaks, I have found often that the tab-bar, tab-bar-tab, and tab-bar-tab-inactive faces don’t always blend cleanly with freshly loaded themes — especially of the older variety (a bit like me) and especially ones that came out before the tab bar was introduced into Emacs.
So how about a simple solution?, Can I implement something, that whenever I load a theme, the tab-bar faces update based on the theme’s default faces to establish a visually pleasant and coherent look?
Yes, yes I can!; the result is a tiny Elisp enhancement that hooks directly into Emacs’ theme-loading process.
Firstly however we need to have a method that will reliably pass over the themes default faces to the tab-bar. Here’s the function that realigns the tab-bar styling with your active theme:
This simply rebuilds the key tab-bar faces so they derive their colours from the current theme’s normal face definitions, so any old themes should now not leave the tab bar faces hanging.
Now for the function activation; Emacs 29 introduced enable-theme-functions, a hook that runs every time a theme is enabled — perfect for our use case, but as always I have my eye on older Emacs versions, so lets fall back to a classic approach: advice on load-theme.
Here’s a version‑aware setup that does the right thing automatically:
(if (version<= "29.1" emacs-version)
;; Emacs 29.1+ — use the official theme hook
(add-hook 'enable-theme-functions
(lambda (_theme)
(selected-window-accent-sync-tab-bar-to-theme)))
;; Older Emacs — fall back to advising load-theme
(progn
(defun selected-window-accent-sync-tab-bar-to-theme--after (&rest _)
(selected-window-accent-sync-tab-bar-to-theme))
(advice-add 'load-theme :after
#'selected-window-accent-sync-tab-bar-to-theme--after)))
With this tweak in place, every time you change themes, your tab-bar instantly updates, colours stay consistent, clean, and theme‑accurate without you having to do anything at all! The downside to this of course is that any newer themes that were created after the advent of the tab bar in Emacs will have their tab-bar faces overridden, but for me this solution is good enough and gives a pleasant coherent visual tab bar experience.
This is about a new package of mine: institution-calendar. It is not
going to be available on GNU ELPA. Users will have to install it from
source (code for this is further below). The reason is that the
predecossor to this package, oxford-calendar, was not accepted:
I will consider my options going forward, with whatever that means for
all my packages.
Overview
The institution-calendar package augments the M-x calendar buffer
to include indicators about the applicable term. Each term has week
numbers, which are displayed on the side of the regular calendar data.
The user option institution-calendar-entity specifies which
institution’s data to use. Currently, the value can be either
oxford-university or cambridge-university. Contact me and I will
add support for your institution.
Each term shows the week numbers it formally defines. For example, the
University of Oxford has three terms of 8 weeks each. When the user
option institution-calendar-include-extra-week-numbers is set to a
non-nil value, then an additional two weeks are added: week 0 for
one week before the term starts and an extra number after the term
ends. This is useful for scheduling purposes, such as to arrange
meetings in preparation of the work ahead or to report on what
happened.
The user option institution-calendar-include-intermonth-header
writes text above the institution’s week indicators. This makes it a
bit easier to tell them apart from the regular calendar data.
Showing the calendar
Enable the minor mode institution-calendar-mode to make all future
calls to M-x calendar use the relevant institution data.
If you do not want to affect the M-x calendar output, then use the
command institution-calendar: it is functionally equivalent to
having the aforementioned minor mode enabled, except it has no
permanent effect on M-x calendar—that will keep its original
appearance.
If, for whatever reason, you need to check the calendar of a specific
institution, then do M-x institution-calendar-cambridge-university
or M-x institution-calendar-oxford-university (more such commands
will be available to match any other institutions that this package
will support).
Installation and configuration
(use-packageinstitution-calendar:ensurenil; not in a package archive:init(unless(package-installed-p'institution-calendar)(package-vc-install"https://github.com/protesilaos/institution-calendar.git")):commands(institution-calendarinstitution-calendar-cambridge-universityinstitution-calendar-oxford-university):config(setoptinstitution-calendar-entity'oxford-university)(setoptinstitution-calendar-include-extra-week-numberst)(setoptinstitution-calendar-include-intermonth-headernil);; If you want to permanently change what M-x calendar shows, enable;; this mode. Otherwise, use the relevant command from the;; :commands listed above.(institution-calendar-mode1))
Backronyms: Interestingly Nothing Serving Teachers Implement Term
Utilities Took Inspiration from Oxford Novices… calendar;
Institution … Cambridge Added Lent Entry Notwithstanding Dates
Already Recorded (yes, I always have a lot of fun writing these!).
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!