Categories: geek » emacs » org

View topic page - RSS - Atom - Subscribe via email

My Emacs writing experience

| writing, emacs, org

I've been enjoying reading people's responses to the Emacs Carnival July theme of writing experience. I know I don't need complicated tools to write. People can write in composition notebooks and on typewriters. But I have fun learning more about the Emacs text editor and tweaking it to support me. Writing is one of the ways I think, and I want to think better. I'll start with the kinds of things I write in my public and private notes, and then I'll think about Emacs specifically.

Types of notes

Text from sketch

What kinds of posts do I write? How? Improvements?

2025-07-25-05

  • Emacs News
    • why: collecting & connecting → fun!
    • how:
      • phone: Reddit: upvotes
      • YouTube: playlist
    • RSS
    • Mastodon: Scrape boosts?
    • Dedupe, categorize: classifier?
    • Blog
    • Mailing list
    • emacs.tv
    • emacslife.com/calendar
  • Bike Brigade newsletter
    • why: help out, connect
    • Reddit + X + Slack -> Slack canvas -> MailChimp
    • Need more regular last-min sweep
    • Copying from Slack sucks; Google Docs?
  • Tech notes
    • Why: figure things out, remember, share
    • code
    • literate programming: notes + code
    • debugger?
    • more notes?
    • thinking out loud?
  • Life reflections
    • Why: figure things out, remember
    • tangled thoughts
    • sketch: habit? more doodles
    • audio braindump
    • snippets on phone
    • learning to think
    • laptop: write
    • audio input?
    • themes, thoughts
      • LLM? reflection questions, topics to learn more about
  • Book notes
    • Why: study, remember, share
    • paper: draw while reading
    • e-book: highlight
      • quotes
    • sketch
      • smaller chunks?
    • blog
  • Monthly/yearly reviews
    • Why: plan, remember
    • phone: daily journal
    • tablet: draw moment of the day
    • phone: time records
    • Emacs: raw data
    • themes, next steps: LLM? reflection questions?
    • blog post

Emacs News

I put together a weekly list of categorized links about the interesting ways people use Emacs. This takes me about an hour or two each week. I enjoy collecting all these little examples of people's curiosity and ingenuity. Organizing the links into a list helps people find things they might be interested in and connect with other people.

I start by skimming r/emacs and r/orgmode on my phone, upvoting posts that I want to include. I also search YouTube and add videos to an Emacs News playlist. I review aggregated posts from Planet Emacslife. I have an Emacs Lisp function that collects all the data and formats them as a list, with all the items at the same level.

For Mastodon, I check #emacs search results from a few different servers. I have a keyboard shortcut that boosts a post and captures the text to an Org Mode file, and then I have another function that prompts me to summarize toots, defaulting to the title of the first link. I have more functions that help me detect duplicates and categorize links. I use ox-11ty to export the post to my blog, which uses the Eleventy static site generator. I also use emacstv.el to add the videos to the Org file I use for emacs.tv.

Some ways to improve this:

  • I probably have enough data that it might be interesting to learn how to write a classifier. On the other hand, regular expression matches on the titles get most of them correctly, so that might be sufficient.
  • YouTube videos are a little annoying to go through because of interface limitations and unrelated or low-effort videos. I can probably figure out something that checks the RSS feeds of various channels.

Bike Brigade newsletter

I also put together a weekly newsletter for Bike Brigade, which coordinates volunteer cyclists to deliver food bank hampers and other essentials. Writing this mostly involves collecting ideas from a number of social media feeds as well as the other volunteers in the community, putting together a draft, and then copying it over to Mailchimp. I'm still figuring out my timing and workflows so that I can stay on top of last-minute requests coming in from people on Slack, and so that I can repurpose newsletter items as updates in the Facebook group or maybe even a blog. If I set aside some regular time to work on things, like a Sunday morning sweep for last-minute requests, that might make it easier to work with other people.

Tech notes

I like coding, and I come up with lots of ideas as I use my computer. I enjoy figuring out workflow tweaks like opening lots of URLs in a region or transforming HTML clipboard contents. My Org files have accumulated quite a few. My main limiting factor here is actually sitting down to make those things happen. Fortunately, I have recently discovered that it's possible for me to spend an hour or two a day playing Stardew Valley, so I can swap some of that time for Emacs tweaking instead. Coding doesn't handle interruptions as well as playing does, but taking notes along the way might be able to help with that. I can jump to the section of my Org file with the ideas I wanted to save for more focus time, pick something from that screen, and get right to it.

Other things that might help me do this more effectively would be:

  • getting better at using my tools (debugger, documentation, completion, etc.),
  • taking the opportunity to plug in an external monitor, and
  • using my non-computer time to mull over the ideas so that I can hit the ground running.

I like taking notes at virtual meetups. I usually do this with Etherpad so that other people can contribute to the notes too. I don't have a real-time read-write Emacs interface to this yet (that would be way cool!), but I do have some functions for working with the Etherpad text.

Life reflections

When I notice something I want to figure out or remember, I use sketches, audio braindumps, or typing to start to untangle that thought. Sometimes I use all three, shifting from one tool to another depending on what I can do at the moment. I have a pretty comfortable workflow for converting sketches (Google Vision) or audio (OpenAI Whisper) to text so that I can work with it more easily, and I'm sure that will get even smoother as the technology improves. I switch from one tool to another as I figure out the shape of my thoughts.

Maybe I can use microblogging to let smaller ideas out into the world, just in case conversations build them up into more interesting ideas. I don't quite trust my ability to manage my GoToSocial instance yet (backups? upgrades?), so that might be a good reason to use a weekly or monthly review to revisit and archive those posts in plain text.

I've been reading my on this day list of blog posts and sketches more regularly now that it's in my feed reader. I like the way this helps me revisit old thoughts, and I've saved a few that I want to follow up on. It feels good to build on a thought over time.

I'd like to do more of this remembering and thinking out loud because memories are fleeting. Maybe developing more trust in my private journals and files will help. (Gotta have those backups!) Then I'll be more comfortable writing about the things we're figuring out about life while also respecting A+ and W-'s privacy, and I can post the stuff I'm figuring out about my life that I'm okay with sharing. I might think something is straightforward, like A+'s progress in learning how to swim. I want to write about how that's a microcosm of how she's learning how to learn more independently and my changing role in supporting her. Still, she might have other opinions about my sharing that, either now or later on. I can still reflect on it and keep that in a private journal as we figure things out together.

Even though parenting takes up most of my time and attention at the moment, it will eventually take less. There are plenty of things for me to learn about and share outside parenting, like biking, gardening, and sewing. I've got books to read and ideas to try out.

I'm experimenting with doing more writing on my phone so that I can get better at using these little bits of time. Swiping letters on a keyboard is reasonably fast, and the bottleneck is my thinking time anyway. I use Orgzly Revived so that Syncthing can synchronize it with my Org Mode files on my laptop when I get back home. There are occasional conflicts, but since I mostly add to an inbox.org when I'm on my phone, the conflicts are usually easy to resolve.

Adding doodles to my reflections can make them more fun. I can draw stick figures from scratch, and I can also trace my photos using the iPad as a way to add visual anchors and practise drawing. If I get the hang of using a smaller portion of my screen like the way I used to draw index cards, that might make thoughts more granular and easier to complete.

When I write on my computer, I often use writeroom-mode so that things feel less cluttered. I like having big margins and short lines. I have hl-line-mode turned on to help me focus on the current paragraph. This seems to work reasonably well.

2025-07-26_00-33-44.png
Figure 1: Screenshot showing writeroom-mode and hl-line-mode

Monthly and yearly reviews

I like the rhythm of drawing daily moments and keeping a web-based journal of brief descriptions of our day. I like how I've been digging into them deeper to reflect on themes. The monthly drawings and posts make it easier to review a whole year. Maybe someday I'll get back to weekly reviews as well, but for now, this is working fine.

My journal entries do a decent job of capturing the facts of our days: where we went, what we did. Maybe spending more time writing life reflections can help me capture more of what goes on in my head and what I want to learn more about.

Book notes

I draw single-page summaries of books I like because they're easier to remember and share. E-books are convenient because I can highlight text and extract that data even after I've returned the book, but I can also retype things from paper books or use the text recognition feature on my phone camera. I draw the summaries on my iPad using Noteful, and then I run it through my Google Vision workflow to convert the text from it so that I can include it in a blog post.

The main limiting factor here is my patience in reading a book. There are so many other wonderful things to explore, and sometimes it feels like books have a bit of filler. When I have a clear topic I'm curious about or a well-written book to enjoy, it's easier to study a book and make notes.

Emacs workflow thoughts

Aside from considering the different types of writing I do, I've also been thinking about the mechanics of writing in Emacs. Sanding down the rough parts of my workflow makes writing more enjoyable, and sometimes a small tweak lets me squeeze more writing into fragments of time.

There are more commands I want to call than there are keyboard shortcuts I can remember. I tend to use M-x to call commands by name a lot, and it really helps to have some kind of completion (I use vertico) and orderless matching.

I'm experimenting with more voice input because that lets me braindump ideas quickly on my phone. Long dictation sessions are a little difficult to edit. Maybe shorter snippets using the voice input mode on the phone keyboard will let me flesh out parts of my outline. I wonder if the same kind of quick input might be handy on my computer. I'm trying out whisper.el with my Bluetooth earbuds. Dictating tends to be stop-and-go, since I feel self-conscious about dictating when other people are around and I probably only have solo time late at night.

Misrecognized words can be annoying to correct on my phone. They're much easier to fix on my computer. Some corrections are pretty common, like changing Emax to Emacs. I wrote some code for fixing common errors (my-subed-fix-common-errors), but I don't use this often enough to have it in my muscle memory. I probably need to tweak this so that it's a bit more interactive and trustworthy.

When I see a word I want to change, I jump to it with C-s (isearch-forward) or C-r (isearch-backward), or I navigate to it with M-f (forward-word). I want to get the hang of using Avy because of Karthik's awesome post about it. That post is from 2021 and I still haven't gotten used to it. I probably just need deliberate practice using the shortcut I've mapped to M-j (avy-goto-char-timer). Or maybe I just don't do this kind of navigation enough yet to justify this micro-optimization (no matter how neat it could be), and isearch is fine for now.

Sometimes I want to work with sentences. expand-region is another thing I want to get used to. I've bound C-= to er/expand-region from that package. Then I should be able to easily kill the text and type a replacement or move things around. In the meantime, I can usually remember to use my keyboard shortcut of M-z for avy-zap-up-to-char-dwim for deleting something.

Even in vanilla Emacs, there's so much that I think I'll enjoy getting the hang of. oantolin's post on his writing experience helped me learn about M-E, which marks the region from the point to the end of the sentence and is a natural extension from M-e. Similarly, M-F selects the next word. I could use this kind of shift-selection more. I occasionally remember to transpose words with M-t, but I've been cutting and pasting sentences when I could've been using transpose-sentences all this time. I'm going to add (keymap-global-set "M-T" #'transpose-sentences) to my config and see if I remember it.

I like using Org Mode headings to collapse long text into a quick overview so I can see the big picture, and they're also handy for making tables of contents. It might be neat to have one more level of overview below that, maybe displaying only the first line of each paragraph. In the meantime, I can use toggle-truncate-lines to get that sort of view.

If I'm having a hard time fitting the whole shape of a thought into my working memory, I sometimes find it easier to work with plain list outlines that go all the way down to sentences instead of working with paragraphs. I can expand/collapse items and move them around easily using Org's commands for list items. In addition, org-toggle-item toggles between items and plain text, and org-toggle-heading can turn items into headings.

I could probably write a command that toggles a whole section between an outline and a collection of paragraphs. The outline would be a plain list with two levels. The top level items would be the starting sentences of each paragraph, and each sentence after that would be a list item underneath it. Sometimes I use actual lists. Maybe those would be a third level. Then I can use Org Mode's handy list management commands even when a draft is further along. Alternatively, maybe I can use M-S-left and M-S-right to move sentences around in a paragraph.

Sometimes I write something and then change my mind about including it. Right now, I tend to either use org-capture to save it or put it under a heading and then refile it to my Scraps subtree, but the palimpsest approach might be interesting. Maybe a shortcut to stash the current paragraph somewhere…

I use custom Org link types to make it easier to link to topics, project files, parts of my Emacs configuration, blog posts, sketches, videos, and more. It's handy to have completion, and I can define how I want them to be exported or followed.

Custom Org link types also let me use Embark for context-sensitive actions. For example, I have a command for adding categories to a blog post when my cursor is on a link to the post, which is handy when I've made a list of matching posts. Embark is also convenient for doing things from other commands. It's nice being able to use C-. i to insert whatever's in the minibuffer, so I can use that from C-h f (describe-function), C-h v (describe-variable), or other commands.

I also define custom Org block types using org-special-block-extras. This lets me easily make things like collapsible sections with summaries.

I want to get better at diagrams and charts using things like graphviz, mermaidjs, matplotlib, and seaborn. I usually end up searching for an example I can build on and then try to tweak it. Sometimes I just draw something on my iPad and stick it in. It's fine. I think it would be good to learn computer-based diagramming and charting, though. They can be easier to update and re-layout when I realize I've forgotten to add something to the graph.

Figuring out the proper syntax for diagrams and charts might be one of the reasonable use cases for large-language models, actually. I'm on the fence about LLMs in general. I sometimes use claude.ai for dealing with the occasional tip of the tongue situation like "What's a word or phrase that describes…" and for catching when I've forgotten to finish a sentence. I don't think I can get it to think or write like me yet. Besides, I like doing the thinking and writing.

I love reading about other people's workflows. If they share their code, that's fantastic, but even descriptions of ideas are fine. I learn so many things from the blog posts I come across on Planet Emacslife in the process of putting together Emacs News. I also periodically go through documentation like the Org Mode manual or release notes, and I always learn something new each time.

This post was really hard to write! I keep thinking of things I want to start tweaking. I treat Emacs-tweaking as a fun hobby that sometimes happens to make things better for me or for other people, so it's okay to capture lots of ideas to explore later on. Sometimes something is just a quick 5-minute hack. Sometimes I end up delving into the source code, which is easy to do because hey, it's Emacs. It's comforting and inspiring to be surrounded by all this parenthetical evidence of other people's thinking about their workflows.

Each type of writing helps me with a different type of thinking, and each config tweak makes thoughts flow more smoothly. I'm looking forward to learning how to think better, one note at a time.

Check out the Emacs Carnival July theme: writing experience post for more Emacs ideas. Thanks to Greg Newman for hosting!

View org source for this post

Finding the shape of my thoughts

| emacs, org, writing

Text from sketch

Finding the shape of my thoughts 2025-07-23-02

I have a hard time following a thought from beginning to end.

Some people are like this and have figured out things that work well for them.

Challenges:

  • too much or not enough
  • one more thing; rabbit holes
  • dangling thoughts

iPad

  • shape of thought
    • topics?
    • enough?
    • flow?
  • metaphors, visual frameworks?
  • zooming in? links?
  • text boxes?

Phone

  • outline, snippets, placeholders
  • outline?
  • short dictation?
  • Keyboard?

Laptop

  • fleshing out: code, links, etc.
  • Zettelkasten?
  • editing audio braindump?
  • managing idea pipeline?
  • leave TODOS, mark them

Overall:

  • Develop thoughts in conversation
  • Use the constraints
  • Get the ball rolling

I want to write more. Writing better can follow, with practice and reflection. But writing is challenging. Coming up with ideas is not the hard part. It's finishing them without getting distracted by the hundred other ideas I come up with along the way. I have a hard time following one thought from beginning to end. My mind likes to flit around, jumping from one idea to another. Even when I make an outline, I tend to add to one section, wander over to another, come back to the first, get very deep into one section and decide it's probably its own blog post, and so on. Sometimes I want to say too much to fit into a blog post. Sometimes I start writing and find that I don't have enough to say yet. Sometimes I keep getting distracted by one more thing I want to do before I feel like I can finish the post. Sometimes an idea turns out to be a deep rabbit hole. Sometimes I can rein in those thoughts by using TODOs and next steps and somedays, but then I have all these threads left dangling and it doesn't quite feel right.

Fortunately, other people have figured out how to work with things like this. Roland Allen shares an example in The Notebook: A History of Thinking on Paper (2023):

P117. More commonly, [Leonardo da Vinci] expresses annoyance at his own distractability or perceived lack of progress. "Alas, this will never get anything done" is a theme that recurs in several notebooks.

Asked about the experience of looking at the "spine-tingling" notebooks, [Martin] Kemp employs strikingly kinetic language. "As material objects, they have an extraordinary intensity, little notebooks with this pretty tiny writing, done at great speed, great urgency, a kind of desperate intensity, when something else crowds in he has to jot it down, he goes back to the original thought, he gets diverted, he comes back to that page and will write some more… it's a very manic business."

My life is much smaller scale, but it's nice to know that other people have figured out or are figuring out how to work with how they are. For example, I've been drafting a post for July's Emacs Carnival theme of writing experience. Along the way, I found myself adding my blog posts as a consult-omni source, using that to add URLs to link placeholders, and writing this post about non-linearity. I'm very slowly learning to break those up into their own posts, or maybe even just save the idea as a TODO so that I can finish the thing that I'm writing before I get distracted by figuring out something else. It's easier to work with the grain than against it, so I follow wherever my curiosity leads, and then figure out what chunks I can break into posts.

I'm also coming to terms with the fact that I don't know what I'm writing until I write it and tweak it. No Athena springing forth fully-formed. The ideas develop in conversation: me with my sketches and text, and if I'm lucky, other people too. Sometimes there isn't enough there yet, so I need to put the idea aside for now. Sometimes there's too much I want to say, so I need to select things to focus on.

When I have an idea I want to write about, I like to start with drawing in Noteful on my iPad: sometimes a mind map, sometimes just words and phrases scattered on a page until I figure out which things are close to each other. I can select things with a lasso and move them closer together, and I can use a highlighter to choose things to focus on. This helps me get a sense of what I want to write about and what examples I want to use. Then I can take a step back and figure out the order that makes sense for the post.

My starting sketch for this post:

Text from sketch

Non-linear writing 2025-07-23-01

  • 1. I have a hard time following one thought from beginning to end.
    • My mind likes to flit around.
    • Other people have figured out how
  • 2. Challenges:
    • trying to cram in too much
    • one more thing
    • yak-shaving / rabbitholes
    • dangling threads
  • 3. iPad
    • Map
    • enough
    • not dense
    • starting points
    • Sketch
    • Order
    • crossing out?
    • Structure?
    • rough sketch vs shareable
    • hyperlinks?
      • Zoom in
        • finer pen, actual zoom?
        • This is as small as it gets. Extra details?
    • Bluetooth keyboard?
    • Beorg, Plain Org?
    • Airdroid or hotspot?
    • visual frameworks, David Gray
    • I'm experimenting with using Noteful's text boxes so that I can quickly dictate the thought
  • 4. Phone
    • Snippets
    • short dictation?
    • outline - collapsible?
    • Sometimes I only have my phone with me. I can make a quick outline in orgzly revived and then fill in paragraphs jumping around as needed. Sometimes I feel like I'm going to lose track of the dangling threads, especially if they're in the middle of a paragraph so maybe a Todo marker might be good for that.
    • Outline
  • 5. Laptop
    • break out smaller chunks into their own posts.
    • leave TODOS
    • Zettelkasten
    • mention Emacs Conf talk about writing; also org-roam
  • audio braindump
    • tangled
    • editing?
    • needs computer for now
    • LLM?
    • Shorter is prob. more useful
  • drawing metaphor?
    • painting?
    • mark-making
    • bounds, shape
    • gradually fill in
  • move ideas for improvement to the different sections

Sometimes ideas peter out at this stage, when I find that I don't have much to say yet about it. I organize my Noteful notes by month, so I just leave the unfinished sketch there. I could probably tag it to make it findable again, but I usually end up moving on to other thoughts instead. If I want to revisit an idea later on, it's often easier to just make a new map. There are so many ideas I can explore, so it's good to quickly find out when I don't have enough to say about something. It might make more sense to me later on.

If I can figure out the rough shape of an idea and I feel like I have enough thoughts to fill it with, then it's time to figure out words. Swiping on an onscreen keyboard is more comfortable on my phone than on the iPad, although maybe that's just a matter of getting used to it. If I've developed the idea enough to have a clear flow, I can write the outline without referring to my sketch. If I happen to have a flat surface like a table, I can write while looking at my drawing. Once I have an outline on my phone, I can fill it in with paragraphs.

Sometimes it's easier for me to dictate than to type, like when I'm watering the plants. I use OpenAI Whisper to transcribe the recordings. The speech recognition is pretty accurate, but I have a lot of false starts, tangents, and rephrasing. My thoughts are rough and tangled, tripping over each other on their way out, but saying them out loud gets them down into a form I can look at. I still need to do a fair bit of work to clean up the text and organize it into my outline. Some people use large-language models to organize raw transcripts, but I haven't quite figured out a good prompt that turns a wall of raw text into something that's easy to include in my draft while retaining my voice. At the moment, I'd rather just manually go over my transcript for ideas I want to include and phrasings I might want to keep. As I massage the braindump into a post, I notice other things I want to add or rephrase. Maybe I'll get the hang of using voice input mode to dictate shorter snippets so that I can do it on my phone or iPad instead of needing computer time.

When I'm ready to expand these fragments into full posts, it's easiest to write on the computer, especially if I want to look up documentation or write code. My Orgzly Revived notes generally synchronize seamlessly with Org Mode in Emacs via Syncthing, aside from the occasional sync conflict that I need to resolve. Then I can build on whatever I started jotting down on my phone. Since I type quickly, thinking is the real bottleneck. If I've thought about things enough through my sketches or phone drafts, writing on my computer goes faster.

I find it easier to assemble a thought out of smaller snippets than to write from scratch, which is why Zettelkasten appeals to me. I still want to figure out some kind of approximate search, or even an exact search that can check Org entry text in a reasonable way. (Maybe org-ql…) My notes are not nearly as organized as people's org-roam constellations, but I'm starting to be able to pull together snippets of drafts, quotes from books, links to previous blog posts, and things I've come across in my reading.

Some ideas stall at this stage, too. M-x occur for "^\* " shows 65 top-level headings in my posts.org drafts. Sometimes it's because I've run into something I haven't figured out yet. Sometimes it's because the thoughts are still tangled. Sometimes it's because I've gotten distracted by other things, or a different approach to the same topic. I generally work on the more recent ones that are still in my mind. I also have a tree-map visualization that gives me a sense of the heft of each draft, in case the accumulation of words helps nudge me to finish it. It's okay for thoughts to take a while.

2025-07-23_20-18-40.png
Figure 1: Treemap visualization of my posts.org

So my iPad is for sketching out the thought, my phone is for writing on the go, and my computer is for fleshing it all out. How could I make this better?

  • iPad:
    • What if I use more structure, visual frameworks, or metaphors, instead of starting from a totally blank page? That can help suggest things to think about, like the way a 2x2 matrix can help organize contrasts.
    • I can zoom in and write with a thin stroke to add more detail. If I need even more space, I can link to a separate page.
    • I can add textboxes and use voice input to quickly capture fragments of ideas as I draw.
  • Phone:
    • I can explore the outline tools of Beorg, Plain Org, or Orgzly Revived to see if I can get the hang of using them when I'm away from my computer.
    • I can try the voice input on my phone. To keep the flow going, I need to resist the urge to correct misrecognized words as long as things are somewhat understandable.
    • Maybe I can try bringing a Bluetooth keyboard to playdates where I'm likely to be near a table.
  • Writing:
    • I can run more ideas through my audio braindumping process so that I can improve my workflow.
    • I can use Org Mode TODO states or tags to manage my idea pipeline so that I can keep track of posts that are almost there.
    • I can be more ruthless about parking an idea as a TODO or a next step instead of feeling like I need to go write that post before I can finish this one. This might also help me write in smaller chunks.

Even if I have to rewrite chunks as I figure things out, that's not a waste. That's just part of how thoughts develop. I'm constrained by the tools that I use and the fragments of time that I have, but I can use those constraints to help me break things down into manageable pieces. If I take advantage of those little bits of time to get the ball rolling, writing at the computer becomes much easier and more fun. This is the kind of brain I've got, and I enjoy learning more about working with it.

View org source for this post

Using web searches and bookmarks to quickly link placeholders in Org Mode

Posted: - Modified: | org, emacs

[2025-07-23 Wed]: Handle Org link updates from the middle of a link.

I want to make it easy to write with more hyperlinks. This lets me develop thoughts over time, building them out of small chunks that I can squeeze into my day. It also makes it easy for you to dig into things that pique your curiosity without wading through lots of irrelevant details. Looking up the right URLs as I go along tends to disrupt my train of thought. I want to make it easier to write down my thoughts first and then go back and add the URLs. I might have 5 links in a post. I might have 15. Sounds like an automation opportunity.

If I use double square brackets around text to indicate where I want to add links, Orgzly Revived and Org Mode both display those as links, so I can preview what the paragraph might feel like. They're valid links. Org Mode prompts me to create new headings if I follow them. I never use these types of links to point to headings, though. Since I only use custom IDs for links, any [[some text]] links must be a placeholder waiting for a URL. I want to turn [[some text]] into something like [[https://example.com][some text]] in the Org Mode markup, which gets exported as a hyperlink like this: some text. To figure out the target, I might search the web, my blog, or my bookmarks, or use the custom link types I've defined for Org Mode with their own completion functions.

Most of my targets can be found with a web search. I can do that with consult-omni with a default search based on the link text, prioritizing my bookmarks and blog posts. Then I don't even have to retype the search keywords.

(defun my-org-set-link-target-with-search ()
  "Replace the current link's target with a web search.
Assume the target is actually supposed to be the description.  For
example, if the link is [[some text]], do a web search for 'some text',
prompt for the link to use as the target, and move 'some text' to the
description."
  (interactive)
  (let* ((bracket-pos (org-in-regexp org-link-bracket-re))
         (bracket-target (match-string 1))
         (bracket-desc (match-string 2))
         result)
    (when (and bracket-pos bracket-target
               (null bracket-desc)
               ;; try to trigger only when the target is plain text and doesn't have a protocol
               (not (string-match ":" bracket-target)))
      ;; we're in a bracketed link with no description and the target doesn't look like a link;
      ;; likely I've actually added the text for the description and now we need to include the link
      (let ((link (consult-omni bracket-target nil nil t)))
        (cond
         ((get-text-property 0 :url link)
          (setq result (org-link-make-string (get-text-property 0 :url link)
                                             bracket-target)))
         ((string-match ":" link)       ; might be a URL
          (setq result (org-link-make-string link bracket-target))))
        (when result
          (delete-region (car bracket-pos) (cdr bracket-pos))
          (insert result)
          result)))))

This is what that looks like:

Screencast: filling in a link URL based on the text

consult-omni shows me the title, first part of the URL, and a few words from the page. C-o in consult-omni previews search results, which could be handy.

Sometimes a web search isn't the fastest way to find something. Some links might be to my Emacs configuration, project files, or other things for which I've written custom Org link types. I can store links to those and insert them, or I can choose those with completion.

(defun my-org-set-link-target-with-org-completion ()
  "Replace the current link's target with `org-insert-link' completion.
Assume the target is actually supposed to be the description.  For
example, if the link is [[some text]], do a web search for 'some text',
prompt for the link to use as the target, and move 'some text' to the
description."
  (interactive)
  (let* ((bracket-pos (org-in-regexp org-link-bracket-re))
         (bracket-target (match-string 1))
         (bracket-desc (match-string 2))
         result)
    (when (and bracket-pos bracket-target
               (null bracket-desc)
               ;; try to trigger only when the target is plain text and doesn't have a protocol
               (not (string-match ":" bracket-target))
               (org-element-lineage (org-element-context) '(link) t)) ; ignore text in code blocks, etc.
      ;; we're in a bracketed link with no description and the target doesn't look like a link;
      ;; likely I've actually added the text for the description and now we need to include the link.
      ;; This is a hack so that we don't have to delete the link until the new link has been inserted
      ;; since org-insert-link doesn' tbreak out the link prompting code into a smaller function.
      (let ((org-link-bracket-re "{{{}}}"))
        (goto-char (cdr bracket-pos))
        (org-insert-link nil nil bracket-target))
      (delete-region (car bracket-pos) (cdr bracket-pos)))))

Here's an example:

Screencast showing how to specify a link with completion

If I decide a web search isn't the best way to find the target, I can use <up> and RET to get out of the consult-omni web search and then go to the usual Org link completion interface.

(defun my-org-set-link-target-dwim ()
  (interactive)
  (or (my-org-set-link-target-with-search)
      (my-org-set-link-target-with-org-completion)))

If I can't find the page either way, I can use C-g to cancel the prompt, look around some more, and get the URL. When I go back to the web search prompt for the link target, I can press <up> RET to switch to the Org completion mode, and then paste in the URL with C-y or type it in. Not elegant, but it will do.

I can now look for untargeted links and process each one. The (undo-boundary) in the function means I can undo one link at a time if I need to.

(defun my-org-scan-for-untargeted-links ()
  "Look for [[some text]] and prompt for the actual targets."
  (interactive)
  (while (re-search-forward org-link-bracket-re nil t)
    (when (and
           (not (match-string 2))
           (and (match-string 1) (not (string-match ":" (match-string 1))))
           (org-element-lineage (org-element-context) '(link) t)) ; ignore text in code blocks, etc.
      (undo-boundary)
      (my-org-set-link-target-dwim))))

Here's how that works:

Screencast showing how I can scan through the text for untargeted links and update them

Play by play
  1. 0:00:00 I started with a search for "build thoughts out of smaller chunks" and deleted the default text to put in "developing" so that I could select my post on developing thoughts further.
  2. 0:00:08 I linked to Orgzly Revived from my bookmarks.
  3. 0:00:10 I selected the Org Mode website from the Google search results.
  4. 0:00:12 I selected the consult-omni Github page from the search results.
  5. 0:00:18 I used <up> RET to skip the web search instead of selecting a candidate, and then I selected the bookmarks section of my configuration using Org link completion.
  6. 0:00:25 I changed the search query and selected the post about using consult-omni with blog posts.
  7. 0:00:31 I chose the p-search Github repository from the Google search results.

If my-org-scan-for-untargeted-links doesn't find anything, then the post is probably ready to go (at least in terms of links). That might help avoid accidentally posting placeholders. I'm going to experiment with going back to the default of having org-export-with-broken-links be nil again, so that's another safety net that should catch placeholders before they get published.

Next steps

I can look at the code for the web search and add the same kind of preview function for my bookmarks and blog posts.

I can modify my C-c C-l binding (my-org-insert-link-dwim) to have the same kind of behaviour: do a web/bookmark/blog search first, and fall back to Org link completion.

Someday it might be nice to add a font-locking rule so that links without proper targets can be shown in a different colour. org-export-with-broken-links and org-link-search both know about these types of links, so there might be a way to figure out font-locking.

I might not use the exact words from the title, so it would be good to be able to specify additional keywords and rank by relevance. The p-search talk from EmacsConf 2024 showed a possible approach that I haven't dug into yet. If I want to get really fancy, it would be neat to use the embedding of the link text to look up the most similar things (blog posts, bookmarks) and use that as the default.

I'm looking forward to experimenting with this. I think it will simplify linking to things when I'm editing my drafts on my computer. That way, it might be easier for me to write about whatever nifty idea I'm curious about while helping people pick up whatever background information they need to make sense of it all.

This is part of my Emacs configuration.
View org source for this post

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

Posted: - Modified: | emacs, org
  • [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

Thinking about time travel with the Emacs text editor, Org Mode, and backups

| emacs, org

Sometimes I just need to rewind 15 minutes. That's the length of A+'s recess at virtual school, which she does at home. At recess, she often likes to get hugs and sometimes a snack. If I'm working on something that requires sustained thought, like code or a blog post, I can't hold those thoughts in my head for that long while cheerfully listening to A+ share the trivia she's read on the Stardew Valley wiki. If I try to keep my train of thought, I get grumpy. I'd rather get better at time travel instead. Naturally, this calls for Emacs.

For people who are unfamiliar with Emacs or Org Mode

GNU Emacs is a highly customizable program for editing text, writing code, and doing mostly whatever people want to get it to do. Org Mode is a package (or ecosystem of packages, really) that modifies GNU Emacs to make it easier to take notes, plan tasks, export documents, and so on. If you're not into Emacs yet, this post might be a little technical, but maybe there are ways to translate some of the ideas to things you're using.

What was I doing again?

Sometimes recess totally resets my brain and I can't even think of what I was just working on. To make it easier to hit the ground running, I try to make a habit of creating a task in Org Mode before I start working on it. Or, more realistically, halfway through, when I realize I have to first do another thing, so then I jot down a quick task for the work I was previously doing and another task for the tangent I'm about to go on. That way, I can quickly check my notes to see what I was doing. org-capture (which I've bound to C-c r) is handy for that. I have a template (t) that creates a timestamped TODO that links to the context I created it in (files, note headings, etc.) and saves it to my inbox file. Then I can jump to my inbox file with a keyboard shortcut and look at what I need to get back to doing.

Sometimes I vaguely remember that I've already created a task for this before and I can find it with C-u C-c C-w (org-refile). When org-refile is called with a universal prefix argument (C-u), it will prompt for a heading in org-refile-targets and jump to it. I have it set to complete the outline path, so I can try to find things by project. Failing that, I might have a quick rummage in my inbox. I usually don't remember the exact words I used in the the task title, though. Maybe someday I'll get the hang of org-ql or p-search (Emacsconf talk on p-search), resurrect the Remembrance Agent so that it can continuously do bag-of-words matching, or use embeddings to find semantically similar tasks and notes. In the meantime, capturing the task is more important than avoiding duplicates. I can find and clean up duplicates later on.

All of that is moot when I'm away from my computer, which is most of the time. My phone is pretty handy for quick notes, though. I use Orgzly Revived to capture a quick note in my inbox. This gets synchronized with my Org Mode notes using Syncthing.

Hmm, I gotta do this first…

Often the interruption doesn't even come from outside, but from my brain's endless stream of interesting ideas. Some of those ideas can be saved as tasks to work on eventually, but sometimes I need to pause my current task and work on the new idea. I have a template for an interrupting task (i) that automatically clocks out of the previous task and clocks into the new one.

My template for interrupting tasks

This is the entry in my org-capture-templates.

        ("i" "Interrupting task" entry
         (file ,my-org-inbox-file)
         "* STARTED %^{Task}\n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n"
         :clock-in :clock-resume
         :prepend t)

Okay, that's done, what was I doing before?

If I clock into tasks, I can use org-clock-goto along with the C-u universal prefix (C-u C-c C-x C-j) to see a list of recently-clocked-in tasks. This is great for "popping the stack," which is how I think of backtracking once I finished an interrupting task.

I usually forget to clock out. That's okay. I'm not looking for precise total times, just breadcrumbs.

… What was I thinking?

Sometimes a few keywords aren't enough to jog my memory. Whenever I think, "Ah, this is easy, I don't need to take notes," I inevitably regret it. Sometimes I realize I have to re-do my thinking fifteen minutes later, when singing 4-Town songs with A+ has pushed those thoughts out of my brain. Sometimes I have to re-do my thinking several months later, which is even harder.

Notes are super-helpful. I love the way Org Mode lets me write notes, paste in hyperlinks, add snippets of code, save the results of my explorations, include my sketches, and even export them as blog posts or documents to share.

Sometimes I have to go back farther into the past

It can take me months or even years before I can circle back to a project or idea. It can be hard to reconstruct my thinking after a lot of time has passed, so it's good to write down as much as possible. Taking notes feels slower than just plunging ahead, but they help me travel back in time to try to remember.

This really gets hammered in when I run into things I've forgotten, like when I dusted off my time-tracking code so I could make some changes. In the four years that elapsed between Aug 2020 (my last change) and Oct 2024 (when I decided to upgrade it to the latest version of Rails), I'd forgotten how to even run a development version of my code. Whoops. I ended up taking more notes along the way.

I try to keep project-related notes as close to the project files as possible, like a README.org in the project directory. Sometimes I don't even remember what the project is called. I try to keep another file that indexes things on my computer as well as things in real life.

Sometimes I know I wrote tasks or notes down before but I can't remember the exact words I used for them. I'm curious about whether embeddings might help me find those things again. So far it's been okay to just add a new task or note, and then periodically clean up entries that are no longer needed.

Going sideways

Sometimes I want to visit alternate timelines, trying different ways to do something. I really like the way undo works in Emacs. It's different from most programs. Emacs keeps the things you undo/redo.

Let's say I start writing a paragraph or a piece of code. I change my mind about something. Maybe I undo, maybe I cut, maybe I delete. I write again. I change my mind again. The first way was better, maybe. I can go back to that, step through any of the intermediate changes, consider the other version again. It's not lost.

Actually navigating the Emacs undo history can be tricky. I like using the vundo package for that. It shows a compact view of the different branches of this timeline so that I can easily jump between them or compare them.

[2025-06-26 Thu] Check out Undo finally clicked with vundo | shom.dev for a screenshot and some explanation of vundo.

If I'm working on something more complicated, like code, I might make changes over several sessions. This is where version control is handy. I like using the Git version control system, especially with the Magit package. I can commit versions of the files manually along with a quick note about what I changed or what I'm trying. This allows me to easily reset to a certain point.

Sometimes I'm good about picking the right point to commit: I've made decent progress and things are working. Sometimes I realize only later on that I probably should have saved a commit a little while ago, and now I'm halfway through another idea that I'm not going to have time to finish and that leaves my project in a non-working state. In that situation, sometimes I'll use the visual undo provided by the vundo package to go backwards to a version that looks about right, save that file, commit it with a quick note, and then go forward in time again.

Saving revisions in Git makes it much easier to go backwards in time even if I've restarted my computer. magit-blame and vc-annotate give me slightly different views showing me the changes in a file. They don't show me information on deleted sections, though. For that, I can use the magit-diff command to compare versions. Sometimes it's easier to flip through the history of a single file with git-timemachine.

Git lets me name different experimental timelines (branches) and logically group changes together. It means I don't have to worry so much about messing up a working file, since I can travel back in time to that version. It also means I can easily compare them to see what I've changed so far.

In addition to using version control for various projects, I also save backup files to a separate directory by setting my backup-directory-alist to (("." . "~/.config/emacs/backups")). Disk space is cheap; might as well keep all the backups. I sometimes manually go into this directory to find older versions of things. It occurs to me that it might be good to flip through the backups in the same way that git-time-machine makes it easy to flip through git revisions. I'm trying out lewang/backup-walker, which shows the incremental diffs between versions. It was last updated 12 years ago(!), but can easily be dusted off to work with Emacs 30 by defining some functions that it's looking for. Here's my config snippet:

(use-package backup-walker
  :vc (:url "https://github.com/lewang/backup-walker")
  :init
  (defalias 'string-to-int 'string-to-number)
  (defalias 'display-buffer-other-window 'display-buffer))

Into the future

It's not all about going back to the past. Sometimes I want to plan ahead: tasks that I want to schedule for a certain date, pre-mortems to help me make decisions, gifts for my future self. I use Google Calendar for appointments and other things I might want to share with W- for planning, but there are lots of other things that aren't tied to a specific time and date. The agenda feature of Org Mode is handy for scheduling things and moving them around.

Scheduled tasks don't work out so well if my agenda gets cluttered by things I ignore, so if I find myself procrastinating something a lot, I think about whether I really want to do whatever it is I've written down.

Some notes aren't associated with specific dates, but with other events that might happen. I have an Org Mode outline with various subheadings under "In case of…", although I often forget to check these or have a hard time finding them again. Maybe someday I can write a script that analyzes the words I use in my journal entries or tasks and finds the notes that approximately match those keywords.

Things I want to try

Thinking out loud more might be worth experimenting with, since I can do that while I'm working in a different file. I've used my audio recorder to record braindumps and I have a workflow for transcribing those with OpenAI Whisper. I think it would be even more useful to have an org-capture equivalent so that I can capture the thought by audio, save the recording in case there are recognition errors (highly likely because of the technical terms), and save the context. Or maybe an even neater interface that keeps an ear out for keywords, executes commands based on them, and saves the rest as notes? whisper-ctranslate2 has a live_transcribe option that works reasonably well after a short delay, and maybe I can use a process filter to pull the information out or write a custom Python script.

I appreciate how working with plain text can help me jump backward or forward in time. I'm looking forward to seeing how this can be even better!

This post was inspired by Emacs Carnival 2025-06: Take Two • Christian Tietze and IndieWeb Carnival: Take Two. Check those out for related blog posts!

This is, in fact, my second take on the topic. =) Here's my first one: Making and re-making: fabric is tuition

View org source for this post

Run source blocks in an Org Mode subtree by custom ID

| emacs, org

I like the way Org Mode lets me logically group functions into headings. If I give the heading a CUSTOM_ID property (which is also handy for exporting to HTML, as it turns into an link anchor), I can use that property to find the subtree. Then I can use org-babel-execute-subtree to execute all source blocks in that subtree, which means I can mix scripting languages if I want to.

Here's the code:

(defun my-org-execute-subtree-by-custom-id (id &optional filename)
  "Prompt for a CUSTOM_ID value and execute the subtree with that ID.
If called with \\[universal-argument], prompt for a file, and then prompt for the ID."
  (interactive (if current-prefix-arg
                   (let ((file (read-file-name "Filename: ")))
                     (list
                      (with-current-buffer (find-file-noselect file)
                        (completing-read
                         "Custom ID: "
                         (org-property-values "CUSTOM_ID")))
                      file))
                 (list
                  (completing-read "Custom ID: " (org-property-values "CUSTOM_ID")))))
  (with-current-buffer (if filename (find-file-noselect filename) (current-buffer))
    (let ((pos (org-find-property "CUSTOM_ID" id)))
      (if pos
          (org-babel-execute-subtree)
        (if filename(error "Could not find %s in %s" id filename)
          (error "Could not find %s" id))))))

For example, in Using Org Mode, Emacs Lisp, and TRAMP to parse meetup calendar entries and generate a crontab, I have a Emacs Lisp source block that generates a crontab on a different computer, and a shell source block that installs it on that computer.

Technical notes: org-babel-execute-subtree narrows to the current subtree, so if I want anything from the rest of the buffer, I need to widen the focus again. Also, it's wrapped in a save-restriction and a save-excursion, so someday I might want to figure out how to handle the cases where I want to change what I'm looking at.

elisp: links in Org Mode let me call functions by clicking on them or following them with C-c C-o (org-open-at-point). This means I can make links that execute subtrees that might even be in a different file. For example, I can define links like these:

  • [[elisp:(my-org-execute-subtree-by-custom-id "update" "~/sync/emacs-calendar/README.org")][Update Emacs calendar]]
  • [[elisp:(my-org-execute-subtree-by-custom-id "crontab" "~/sync/emacs-calendar/README.org")][Update Emacs meetup crontab]]

That could be a good starting point for a dashboard.

Related: Execute a single named Org Babel source block

This is part of my Emacs configuration.
View org source for this post

Using Org Mode, Emacs Lisp, and TRAMP to parse meetup calendar entries and generate a crontab

| org, emacs

Times and time zones trip me up. Even with calendar notifications, I still fumble scheduled events. Automation helps me avoid embarrassing hiccups.

We run BigBlueButton as a self-hosted web conferencing server for EmacsConf. It needs at least 8 GB of RAM when active. When it's dormant, it fits on a 1 GB RAM virtual private server. It's easy enough to scale the server up and down as needed. Using the server for Emacs meetups in between EmacsConfs gives people a way to get together, and it also means I can regularly test the infrastructure. That makes scaling it up for EmacsConf less nerve-wracking.

I have some code that processes various Emacs meetup iCalendar files (often with repeating entries) and combines them into one iCal file that people can subscribe to calendar, as well as Org files in different timezones that they can include in their org-agenda-files. The code I use to parse the iCal seems to handle time zones and daylight savings time just fine. I set it up so that the Org files have simple non-repeating entries, which makes them easy to parse. I can use the Org file to determine the scheduled jobs to run with cron on a home server (named xu4) that's up all the time.

This code parses the Org file for schedule information, then generates pairs of crontab entries. The first entry scales the BigBlueButton server up 1 hour before the event using my bbb-testing script, and the second entry scales the server down 6 hours after the event using my bbb-dormant script (more info). That gives organizers time to test it before the event starts, and it gives people plenty of time to chat. A shared CPU 8 GB RAM Linode costs USD 0.072 per hour, so that's USD 0.50 per meetup hosted.

Using #+begin_src emacs-lisp :file "/ssh:xu4:~/bbb.crontab" :results file as the header for my code block and using an SSH agent for authentication lets me use TRAMP to write the file directly to the server. (See Results of Evaluation (The Org Manual))

(let* ((file "/home/sacha/sync/emacs-calendar/emacs-calendar-toronto.org")
       (time-format "%M %H %d %m")
       (bbb-meetups "OrgMeetup\\|Emacs Berlin\\|Emacs APAC")
       (scale-up "/home/sacha/bin/bbb-testing")
       (scale-down "/home/sacha/bin/bbb-dormant"))
  (mapconcat
   (lambda (o)
     (let ((start-time (format-time-string time-format (- (car o) 3600 )))
           (end-time (format-time-string time-format (+ (car o) (* 6 3600)))))
       (format "# %s\n%s * %s\n%s * %s\n"
               (cdr o)
               start-time
               scale-up
               end-time
               scale-down)))
   (delq nil
         (with-temp-buffer
           (insert-file-contents file)
           (org-mode)
           (goto-char (point-min))
           (org-map-entries
            (lambda ()
              (when (and
                     (string-match bbb-meetups (org-entry-get (point) "ITEM"))
                     (re-search-forward org-tr-regexp (save-excursion (org-end-of-subtree)) t))
                (let ((time (match-string 0)))
                  (cons (org-time-string-to-seconds time)
                        (format "%s - %s" (org-entry-get (point) "ITEM") time)))))
            "LEVEL=1")))
   "\n"))

The code makes entries that look like this:

# OrgMeetup (virtual) - <2025-06-11 Wed 12:00>--<2025-06-11 Wed 14:00>
00 11 11 06 * /home/sacha/bin/bbb-testing
00 18 11 06 * /home/sacha/bin/bbb-dormant

# Emacs Berlin (hybrid, in English) - <2025-06-25 Wed 12:30>--<2025-06-25 Wed 14:30>
30 11 25 06 * /home/sacha/bin/bbb-testing
30 18 25 06 * /home/sacha/bin/bbb-dormant

# Emacs APAC: Emacs APAC meetup (virtual) - <2025-06-28 Sat 04:30>--<2025-06-28 Sat 06:00>
30 03 28 06 * /home/sacha/bin/bbb-testing
30 10 28 06 * /home/sacha/bin/bbb-dormant

This works because meetups don't currently overlap. If there were, I'll need to tweak the code so that the server isn't downscaled in the middle of a meetup. It'll be a good problem to have.

I need to load the crontab entries by using crontab bbb.crontab. Again, I can tell Org Mode to run this on the xu4 home server. This time I use the :dir argument to specify the default directory, like this:

#+begin_src sh :dir "/ssh:xu4:~" :results silent
crontab bbb.crontab
#+end_src

Then cron can take care of things automatically, and I'll just get the e-mail notifications from Linode telling me that the server has been resized. This has already come in handy, like when I thought of Emacs APAC as being on Saturday, but it was actually on Friday my time.

I have another Emacs Lisp block that I use to retrieve all the info and update the list of meetups. I can add (goto-char (org-find-property "CUSTOM_ID" "crontab")) to find this section and use org-babel-execute-subtree to execute all the code blocks. That makes it an automatic part of my process for updating the Emacs Calendar and Emacs News. Here's the code that does the calendar part (Org source):

(defun my-prepare-calendar-for-export ()
  (interactive)
  (with-current-buffer (find-file-noselect "~/sync/emacs-calendar/README.org")
    (save-restriction
      (widen)
      (goto-char (point-min))
      (re-search-forward "#\\+NAME: event-summary")
      (org-ctrl-c-ctrl-c)
      (org-export-to-file 'html "README.html")
      ;; (unless my-laptop-p (my-schedule-announcements-for-upcoming-emacs-meetups))
      ;; update the crontab
      (goto-char (org-find-property "CUSTOM_ID" "crontab"))
      (org-babel-execute-subtree)
      (when my-laptop-p
        (org-babel-goto-named-result "event-summary")
        (re-search-forward "^- ")
        (goto-char (match-beginning 0))
        (let ((events (org-babel-read-result)))
          (oddmuse-edit "EmacsWiki" "Usergroups")
          (goto-char (point-min))
          (delete-region (progn (re-search-forward "== Upcoming events ==\n\n") (match-end 0))
                         (progn (re-search-forward "^$") (match-beginning 0)))
          (save-excursion (insert (mapconcat (lambda (s) (concat "* " s "\n")) events ""))))))))
(my-prepare-calendar-for-export)

I used a similar technique to generate the EmacsConf crontabs for automatically switching to the next talk. For that one, I used Emacs Lisp to write the files directly instead of using the :file header argument for Org Mode source blocks. That made it easier to loop over multiple files.

Hmm. Come to think of it, the technique of "go to a specific subtree and then execute it" is pretty powerful. In the past, I've found it handy to execute source blocks by name. Executing a subtree by custom ID is even more useful because I can easily mix source blocks in different languages or include other information. I think that's worth adding a my-org-execute-subtree-by-custom-id function to my Emacs configuration. Combined with an elisp: link, I can make links that execute functional blocks that might even be in different files. That could be a good starting point for a dashboard.

I love the way Emacs can easily work with files and scripts in different languages on different computers, and how it can help me with times and time zones too. This code should help me avoid brain hiccups and calendar mixups so that people can just enjoy getting together. Now I don't have to worry about whether I remembered to set up cron entries and if I did the math right for the times. We'll see how it holds up!

View org source for this post