Priests and other religious leaders of various persuasions have been known to use Emacs. It’s not common, of course, but Irreal has mentioned a couple of them—most recently Randy Ridenour—but in almost 16 years, I can’t remember more than two.
Until now. MykhailoKazarian is a priest in the Orthodox Church of Ukraine and is far from a casual Emacs users. Rather than using Emacs to write his sermons, papers, and keep church records, Father Mykhailo is an actual coder who’s worked with Pascal, Python, and Java.
Due to the current difficulties in the Ukraine, Father Mykhailo has had to work with less than ideal conditions, including poor Internet connections and—what some would call—under powered hardware. Through it all he’s been an Emacs user. He says he’s still an Emacs user in 2025 and has a long post on why.
One of the things you often hear about Emacs is that it’s bloated, needs a lot of memory, and won’t run reasonably on anything but the most powerful hardware. Put aside, for the moment, the history of Emacs that puts the lie to that claim, Father Mykhailo is running Emacs on a Raspberry Pi—sometimes, when conditions warrant, powered by an automobile battery—and still manages to enjoy the many benefit that Emacs offers.
Father Mykhailo’s post goes into a lot of detail on why he’s still using Emacs after 20 years and plans to keep on using it. Take a look at his post for a deeper dive.
The quick-fasd Emacs package integrates the Fasd tool into the Emacs environment. Fasd is a command-line utility that provides fast access to frequently used files and directories.
After installing quick-fasd and enabling quick-fasd-mode:
All visited files and directories are automatically added to the Fasd database. (quick-fasd-mode registers hooks in find-file-hook and dired-mode-hook to add visited files and directories to the Fasd database automatically.)
The quick-fasd-find-path function prompts for input and presents candidates from the Fasd index. For example, recently accessed files can be opened, and frequently used directories can be visited without leaving Emacs.
When invoked from the minibuffer, quick-fasd-find-path appends the selected path, simplifying completion.
If this package improves the workflow, consider supporting the project by starring quick-fasd.el on GitHub so that more Emacs users can benefit from it.
I've been volunteering to help with the Bike Brigade newsletter. I like that there are people who are out there helping improve food security by delivering food bank hampers to recipients. Collecting information for the newsletter also helps me feel more appreciation for the lively Toronto biking scene, even though I still can't make it out to most events. The general workflow is:
collect info
draft the newsletter somewhere other volunteers can give feedback on
convert the newsletter to Mailchimp
send a test message
make any edits requested
schedule the email campaign
We have the Mailchimp Essentials plan, so I can't just export HTML for the whole newsletter. Someday I should experiment with services that might let me generate the whole newsletter from Emacs. That would be neat. Anyway, with Mailchimp's block-based editor, at least I can paste in HTML code for the text/buttons. That way, I don't have to change colours or define links by hand.
The logistics volunteers coordinate via Slack, so a Slack Canvas seemed like a good way to draft the newsletter. I've previously written about my workflow for copying blocks from a Slack Canvas and then using Emacs to transform the rich text, including recolouring the links in the section with light text on a dark background. However, copying rich text from a Slack Canvas turned out to be unreliable. Sometimes it would copy what I wanted, and sometimes nothing would get copied. There was no way to export HTML from the Slack Canvas, either.
I switched to using Google Docs for the drafts. It was a little less convenient to add items from Slack messages and I couldn't easily right-click to download the images that I pasted in. It was more reliable in terms of copying, but only if I used xclip to save the clipboard into a file instead of trying to do the whole thing in memory.
I finally got to spend a little time automating a new workflow. This time I exported the Google Doc as a zip that had the HTML file and all the images in a subdirectory. The HTML source is not very pleasant to work with. It has lots of extra markup I don't need. Here's what an entry looks like:
Figure 1: Exported HTML for an entry
Things I wanted to do with the HTML:
Remove the google.com/url redirection for the links. Mailchimp will add its own redirection for click-tracking, but at least the links can look simpler when I paste them in.
Remove all the extra classes and styles.
Turn [ call to action ] into fancier Mailchimp buttons.
Also, instead of transforming one block at a time, I decided to make an Org Mode document with all the different blocks I needed. That way, I could copy and paste things in quick succession.
Here's what the result looks like. It makes a table of contents, adds the sign-up block, and adds the different links and blocks I need to paste into Mailchimp.
Figure 2: Screenshot of newsletter Org file with blocks for easy copying
I need to copy and paste the image filenames into the upload dialog on Mailchimp, so I use my custom Org Mode link type for copying to the clipboard. For the HTML code, I use #+begin_src html ... #+end_src instead of #+begin_export html ... #+end_export so that I can use Embark and embark-org to quickly copy the contents of the source block. (That doesn't work for export blocks yet.) I have C-. bound to embark-act, the source block is detected by the functions that embark-org.el added to embark-target-finders, and the c binding in embark-org-src-block-map calls embark-org-copy-block-contents. So all I need to do is C-. c in a block to copy its contents.
Here's the code to process the newsletter draft
(defunmy-brigade-process-latest-newsletter-draft (date)
"Create an Org file with the HTML for different blocks."
(interactive (list (if current-prefix-arg (org-read-date nil t nil "Date: ")
(org-read-date nil t "+Sun"))))
(when (stringp date) (setq date (date-to-time date)))
(let ((default-directory "~/Downloads/newsletter")
file
dom
sections)
(call-process "unzip" nil nil nil "-o" (my-latest-file "~/Downloads""\\.zip$"))
(setq file (my-latest-file default-directory))
(with-temp-buffer
(insert-file-contents-literally file)
(goto-char (point-min))
(my-transform-html '(my-brigade-save-newsletter-images) (buffer-string))
(setq dom (my-brigade-simplify-html (libxml-parse-html-region (point-min) (point-max))))
(setq sections
(my-html-group-by-tag
'h1
(dom-children
(dom-by-tag
dom'body)))))
(with-current-buffer (get-buffer-create "*newsletter*")
(erase-buffer)
(org-mode)
(insert
(format-time-string "%B %-e, %Y" date) "\n""* In this e-mail\n#+begin_src html\n""<p>Hi Bike Brigaders! Here’s what's happening this week, with quick signup links. In this e-mail:</p>"
(my-transform-html
'(my-brigade-remove-meta-recursively
my-brigade-just-headings)
(copy-tree dom))
"\n#+end_src\n\n")
(insert "* Sign-up block\n\n#+begin_src html\n"
(my-brigade-copy-signup-block date)
"\n#+end_src\n\n")
(dolist (sec '("Bike Brigade""In our community"))
(insert "* " sec "\n"
(mapconcat
(lambda (group)
(let* ((item (apply 'dom-node'div nil
(append
(list (dom-node 'h2 nil (car group)))
(cdr group))))
(image (my-brigade-image (car group))))
(format "** %s\n\n%s\n%s\n\n#+begin_src html\n%s\n#+end_src\n\n"
(car group)
(if image (org-link-make-string (concat "copy:" image)) "")
(or (my-html-last-link-href item) "")
(my-transform-html
(delq nil
(list
'my-transform-html-remove-images'my-transform-html-remove-italics'my-brigade-simplify-html'my-brigade-format-buttons
(when (string= sec "In our community")
'my-brigade-recolor-recursively)))
item))))
(my-html-group-by-tag 'h2 (cdr (assoc sec sections 'string=)))
"")))
(insert "* Other updates\n"
(format "#+begin_src html\n%s\n#+end_src\n\n"
(my-transform-html
'(my-transform-html-remove-images
my-transform-html-remove-italics
my-brigade-simplify-html)
(car (cdr (assoc "Other updates" sections 'string=))))))
(goto-char (point-min))
(display-buffer (current-buffer)))))
(defunmy-html-group-by-tag (tag dom-list)
"Use TAG to divide DOM-LIST into sections. Return an alist of (section . children)."
(let (section-name current-section results)
(dolist (node dom-list)
(if (and (eq (dom-tag node) tag)
(not (string= (string-trim (dom-texts node)) "")))
(progn
(when current-section
(push (cons section-name (nreverse current-section)) results)
(setq current-section nil))
(setq section-name (string-trim (dom-texts node))))
(when section-name
(push node current-section))))
(when current-section
(push (cons section-name (reverse current-section)) results)
(setq current-section nil))
(nreverse results)))
(defunmy-html-last-link-href (node)
"Return the last link HREF in NODE."
(dom-attr (car (last (dom-by-tag node 'a))) 'href))
(defunmy-brigade-image (heading)
"Find the latest image related to HEADING."
(car
(nreverse
(directory-files my-brigade-newsletter-images-directory
t (regexp-quote (my-brigade-newsletter-heading-to-image-file-name heading))))))
When I saw two more events and one additional link that I wanted to include, I was glad I already had this code sorted out. It made it easy to paste the images and details into the Google Doc, reformat it slightly, and get the info through the process so that it ended up in the newsletter with a usefully-named image and correctly-coloured links.
I think this is a good combination of Google Docs for getting other people's feedback and letting them edit, and Org Mode for keeping myself sane as I turn it into whatever Mailchimp wants.
My next step for improving this workflow might be to check out other e-mail providers in case I can get Emacs to make the whole template. That way, I don't have to keep switching between applications and using the mouse to duplicate blocks and edit the code.
Marcin Borkowski (mbork) has an interesting post on entering dates in Calc mode. The short version is that he often enters dates in Calc but it’s a painful exercise. He’d much rather enter them the same way you do in Org mode, which is intuitive and flexible. That flexibility comes at the cost of being complicated but that’s mostly because there are so many easy ways to specify a date.
Naturally, mbork wanted to be able to enter Calc dates in the user friendly Org mode way. That turns out to be surprisingly easy. After the boilerplate it’s just a couple of lines of code plus another line to bind a key sequence to the function in the Calc key map. Read mbork’s post to see what I mean about easy.
What struck me most about the post was his mention of org-read-date, which Org uses to read dates. It’s incredibly flexible and, as mbork shows, usable in non-Org contexts. Take a look at the doc string for org-read-date to see what I mean. Even though I spend a huge amount of time in Org mode and enter a lot of date/time stamps and even though I’ve read the documentation on entering dates in Org, I didn’t remember how powerful the function is. Take a look at the doc string to see all the ways you can specify dates.
This is just another example of Emacs’ extensibility. Mbork had a problem and was able to solve it by gluing together a couple of existing Emacs functions that nominally had nothing to do with each other.
I've gotten into the habit of using tabs, via tab-bar, to organise my buffers
when I have multiple projects open at once. Each project has its own tab.
There's nothing fancy here (yet), I simply open a new tab manually before
opening a new project.
A while ago I added bufferlo to my config to help with getting consult-buffer
to organise buffers (somewhat) by tab. I copied the configuration from the
bufferlo README and started using it. It took me a little while to notice that
the behaviour wasn't quite what I wanted. It seemed like one buffer "leaked"
from another tab.
Figure 1: Example of buffer leakage
In the image above all files in ~/.emacs.d should be listed under Other
Buffers, but one has been brought over into the tab for the Sider project.
After a bit of experimenting I realised that
the buffer that leaks is the one I'm in when creating the new tab, and
my function for creating a new tab doesn't work the way I thought.
My function for creating a new tab looked like this
(lambda()(interactive)(tab-new)(dashboard-open))
and it turns out that tab-new shows the current buffer in the new tab which in
turn caused bufferlo to associate it to the wrong tab. From what I can see
there's no way to tell tab-new to open a specific buffer in the newly created
tab. I tried the following
Helpful is a fantastic Emacs package that drastically improves the builtin help-mode.
One of the particularly nice features is finding references to a particular symbol.
Unfortunately it can be painfully slow in practice due to actually parsing every loaded Elisp file:
Instead of walking the ASTs of every file, why not do a regex search?
It sacrifices correctness (and completely ignores the type of the symbol), but it turns out to be orders of magnitude faster in practice.
Bonus points for using a fast tool like ripgrep and extra bonus points for completing the work asynchronously so as not to block Emacs’s main thread.
I don’t mean to denigrate elisp-refs; the author clearly has put a lot of thought into performance and it’s only natural that using an approach that heavily cuts corners together with a tool implemented in optimized machine code instead of Elisp (which is especially hampered by GC performance) will lead to faster results.
I’m a ripgrep junkie and I prefer it for grokking most codebases, but the speed comes at the cost of having to sift through many false positives.
I use the wonderful deadgrep package to do just that:
The code is quite hacky because deadgrep is not designed to allow passing multiple directories in a single search, but this gets the job done.
Emacs Lisp
(with-temp-buffer (let ((button (make-button (point-min) (point-max)))
(time-to-draw)
(time-to-completion))
(button-putbutton'symbol#'car)
(setqtime-to-completion (benchmark-elapse (setqtime-to-draw (benchmark-elapse (helpful--all-referencesbutton)))
(whiledeadgrep--running (sit-for0.1'nodisp))))
(format"Time to first draw: %s\nTime to completion: %s"time-to-drawtime-to-completion)))
Time to first draw: 0.084542
Time to completion: 38.184606
In this pathological case the total time is slower than using elisp-refs-function because there are almost four times as many matches because of comments/docstrings and partial matches like mapcar or car-safe (including symbols that aren’t functions like byte-car).
The major difference is that while the performance of elisp-refs-* functions1 are roughly constant regardless of the total number of references to a symbol, using ripgrep is significantly faster for terms with fewer than 10k matches (not to mention that you can browse the results immediately).
If you want to remove the partial matches, you could use the following advice instead:
This unfortunately will highlight the entire match instead of just the capturing group, so I prefer not to use it (althgough the speed is mostly the same, if not a bit faster).
elisp-refs-symbol is faster than its counterparts due to reduced implementation complexity ↩︎
I perform date arithmetic pretty often in Emacs Calc. One problem I’ve always had with that is that entering date forms is very far from ergonomic. (To be fair to Calc, once you get used to how Org mode allows to enter dates, basically any other way of entering them feels slow and inconvenient. This is what Org manual says about it: “The actions of the date/time prompt may seem complex, but I assure you they will grow on you, and you will start getting annoyed by pretty much any other way of entering a date/time out there” – and boy, is it true!) So, it occurred to me that it would be great to be able to use the Org date prompt in Calc. Of course, this is Emacs, so it shouldn’t be difficult, right?
Joe Marshall has a nice post on Why Lisp Still Matters. He observes that Lisp tends to attract the very best programmers but the reason for that and the reason for Lisp’s enduring success is its extensibility. As Marshall says, the thing about Lisp is that it’s based on the Lambda calculus, which means, among other things, that the language can be extended in terms of itself. In practical terms, that means that you can use macros to define new syntax and provide the semantics of that new syntax with some functions.
All of this is fairly well known and Marshall has a good exposition of the argument in his post. As I was reading it, it popped into my mind that it’s the same with Emacs. It’s not—just—a matter of Emacs being written in Lisp. The similarity is based on their extensibility. Like Lisp, if you need some new Emacs functionality it’s easy to build it yourself using the language the Emacs itself is written in. As with any Lisp, you can even extend Emacs’ Lisp language if you need to.
A lot of Emacs’ extensibility is, of course, enabled by it’s being written in Elisp. That fact should give pause to all those who are always agitating for Emacs to be rewritten in Java, Javascript, Python, Ruby, or whatever. I doubt every much that we’d have the same level of extensibility if that were to happen.
I found Dan’s dedication inspiring—a simple bit of note taking sustained over
the decades. That list, creating a bit of imortality, with its longitudinal
insight into the intersection of individual curiosity, collection curation, and
the currents of culture.
Drawing on that inspiration, I started thinking about my personal habits that
I’m establishing. Namely writing journal entries then printing those out each
month. I also use Org-Mode📖
to track my reading. I have a single Org-Mode
document that:
Tracks when I started, completed/abandoned reading a book.
Any quotes that I’ve gathered from that book.
Additional notes around the read.
I wrote about some of this in Re-Wiring My Epigraphs Functionality to Leverage
My Bibliography File
So, drawing inspiration from Dan, I wrote an Emacs📖
function:
org-dblock-write:books-finished. That function conforms to the Dynamic Block
API and I simply drop it into my Org-Mode
document at some point within the
month’s entries.
What it does:
Determine the month and year for the block
Query my Bibliography for books I finished reading
Insert into the document a list of those books, as links to those books
This way, when I print my monthly journal, I’ll have a list of books I finished
that month.
Here’s a bit of humor that I’ve had sitting in one of my browser tabs for some time. Over on the Emacs subreddit, Hlorri asks you to Tell me you use emacs (without telling me you use emacs). Although meant to be humorous—and many of the answers are humorous—a lot of them reveal important things about Emacs.
The most common humorous answer involves the strength of the user’s pinky. A typical example is, “I thumb wrestle with my pinky.” Another, that I really liked and could relate to was, “My editor’s config file is older than some of my co-workers.” Several offerings involved the wearing away of the text on the CTRL key.
Among the answers that weren’t so much funny as telling were:
I expect to get things done in a single app.
I cannot use essentially any other piece of software without accidentally opening 10 print dialogs or new blank documents/tabs at some point. (and several variations on this theme)
There’s a mixture of awe, confusion, and mystery when I pair program with co-workers.
Whenever someone wants to give me any “advice” I frown.
I interactively do right what other people write programs to do wrong.
I use the same program for e-mails, coding, calendar and keeping notes (and probably much more).
I write utilities in elisp instead of bash.
You mean your code editor doesn’t have a media player, or a planner, or its own window manager?
And a bunch more. Take a look at Hlorri’s post to see them all.
While we can already run these agents from Emacs with the likes of vterm, I'm keen to offer an Emacs-native alternative to drive them. To do that, I'm working an a new package: agent-shell (more on this to be shared soon). While this new Emacs agent shell has an opinionated user experience, it uses ACP under the hood. Being a protocol, it's entirely UI-agnostic. For this, I now have an early version available of the acp.el library.
acp.el implements Agent Client Protocol for Emacs lisp as per agentclientprotocol.com. While this library is in its infancy, it's enabling me to carry on with my agent-shell work. acp.el lives as a separate library, is UI-agnostic, and can be used by Emacs package authors to build the their desired ACP-powered agent experience.
You can instantiate an ACP client and send a request as follows:
I'm new at using ACP myself, so I've added a special logging buffer to acp.el which enables me to inspect traffic and learn about the exchanges between clients and agents. You can enable logging with:
(setq acp-logging-enabled t)
Look out for the *acp traffic* buffer, which looks a little like this:
If you're keen to experiment with ACP in Emacs lisp and build agent-agnostic packages, take a look at acp.el (now on GitHub). As mentioned, it's early days for this library, but it's a start. Please file issues and feature requests. If you build anything on top of acp.el, lemme know. I'd love to see it in action.
Make this work possible
I'm working on two new Emacs packages: acp.el (introduced in this post) and agent-shell (I'll soon share more about that). Please help me make development of these packages sustainable. These packages take time and effort, but also cost me money as I have to pay for LLM tokens throughout testing and development. Please help fund it.
libxml-parse-html-region turns HTML into a DOM
(document object model). There's also
xml-parse-file and xml-parse-region.
xml-parse-string actually parses the character
data at point and returns it as a string instead
of parsing a string as a parameter. If you have a
string and you want to parse it, insert it into a
temporary buffer and use
libxml-parse-html-region or xml-parse-region.
Then you can use functions like dom-by-tag,
dom-search, dom-attr, dom-children, etc. If
you need to make a deep copy of the DOM, you can
use copy-tree.
Turning the DOM back into HTML can be a little
tricky. By default, dom-print escapes & in
attributes, which could mess up things like href:
shr-dom-print handles & correctly, but it adds spaces in between elements. Also, you need to escape HTML entities in text, maybe with org-html-encode-plain-text.
In Emacs, my text editor of choice, tab-bar-mode lets you replicate something like the experience of using tabs in a web browser. For me, this means opening a lot of tabs with notes, lists, drafts, etc., such that my tab bar becomes a visual buffet of whatever documents are pertinent to the task at hand, be it writing, teaching, coding, whatever.
This works great for me, on a project by project basis. But what if I want to switch to another task or project? What is to become of the perfectly curated set of tabs I have open?
The problem of tab proliferation is a distinctlymodernone. Raise your hand if the top of your web browser looks like a graveyard of articles you’ve been meaning to read since the last time your computer crashed. Many browsers have solved this issue by allowing users to save and restore groups of tabs under meaningful names, like “Research Project” or “Articles I Still Won’t Read.” But who does that? (Ok, I do…)
As far as I know,™ Emacs does not offer any built-in way to save and restore a beautifully curated row of tabs. There are many options for saving and restoring activities, workspaces, and frame/buffer configurations. But none that do exactly what I want, namely, allow me to save and later re-open a set of tabs, one file per tab, in a specific order.
I find tab-sets most useful for teaching and writing.
For example: I sometimes teach the same class several times in a week. I therefore often found myself repeating the process of finding and re-opening all the files that I wanted to reference for that class, mostly lecture notes and slides (exported with org-reveal). Because these files can be spread out across my file system, finding and re-opening them is rarely as simple as navigating to a dedicated directory.
Now, with tab-sets.el, I can use a single command, tab-sets-save, to save the current frame’s tabs under a meaningful name, say, “Week 1 Class”, and safely close the frame. When it’s time to teach the same class again, I can call tab-sets-open, select “Week 1 Class,” and find a brand new frame displaying all the tabs exactly as they were when I saved them.
The same process works for quickly returning to a writing project.
A few niceties include:
minibuffer annotations, offering a preview of each tab set
optional integration with bookmarks.el, meaning tab sets are accessible as standard bookmarks, through list-bookmarks
optional integration with embark, meaning you can open, rename, or delete a tab set right from the minibuffer
This package isn’t published anywhere, so install directly, however you want or know how. For example:
Over at sembr.org, Mattt has an interesting post on what he calls semantic line breaks. The idea, as he expresses it, is
Semantic Line Breaks describe a set of conventions for using insensitive vertical whitespace to structure prose along semantic boundaries.
That sounds a little abstract but in practice it means adding a line break after each sentence and after logical breaks in the text. Here’s a more accessible description:
When writing text with a compatible markup language, add a line break after each substantial unit of thought.
There’s a specification at the link that specifies where the line breaks could/should occur.
The point of all this is that it makes it easier for the author or editors to read the input text while at the same time not affecting the way the text is rendered at output time. That works because of the way many markup languages—including, of course, Org mode—work. Take a look at the sembr.org site for the details and the precise specifications.
This seems like something that might be useful for many writers but, personally, I find that using visual-line-mode is enough for me. It shows all the text on screen by default and although it doesn’t break lines at semantically significant places, the semantic structure of the text comes through clearly for me.
But you may be different. If you find that adding an occasional line break helps make the meaning of your text clearer than by all means add them. It won’t affect the formatting of the output and it may help you and any editors you have in the process to parse the input.
In this video I talk to port19 about repetitive strain injury related
to computer usage. I am introduced to the experience port19 went
through, with pain in the hands and fingers. I ask about the computing
setup that first contributed to the problem and learn how
keyboard-driven it was. port19 covers the progression to a split
mechanical keyboard, learning about specialised exercises to work out
the relevant muscles, as well as reading about research on the
psychosomatic factors involved. We eventually get to the topic of the
custom keyboard port19 uses right now (the ZSA Voyager) and talk about
all the details involved, from the weight of the switches to the
typing angle, the use of layers to access certain characters, and the
placement of modifier keys. To have a complete picture of the
ergonomics port19 has considered, I also ask about the overall desk
setup and the computer use overall. Once we cover this topic, we
discuss how dictation software makes it easier to avoid the keyboard
altogether while still being productive: port19 explains the relevant
setup with ffmpeg and whisper program of OpenAI and shares the
script for it. Towards the end of our talk, I ask port19 about the
switch away from the IT sector into geriatric nursing. I learn about
how things are in that field and what the experience is like.
In this new video series, I talk to anybody who is interested to have
a video call with me (so do contact me if you want!). The topics cover
anything related to Emacs, technology, and life in general. More here:
https://protesilaos.com/prot-asks/.
Roman Numerals. On the one hand, it’s hard to understand why anyone cares anymore. Some, like the late Rich Stevens considered them an anachronistic barbarism and labeled his books “Volume 1, 2, …” rather than the more conventional “Volume I, II, …”. Others continue to label volumes with the conventional Roman numerals and, of course, there’s all those buildings with their erection date labeled, of course, with Roman numerals on their facade.
It used to be that everyone learned how to read and write Roman numerals in School but, according to a teenager of my acquaintance, that’s no longer the case. Were it not for their ubiquitousness on the front of buildings and multivolume books, I’d be happy so see them disappear from our milieu but for the time being, it seems useful to be able to at least read them.
Really, it’s not much of an effort. There are only 7 symbols and they are combined in a regular way to form all the numbers that we’re apt to see today. It’s true that there’s more than one way to form some numbers but all of them are easy to decipher. It’s hard to see how anyone with more than a couple of brain cells to rub together would take more than 15 minutes to completely master the system.
Still, Common Lisp is famous for having functions to convert between Arabic and Roman numerals. That’s less true for Emacs Lisp but there are, apparently, some functions spread across a couple of packages to do the job. Charles Choi doesn’t like that so he wrote a package to provide the capability in a single package.
I suppose, if you squint hard enough, you can imagine cases where you might want to do this sort of thing programmatically but, really, it’s mostly too easy to do manually to need a function. Still, if you do have a need to do this programmatically, Choi has got you covered.
We’ve all been there, your code was working perfectly from a clean checkout, but after making a bunch of changes across multiple files, something has broken. The dreaded question arises! which change caused the break? This is the story of how a debugging session led me to discover gaps in Emacs’ VC mode and ultimately create a custom solution.
I started with a clean, working codebase. After implementing several features across different files, my software suddenly stopped working. The classic debugging nightmare, multiple changes, one (or more) breaking changes, and no clear path to the culprit.
My debugging strategy as always is methodical. Over many years of software engineering I have learnt that you just need to figure out a systematic approach and then just get on with it!
Start from the known-good base version
Gradually reintroduce changes from my working set
Test after each addition to identify the breaking point
Git stash turned out to be perfect for this workflow. Firstly I stashed all my changes, giving me a clean working directory to start from. My plan was to selectively apply portions of the stash, testing after each addition.
Using Emacs’ built-in VC mode, I could use vc-git-stash-show to display my stashed changes in a diff buffer. From there, I could navigate through the files and selectively apply hunks using Emacs’ diff mode commands. This gave me fine-grained control over which changes to reintroduce.
As I progressed through applying changes, I realised that I would really like to keep an eye on what changes remained in my stash compared to my current working directory, basically like a dynamic diff to be regenerated after each application (like typically on an individual file using ediff). This would allow me to keep an eye on likely culprits as I move through the hunking process.
In pure Git, this is straightforward:
git diff stash@{0}
But Emacs’ VC mode doesn’t provide a command for this specific operation (I have found this not to be uncommon for Emacs vc-mode, but I still like it anyways!)
Generally I think, Emacs’ VC interface is designed to be VCS agnostic, which is both a strength and a limitation. While it provides excellent abstractions for common operations like vc-diff, it doesn’t expose Git specific features like comparing against stash references.
The available VC commands were:
vc-diff - compares working directory with HEAD or between revisions
vc-git-stash-show - shows the diff of a stash
But no “diff working directory against stash” command
Now, it’s worth noting that Magit, does apparently provide this functionality, but I prefer to run on air-gapped systems (yes, that again!) where installing external packages isn’t always practical or desired. In such environments, I lean heavily on Emacs’ built-in functionality and augment it with custom elisp when needed which is probably something I suspect I am likely to do in this case.
I had an initial eshell idea on how to accomplish this!, for example you can redirect command line output to Emacs buffers using the #<buffer name> syntax, so lets try that!
After some experimentation, I still couldn’t quite get eshell to generate a buffer from a command and then initiate a mode. Of course I could just jump to the buffer and run it myself, but generally I wanted a solution to be easily repeatable.
Right, lets scrap the eshell idea and lets fall back on my tried and tested method of writing a defun in elisp!:
“S” is currently used for a regex search of some kind which I currently don’t understand and hence am not using.
Now I can use C-x v S to quickly diff my working directory against any stash (although who knows when I will need this again!)
With this in place, my debugging workflow became smoother
Stash all changes
Apply changes incrementally using vc-git-stash-show
Test the software after each addition
When it still works, check what remains C-x v S
Continue applying changes from the remaining diff
When it breaks, I have a good idea of the breaking issue
This experience taught me several valuable lessons:
VC mode’s limitations: While Emacs’ VC interface is excellent for common operations, specialized Git workflows sometimes require custom solutions.
The value of built-in solutions: Working in air-gapped environments has taught me to maximize Emacs’ built-in capabilities before reaching for external packages. While Magit would have solved this problem out of the box, building the solution myself using VC mode and custom elisp keeps dependencies minimal and increases my understanding of both Git and Emacs internals.
Eshell’s power: The ability to redirect command output directly to Emacs buffers is incredibly useful, even if it has some quirks with command chaining and in the end I never really got it to work, but it is in my brain more concretely now as this blog post now exists!
Integration matters: Binding custom functions to standard keymaps makes them feel like native features.
It’s September so there’s a new Emacs Carnival. This month the topic is Obscure Emacs Packages. I have to admit that when I first saw the topic I wasn’t too hopeful that it would be interesting but, of course, I felt the same about the “Elevator Pitch” topic and we all know how obsessed I ended up being with that.
Now that I’ve read my first post for the new topic, tusharhero’s Obscure Emacs Packages, I guessing that I’m going to end up loving this topic too. Tusharhero’s post actually lists 4 obscure packages—none of which I was familiar with—that many Irreal readers might find useful.
By all means, take a look at his post for all the details but I want to mention two of them that I think could be particularly useful. The first is Tinee that is an implementation of Emacs Everywhere specifically for Wayland, which isn’t supported by Emacs Everywhere. The package was written by Tusharhero himself who says it’s not as full featured as Emacs Everywhere but if you’re an Emacs user on a Wayland system, it seems definitely worth taking a look at.
The other package is Emacs Reader by Divya Ranjan. It aims to be a complete replacement for PDF-tools and DocView. It’s still in the early stages but Tusharhero says it has “RIDICULOUSLY better performance and smoothness and responsiveness in comparison to both PDF tools and DocView.” It’s still early days for this package but if it lives up to its promise, it will be a package that almost everyone will want.
Take a look at Tusharhero’s post and see if there’s anything there for you.
Last week, I was delighted to see the Zed editor shipping beta support for their Claude Code integration. Being an Emacs enthusiast, you may wonder about my excitement. In their demo, the Zed team mentioned the integration is now possible thanks to Agent Client Protocol (ACP), which they developed in collaboration with Google. This is great news for Emacs users, as it opens the possibilities for deeper native agent integrations in our beloved editor. You can think of ACP as LSP but for LLM agents.
Somehow, I wasn't super excited about tool calling, as it felt like these integrations would fall short when compared to more advanced agents like Anthropic's Claude Code or Google's Gemini CLI. In fact, I haven't been that enthusiastic about these agents, since they offered relatively little API surface to enable deeper Emacs integration (which is where I live!). That is, until ACP came along.
With ACP in mind, I'm much more likely to get on board with Emacs-agent integrations. I can now delegate all that complex agent logic to external tools and focus on building a great Emacs experience I'd be happy with.
And with that, I had an initial go at prototyping a bare minimum but with enough UX shell goodies to get me excited about it. I chose Gemini for this prototype. You can see it all in its minimal glory:
So how badly do you want ACP support in your beloved Emacs?
While getting the initials kinda working was relatively straightforward (with everything I already know about building chatgpt-shell), adding support for all ACP features with a delightfully polished Emacs experience will take a bunch of effort. While I'm excited about the prospects, dedicating a chunk of my time to make this happen isn't super feasible. You may have noticed more Emacs-related work/posts from me lately. This is currently possible because I've gone full indie dev. The flexibility is great, but doing Emacs things isn't exactly gonna help pay the bills unless interested folks help fund/support the effort.
My shell packages have quite a few enthusiastic users who more often than not, are using my package to talk to paid cloud services from the likes of OpenAI, Anthropic, or Google. I'm looking at you folks! I understand you're sending money to these companies who are providing you with a great service, but also remember the lovely Emacs integrations you use, which also need funding (much more than these well-funded commercial entities). While I'm a fan of chat-like Emacs shells for LLM/agents and would like to build a new agent shell, I also want to dedicate a chunk of this effort to building a UX-agnostic ACP Emacs library (acp.el). This library could be leveraged by me or any other Emacs package author.
So how badly do you want ACP support in your beloved Emacs? Enough to take your wallets out and help fund it?
Mondo can translate into (almost) every language the names of the world's scripts, languages, territories (including countries), and countries' subdivisions. Translations happen locally — in your machine.
Do you have fonts that can display emoji flags? Vexil can convert from country code to country flag, and vice-versa. Likewise for countries' subdivisions.
A few months ago I mentioned Artist mode. It seems that at least one person liked that post enough to mention it (thanks!), even if indirectly. That very post also mentioned svg-clock, which is a cute analog clock displayed directly in Emacs. Of course, my immediate thought was, why not create an ASCII-art analog clock using Artist mode?
It all started with Super Bowl LIX. “What number is that?” Sure, that answer could be solved with a Google search, or even worse, burning petrol to fuel some LLM query. Being an Emacs user, I was certain someone had already solved the problem of translation to Roman numbers. Indeed this was the case but the answer was in two separate packages, both included in Emacs:
Roman to Hindu-Arabic number translation was covered by the command rst-roman-to-arabic in the reStructuredText (rst) package.
Hindu-Arabic to Roman number translation was covered by the command org-export-number-to-roman in the Org export (ox) package.
“Hunh” I thought, seems like I’d never recall that these two packages have the commands that would do the job for me. I’d have a much better chance of using them if they were in a single package whose name I could remember.
So begat numeri, a new package that wraps both of the above commands, now available on MELPA.
There are two commands of note:
numeri-arabic-to-roman
This command will accept either an Arabic integer number selected as a region or input via mini-buffer prompt and convert it to its Roman equivalent. The result is copied into the kill-ring.
numeri-roman-to-arabic
This command will accept either a Roman integer number selected as a region or input via mini-buffer prompt and convert it to its Arabic equivalent. The result is copied into the kill-ring.
Closing Thoughts
I have no expectations on how useful folks will find numeri. Regardless, there is delight in knowing that for even the most esoteric of problems, there’s a non-zero chance that someone has already solved it in Emacs.
In the developer world, we often talk about two types of bankruptcy:
Email Bankruptcy
This is when we have so many unread emails that we simply give up, delete them all, and start over.
Emacs Bankruptcy
This is when our Emacs configuration becomes so big and complicated that we no longer understand it and start over by deleting our configuration and beginning again with a blank slate.
My Anglo-Saxon upbringing pretty much guaranteed that I’d be resistant to either of these. I’m far too anal to leave any email unread or at least scanned. It’s not really much of an issue today since, unlike the old days, I can access and read my email from wherever I am. That and my zero inbox policy ensure that I never get behind with Email.
As for my Emacs configuration, it’s almost 20 years old and while it has grown and had some minor changes, I’ve never felt the need—or even the urge—to quit and start over. It is, in short, an organic entity that grows and changes in step with my needs. Why would I want to abandon it and start over?
All this discussion is in reaction to a video by TrepidTurtle on the Emacs subreddit about Emacs bankruptcy. TrepidTurtle views Emacs bankruptcy as a natural and common process in our Emacs life. He says, for example, that he was aghast to discover that his Emacs configuration was over 2000 lines long and remedied that by deleting his entire Emacs environment—packages and all—and starting over from scratch. Implicit in his argument is the belief that a minimalist configuration is better.
I don’t understand that. My configuration is 2055 lines and while there may be some cruft most of it is carefully curated packages, settings, and bespoke functions that I’ve developed over my Emacs career. Why would I want to delete all that work?
As for having a minimalist configuration, I add things as I need them or think they will enhance my workflow. I see nothing ipso facto virtuous about having a minimal configuration.
Still, people’s opinions differ and you may be sympathetic to the idea of starting over. If so, take a look at TrepidTurtle’s video for some ideas as to how to do that.
This is my second submission to the Emacs Carnival (my first one was
emacs elevator pitch). The theme this time around is about obscure
emacs packages. I figure I will just talk about the obscure packages I
use.
This package by Thanos Apollo allows you to search for YouTube videos
and play them through mpv (which uses yt-dlp under the hood). As we
all know, YouTube is a demonic proprietary website but unfortunately a
lot of videos are only available on YouTube. So as much as I would
like to only rely on Peertube and friends, I can't.
Yeetube directly parses the search results from the search page HTML,
this is what differentiates it from tools like ytfzf which rely on the
invidious api to get the search results. The problem with invidious is
that because YouTube keeps blocking them, there is no guarantee that
the instance you use now will work reliably tomorrow. AFAIK, ytfzf
also used to directly parse the YouTube search result pages, I am not
sure why they stopped.
And after parsing the search results, it displays them in a neat table
with THUMBNAILS! And it also allows you to control mpv from within
Emacs.
This time the author is me! The package name is a homage to the
venerable emacs-everywhere package. Of which tinee is just a simple
substitute on Wayland.
For those who don't know what emacs-everywhere or tinee are supposed
to do, they allow you to use Emacs to write everywhere on your
computer. This is done by binding a key to tinee/emacs-everywhere in
your desktop environment, which then opens up a small emacs frame for
you to type what you want, after you are done, it automatically sends
the content into the proper place.
A few years ago, starting in 2016, I wrote a series of articles (1, 2, 3) about my favorite Emacs packages and tips. Reading them now, almost ten years later, I find they’ve aged well. I still use everything on the list and my “can’t live without” packages are pretty much the same. I didn’t include Org mode or Magit because, even then, they were given must haves.
I was reminded of all this when I saw a post on the Emacs subreddit from macro_. The post asked what are the must have Emacs packages in August 2025? Once you get past the usual ankle biting, the answers are revealing. Although consult and Ivy were mentioned frequently there seemed to me be less emphasis on actual editing and more on things like LLM and LSP. For example, no one mentioned Steve Purcell’s indispensable whole-line-or-region. I use it many, many times everyday—probably more than any other package.
Still there are plenty of good suggestions in the comments and they are well worth taking a look at. Everybody’s opinion will differ, of course, but I find it really useful to see what others think and why they think it. Sometimes they even convince me to try out one of their favorites. Take a look at the comments and see if there’s anything there that might improve your workflow.
In this video, I talk to Greg about a number of topics ranging from
the technical work one does in Emacs to outdoors activities in
Pennsylvania. Our discussion starts with the topic of Emacs, where I
learn that Greg has been working professionally with software for
twenty years. In Greg’s experience, one of the advantages of using
Emacs for work is that you do not need to change your tools when you
switch jobs or work with different programming languages. We then
discuss Greg’s attempt to use Emacs on Android, as there now is an
official build for that platform. We briefly touch on the Clojure
programming language and also talk about ways of learning to program.
Once we cover these technical issues, we switch to the outdoor
activities that Greg enjoys: cycling, fishing, and just being outside
with his dog. I learn from Greg a few things about fishing and we
eventually comment on a lot of more profound insights about natural
experiences in general. Our final comments revolve around the
appreciation of the comments, in light of a politics essay I published
recently titled “When your country is a theme park”:
https://protesilaos.com/politics/2025-09-02-when-your-country-is-a-theme-park/.
About “Prot Asks”
In this new video series, I talk to anybody who is interested to have
a video call with me (so do contact me if you want!). The topics cover
anything related to Emacs, technology, and life in general. More here:
https://protesilaos.com/prot-asks/.
Backronym: Should Highlight Only With the Family Of the Named Typeface.
Below are the release notes.
Version 1.0.0 on 2025-09-07
This major update introduces support for fonts that cover more than
the Latin script. It also expands the available functionality with
quality-of-life refinements.
Support for Arabic, Chinese, Greek, Japanese, Korean, Russian
The commands show-font-tabulated (alias show-font-list) and
show-font-select-preview can now generate a preview for fonts that
are optimised to display the aforementioned languages. Each language
provides its own user option to control the sample text it displays.
The naming pattern show-font-LANGUAGE-sample.
Of those, I only know Greek and thus wrote the value of
show-font-greek-sample, namely: "Πρωτεσίλαος ο φιλόσοφος του οποίου
τα έργα βρίθουν αστειισμών". For the others I used translation
software to get the equivalent of "Protesilaos does not read
LANGUAGE". Please let me know if there are any mistakes in this
regard. I was thinking of writing something a bit more funny, but was
concerned the joke may not translate well.
Checking for language support among known families
While I have written functions that test if a given font can display a
range of characters, this approach is computationally intensive if we
need to check for many code points across multiple fonts.
The alternative is to maintain lists of known font families that are
meant to work with the given language. Those generally support Latin
as well, but the idea is to let them shine in the language they are
meant to be used for.
For example, here is how we know that a font family is meant to
display Arabic script:
(defconstshow-font-arabic-families'("AlArabiya""AlBattar""AlHor""AlManzomah""AlYarmook""Dimnah""Hani""Haramain""Hor""Kayrawan""Khalid""Mashq""Nagham""Noto Kufi Arabic""Noto Naskh Arabic""Noto Sans Arabic""Rehan""Sharjah""Sindbad")"List of families that specialise in Arabic.
Also see `show-font-greek-families' for the rationale of grouping font
families in distinct variables.")
The list is not exhaustive and I am always eager to expand it. Just
let me know.
I learnt about these font families through trial and error by (i)
installing them on my Debian system and (ii) searching online for
common samples of them. Do apt search -n fonts- to check the
relevant packages.
Support for music notation, mathematics, and other symbols
As with the natural languages, there are some fonts that specialise in
displaying symbols. For example, MathJax has a bunch of fonts for
showing those fancy formulas in the processed output of LaTeX
documents. Again, there is a defconst for each of those types of
font listing the known families. The concomitant user options are:
show-font-mathematics-sample
show-font-music-sample
show-font-symbols-sample
Problematic fonts are hidden from the list view
The show-font-hidden-families lists the fonts that are not known to
cause problems. They do not render properly any of the supported
samples and I am not sure even when they claim to support a certain
set of characters (e.g. show-font--displays-latin-p returns
non-nil). If you think there is a mistake here, please contact me.
Show a full preview from the list view
While in the buffer produced by show-font-tabulated (alias
show-font-list), type RET to get a complete preview of the font
family of the current line. This is the same as invoking the command
show-font-select-preview and then selecting the given family.
The command called by that key binding is show-font-tabulated-select-preview.
A hint of it is also shown in the tabulated list header.
Quickly copy the name of the font in the list view
As above, type w in the tabulated view to copy the name of the font
family to the kill-ring. The command is show-font-tabulated-copy-name
and there is also a hint of it in the tabulated list header.
James Dyer has another in his series of posts describing the sanding down of his workflow. This time he troubleshoots and resolves a problem with minibuffer file completion. His specific problem was that he didn’t like the way fuzzy file completion was working.
The default behavior was to have whatever he entered match any file name that had the same letters in the same order even if they weren’t consecutive. What he wanted was to match any files whose name had a substring that matched his input.
As usual, Emacs has you covered. There’s a completion-styles parameter that you can set to get the exact behavior he wanted. Except it didn’t work. It turned out that another function in the call path was resetting completion-styles to the behavior he was trying to avoid.
It wasn’t too hard to fix this and you see how he did it in his post. There was another small issue that he also fixed. Again, see Dyer’s post.
The meat of his post for me was his three conclusions at the end:
The source code is always easily available and you can solve most problems by looking to see what is actually happening.
Be wary of local versus global settings.
With Emacs, there’s always another way. If you don’t like the way something works, Emacs probably provides another way of achieving the same end.
Most people probably aren’t going to care about the issue Dyer was fixing—at least I don’t care about it—but the point is that whatever Emacs is doing that’s not quite right for you, it’s almost always pretty easy to fix it.
For my completion framework, I’m currently using fido-mode, and more recently, fido-vertical-mode. However, I’m scratching yet another itch in my ongoing quest to be more efficient in Emacs, specifically to jump to files more quickly. I explored this in a previous post where I enhanced the recentf functionality to work through completing-read in a predictable order, but what about the completing-read interface itself?
This happens to me often in Emacs, there is a subconscious functional annoyance which eventually bubbles to the surface and this case the surface bubble revolves around fido’s fuzzy matching behaviour. Simply put, I don’t like it!
While it can be helpful for discovering files and commands you partially remember, sometimes you know exactly what you’re looking for and want a more literal, predictable search experience, in fact for me now, I would say it is not just sometimes, but always!. The fuzzy matching is finding too many candidates when I type in a few characters and really I want a contiguous input string to be literally matched.
This post chronicles my journey from fido’s flex matching to a custom setup that provides literal substring matching, perfect for when you know what you want and just want to type it directly.
Hang on a sec, can’t I just change the completion style?, this should be easy!
(setq completion-styles '(substring basic))
But that has no effect!, boooo!
Anyways, that was a quick attempt at a fix, in the meantime lets explore flex a little bit more and icomplete (which is the underpinning completion technology of fido) and see if we cam come up with a robust solution.
Fido-mode use what’s called “flex” completion by default. This means that when you type abc, it will match files like a_long_b_filename_c.txt because it finds the letters a, b, and c in that order, even with other characters between them.
While this flexibility is powerful, it can be frustrating when you want to search for a specific substring. If you’re looking for a file named project-abc-config.txt, you might expect typing abc to prioritize that match, but flex matching might show you a_big_collection.txt first instead.
So back to my initial attempt at a fix by setting the completion-styles variable. The substring style matches your input as a contiguous block anywhere within candidates, while basic does prefix matching. This seemed like exactly what I wanted, I just need to find a way to set it and to make it stick.
After some digging into the source code, I found the culprit in icomplete.el. The icomplete--fido-mode-setup function contains the following:
(defun icomplete--fido-mode-setup ()
"Setup `fido-mode''s minibuffer." (when (and icomplete-mode (icomplete-simple-completing-p))
;; ... other settings ... (setq-local completion-styles '(flex) ; This line forces flex! completion-flex-nospace nil;; ... more settings ... )))
This function runs every time you enter the minibuffer, forcibly overriding any completion-styles setting you might have configured. This explains why my setq had no effect, fido was resetting it on every use!
Rather than fight fido’s opinionated behaviour, I could instead switch to icomplete-vertical-mode, which provides a similar interface but respects the standard completion configuration.
(icomplete-vertical-mode 1)
;; scroll list rather than rotating(setq icomplete-scroll t)
;; Make completion case-insensitive(setq completion-ignore-case t)
(setq read-file-name-completion-ignore-case t)
(setq read-buffer-completion-ignore-case t)
(with-eval-after-load 'icomplete (setq completion-styles '(substring basic partial-completion emacs22)))
This gave me the literal substring matching I wanted and I think I have managed to set up everything else to the way fido comes out of the box.
However, there was one more hurdle.
By default, icomplete-vertical-mode requires you to explicitly select a completion before submitting with C-m (Enter) which is a keybinding I had grown accustomed to using in fido. This adds an extra confirmation step that fido-mode doesn’t have. There is a way around this however and that is to adapt to the keybinding C-j which typically is more of a do literal action then exit type of thing, where C-m is more of just a simple Enter/action. I am willing to adapt to this keybinding.
So this works pretty well for me really, but can I not just get completion-styles to stick for fido?, even though I have a solution I really want to see if I can adjust fido’s default functionality.
Well simply I used an advice function to wrap around the original fido setup function and set up the completion-styles local variable after fido has done its thing:
Now I have two options for using completion in Emacs the way I want it and now I can find files, or anything else for that matter much more quickly.
This journey taught me several important lessons about Emacs customization:
Read the source: When configuration variables don’t seem to work as expected, the source code often reveals why.
Local vs. global settings: Fido uses setq-local to override settings per-buffer, which is why global setq calls don’t work.
There’s always another way: Emacs’ flexibility means there are usually multiple approaches to achieving the same goal.
While fido-mode’s fuzzy matching is excellent for discovery and exploration, I just wanted the predictability of literal substring matching and with a small advice function, you can have the best of both worlds!
In honor of the release of Debian 13 Trixie I've treated myself to a
fresh install of Debian. In the spirit of minimalism I've started
with a completely bare base-system, opting to skip all graphical
desktops for my initial install. I enjoy the process of starting with
essentially a Linux server and only installing packages as I actually
need them. Doing this highlights the applications I rely on, and in
turn the software those apps rely on. In other words dependencies.
Now I'll admit this is mostly a fun exercise that appeals to my
brain in particular. For the most part extra software is not a big
issue, and it is better to have a working computer than a few less
bytes on a hard drive. I just find a minimal setup satisfying. At
one point I was an Arch Linux user, it is what it is.
The tradeoffs of software minimalism were highlighted for me when I
installed mpd (Music Player Daemon). mpd has a lot of
dependencies and a few that I found unnecessary. I will likely never
need to play Commodore 64 music files (libsidplayfp6), but that is
also what makes mpd great. It is a flexible tool you can throw
anything at. Still I was a little more annoyed to see JavaScript
libraries installed just to show the documentation pages (Sphinx).
If I was a python programmer I likely would have never noticed because
Sphinx is a dependency for nearly every *python-doc package. For me
though mpd is the only package that requires these dependencies.
It's just a few extra bytes, I can live with it. Perhaps someday I'll
be in the woods completely offline and need to consult a local copy of
the mpd docs. In that moment I'll be so thankful. I might not even
consider that Sphinx definitely has export options that does not
require JavaScript libraries.
Of course this is not a real deal breaker. I could build mpd
myself, or build a custom deb package. I could switch back to Arch
Linux. Better yet I could switch to Gentoo and spend all my days
compiling packages. No, I use Debian because I like Debian. I like
the philosophy, stability, and cooperative spirit of the project.
Debian maintainers are making sane choices that makes stuff work with
reasonable defaults that supports a huge range of use cases.
With all that in mind, lets talk about LaTeX.
1. Installing LaTeX on Debian
To build LaTeX documents we need to install the TeX Live distribution.
A full install of the texlive packages is huge and includes
thousands of TeX packages. Majority of which I will never use and
don't want to install. The problem is to compile Org-mode documents
we need several packages that are only included in the extensive
texlive-latex-extra and texlive-pictures packages.
You can see the list of TeX packages each of these installs in the
package descriptions. Run apt info to see the full list. The
texlive-latex-extra lists around 1,500 packages. I need ~3.
apt info texlive-latex-extra
1.2. Manual TeX Live Install
If you really want a minimal install the best option would be to
just manually install texlive and the packages you want. You can do
that by following the instructions on tug.org/texlive/, That is
probably the right way to circumvent installing every Debian texlive
package. Once installed you'll be able to manage your whole texlive
install with the included package manager tlmgr (TeX Live Manager).
1.3. Mixed Install with apt and tlmgr
The method I used was a little more chaotic. I installed the base
texlive system via apt. This gives us all the critical stuff for
building basic documents (pdflatex, tlmgr) with minimal effort.
apt install texlive
After that I go to my documents and start trying to build them. Even
for a basic Org to LaTeX document there are several TeX packages that
will be missing. The compile warning will call out the missing
package *.sty and then I can install it with tlmgr.
ERROR: LaTeX Error: File `wrapfig.sty' not found.
--- TeX said ---
./test.tex:9: ==> Fatal error occurred, no output PDF file produced!
On Debian the texlive package manager tlmgr is included with the
base texlive install. The Debian maintainers have explicitly
recommend not to use this tool1, because it may install TeX
packages are incompatible with the deb package versions. So please
do this at your own risk and do NOT pester Debian volunteers if
your TeX install breaks!
One benefit of this mixed setup is the tlmgr tool is forced into
--user mode. This means all packages are installed locally in your
home directory. For my use case that is perfect. If something breaks
I can always delete the directory and fall back to an apt install.
By default packages are stored in ~/texmf/, and can be changed by
setting the TEXMFHOME environment variable. In my ~/.bashrc I
have this set:
exportTEXMFHOME=$HOME/.config/texlive/
Then initialize our tlmgr folder with:
source ~/.bashrc # Ensure your $TEXMFHOME is set
echo $TEXMFHOME
tlmgr init-usertree
Now we can install the couple missing dependences needed for Org LaTeX
export via tlmgr. Once you've installed all the packages reported
in your LaTeX compile errors you should be all set going forward.
tlmgr install warpfig rotating ulem capt-of
Again this is a hack-y solution, but currently I only have ~10
packages manually installed. That is in comparison to the hundreds of
packages included in all of texlive. This solution may become more
fragile over time as Debian stable drifts further out of date with
current TeX Live releases. Although I suspect most popular TeX
packages are pretty stable and slow moving.
Earlier today I joined Prot for an episode of his Prot Asks series,
where he talks to folks about Emacs and life in general. We talked
about free software, free knowledge, the importance of community and
the commons, and life in Canada.
See the episode’s page on Prot’s website for the video recording,
Prot’s summary of our chat, and the links I shared after our call.
You can also watch the recording embedded below.
I really enjoyed our chat today and time went by very quickly.
Thanks again for having me, Prot!
When I'm deep into developing a feature, I don't want to think about
whether a file is already open in a buffer, or if I need to search the
whole project to find it.
I just want to jump straight to the files I'm actively working on,
the ones that Git tells me are modified, untracked, or even
renamed.
Emacs already gives us great defaults for different contexts:
➡ C-x b (switch-to-buffer) → jump between open buffers.
➡ C-x p b (project-switch-to-buffer) → jump between buffers in the
current project.
➡ C-x p f (project-find-file) → find any file inside the project.
Each of these has its value and time. But in the middle of a coding
session, when iterating fast on a feature, none of them give me what I
want: a list of just the files I've touched.
That's the itch that led me to write this command.
The Function and Binding
(defunemacs-solo/switch-git-status-buffer()"Parse git status from an expanded path and switch to a file.The completion candidates include the Git status of each file."(interactive)(require'vc-git)(let((repo-root(vc-git-root default-directory)))(if(not repo-root)(message"Not inside a Git repository.")(let*((expanded-root(expand-file-name repo-root))(command-to-run(format"git -C %s status --porcelain=v1"(shell-quote-argument expanded-root)))(cmd-output(shell-command-to-string command-to-run))(target-files(let(files)(dolist(line(split-string cmd-output "\n"t)(nreverse files))(when(>(length line)3)(let((status(substring line 02))(path-info(substring line 3)));; Handle rename specially(if(string-match"^R" status)(let*((paths(split-string path-info " -> "t))(new-path(cadr paths)))(when new-path
(push(cons(format"R %s" new-path) new-path) files)));; Modified or untracked(when(or(string-match"M" status)(string-match"\?\?" status))(push(cons(format"%s %s" status path-info) path-info) files)))))))))(if(not target-files)(message"No modified or renamed files found.")(let*((candidates target-files)(selection(completing-read"Switch to buffer (Git modified): "(mapcar#'car candidates)nilt)))(when selection
(let((file-path(cdr(assoc selection candidates))))(when file-path
(find-file(expand-file-name file-path expanded-root)))))))))))(global-set-key(kbd"C-x C-g")'emacs-solo/switch-git-status-buffer)
The command parses git status --porcelain=v1, collects the
interesting files, and offers them through completing-read. This
means it works with whichever completion frontend you already use, and
it switches to the selected file's buffer (opening it first if it
isn't already open).
Completion in Action
Here's how it looks in different completion UIs:
➡ Plain completion buffer
➡ Fido
➡ Fido vertical
➡ Icomplete vertical
➡ Vertico
No matter which you prefer, you get a filtered list of just the files that matter right now.
Why emacs-solo?
This command is part of my
emacs-solo project, a
collection of Emacs enhancements that stick to built-in tools and
minimal glue code.
The goal is to show how far we can go without depending on a huge
package ecosystem, while still making day-to-day workflows smoother.
Conclusion
When I'm developing a feature, I care about the files I've just
modified, created, or moved.
This command scratches exactly that itch: fast navigation to
Git-relevant files, without worrying if they're open or buried
somewhere in the project tree.
Give it a try, and check out
emacs-solo for more
small, practical commands like this one. 🚀
Edit:
2025-09-04: Fixed the global binding to
emacs-solo/switch-git-status-buffer, as pointed by u/jplindstrom
on r/emacs
(reddit).
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!