Irreal: Casual Man and Help

A quick post today.

Charles Choi has just released another app in his Casual suite. This time it’s for the man and help modes. We all use these all the time, of course, but there are plenty of obscure bindings that most of us don’t remember. As usual Choi’s app helps with that by providing easily invoked menus.

Along the way, Choi also tells us about some little known features of man and help mode. For instance, when in man mode, you can press Return when the point is on another command and Emacs will open the man page for that command whether or not it’s an actual link. There are a couple of other nice facts like that so be sure to take a look at Choi’s post.

As I always say when I do one these posts, Choi’s Casual suite just keeps getting better and better and since you can use the same binding to invoke the appropriate menu, they’re easy to use and well worth installing.

-1:-- Casual Man and Help (Post Irreal)--L0--C0--2025-06-25T16:22:38.000Z

Charles Choi: Announcing Casual Man & Help

The penchant for contemporary software today to rely on cloud services becomes a malady whenever said services fail. The recent Google Cloud Platform failure two weeks ago was just one such event whose impact was widespread. By no means is this a novel thing as computer historians would rightfully attest. Complex, changing systems will inevitably fail. Sadly, much of the software in production today is not designed to fail gracefully.

Because the web, an overwhelming amount of computer documentation (reference manuals, user guides, tutorials, etc.) is now read from the Internet. This is great until it’s not. Software that packages documentation “local-first” relaxes having to be on-line to read it. So it is with Emacs and Unix-based systems with Man pages.

Observing the above, I announce two more Transient menus for Casual, one for the Unix Man page reader and the other for the built-in Emacs Help system.

By and large these menus are for navigation features but delving into both Man-mode and help-mode, I’ve come across some TILs:

  • Setting the variable Man-switches to the value “-a” will display all the man pages for a specified topic.
    • This is useful if you have multiple variants of the same utility installed. An example is ls where one variant could be GNU and the other BSD.
  • In Man-mode, if the point is on another command with a man page, pressing RET (man-follow) will open its man page in another window, regardless if it is a link.
  • From help-mode, the commands help-goto-info (binding ’i’) and help-goto-lispref-info (binding ’I’, Elisp only) will send you to an Info manual on the current help topic if it is available. (the missing link!)

For Man-mode, I’ve taken the liberty of making an occur-style command using a regexp that looks for a ’-’ or ’+’ command line option. It is bound to ’o’ in the menu casual-man-tmenu.

Both Casual Man & Help are part of the Casual v2.6.0 release on MELPA.

Links

Some earlier posts related to Emacs documentation:

-1:-- Announcing Casual Man & Help (Post Charles Choi)--L0--C0--2025-06-24T22:00:00.000Z

Amit Patel: Emacs: marking text

Although I primarily use Emacs, I love the idea of vim text objects, and wanted to incorporate them into my editing. The Kakoune introduction explains that vim uses verb first, noun second for its commands, whereas Kakoune uses noun first, verb second. I wrote a blog post about why I prefer noun-verb over verb-noun, not only in text editors but also in games and other applications.

I started cobbling together some commands in a hydra, trying to match vim keys when I could:

(defhydra hydra-mark (:body-pre (set-mark-command nil) :color red)
  "
_w_, _,w_: word, symbol    _i'_, _a'_: string  _i)_, _a)_: pair
_t_, _f_: to char (exclude/include)   _0_, _$_: begin/end of line
_;_: comment   _u_: url    _e_: email      _>_: web-mode block or tag
_S_: sexp      _d_: defun  _p_: paragraph  _s_: sentence
_h_, _j_, _k_, _l_: move   _H-._: off
  "
  ("t" mark-to-char-exclusive)
  ("f" mark-to-char-inclusive)
  ("0" move-beginning-of-line)
  ("$" move-end-of-line)
  ("w" er/mark-word)
  (",w" er/mark-symbol)
  ("i'" er/mark-inside-quotes)
  ("a'" er/mark-outside-quotes)
  ("i)" er/mark-inside-pairs)
  ("a)" er/mark-outside-pairs)
  ("i]" er/mark-inside-pairs)
  ("a]" er/mark-outside-pairs)
  ("j" next-line)
  ("k" previous-line)
  ("h" left-char)
  ("l" right-char)
  (";" er/mark-comment)
  ("u" er/mark-url)
  ("e" er/mark-email)
  ("d" er/mark-defun)
  ("S" mark-sexp)
  ("s" mark-end-of-sentence)
  ("p" mark-paragraph)
  (">" web-mode-mark-and-expand)
  ("H-." deactivate-mark :exit t)
   )
(defun my/hydra-mark ()
  (interactive)
  (set-mark-command nil)
  (hydra-mark/body))

(bind-key "H-." #'my/hydra-mark)
And some helper functions:
(defun move-to-char (arg char)
  (interactive (list (prefix-numeric-value current-prefix-arg)
                     (read-char "Move to char: " t)))
  (search-forward (char-to-string char) nil nil arg))

(defun mark-to-char-exclusive (arg char)
  "Mark up to but not including ARGth occurrence of CHAR."
  (interactive (list (prefix-numeric-value current-prefix-arg)
                     (read-char "Mark to char: " t)))
  (set-mark
   (save-excursion
     (move-to-char arg char)
     (backward-char)
     (point))))

(defun mark-to-char-inclusive (arg char)
  "Mark up to and including ARGth occurrence of CHAR."
  (interactive (list (prefix-numeric-value current-prefix-arg)
                     (read-char "Mark to char: " t)))
  (set-mark
   (save-excursion
     (move-to-char arg char)
     (point))))

(use-package expand-region) ;; for the er/* commands

I've been using this since 2017. It's now 2022. How did it go?

Old habits are hard to break. I used this for urls, words, strings, almost none of the others. It's easier for me to move the cursor manually than to learn the specific commands, unless the command is something I use often.

So I'm going to call this experiment complete. I learned that it it's not going to work for me. That's ok. I try lots of things and most don't work. Some do, which is why I keep trying.

I still think the idea is good but I have 30 years of emacs muscle memory to fight.

I considered switching to one of these:

  • mark-thing-at lets you define keys for marking each type of thin
  • objed lets you work on text objects, inspired by vim and kakoune
  • expand-region will guess the object instead of making me choose

I decided I'll remove my experiment code and try expand-region next. [update 2025-05: gave up on expand-region in general, as I only use mark word or url, so I wrote something specific to that.]

-1:-- Emacs: marking text (Post Amit Patel)--L0--C0--2025-06-24T21:48:08.272Z

Rahul Juliato: Setting up Emacs native tab-bar and tab-bar-groups for a tmux-like experience

Explore how to turn Emacs' native tab-bar and tab-bar-groups into a powerful, tmux-like window and session management experience—no external packages needed. Organize your workflows with tabs, group them by project or context, and navigate with ease inside your Emacs session, all while keeping tmux nearby for when it still shines.

Here I'm traversing an open session using this concept of organization by simply issuing C-TAB.

tab-bar-config-demo

If you prefer not to show the group name, want to display buffer names, use other custom decorations, jump right into your group, don’t worry, we’ll explore all these possibilities step by step.

Now, how do we achieve this! 🤩


Motivation

It’s no secret that many Emacs users take advantage of its excellent window management capabilities, like: splitting windows, saving layouts, undoing and redoing them and even use tab-bar as a sort of tmux-like workflow.

What I’m presenting here takes it a step further: bringing in a "split by session" feature, just like tmux UI. In other words, we’re expanding our window management arsenal with:

➖ Tabs, as in Emacs we call it tab-bar (not to be confused with the VSCode-like tab-line mode): which can hold splits of different buffers, either in the same file, different files, terminals, and everything else Emacs can print in a buffer.

➖ Tab Groups, which can hold groups of tabs, mimicking sessions as we call them in tmux, or perspectives if you know this concept from persp-mode or perspective-el, or even activities if you use Doom Emacs.

Also, did I mention we're going to do it without any external package dependencies?

With the provided configuration, we're going to organize our current running Emacs session in "two levels":

The 'tab-bar-groups'

This level holds the tab-group. This might contain a "topic" like "Chat", "Mail" or "News", or simply your project name like "My Project", or if you're working with multiple projects at the same time, one level that might be organized by "Your Workflow". And of course, you can have all of this at the same time, like:

tab-bar-groups

The 'tab-bars'

This level contains your tabs, which can hold all sorts of window arrangements (for the uninitiated, from Emacs's point of view, the OS- level 'window' that holds the Emacs application is called a 'frame', while 'windows' are the inner splits that hold our buffers).

tab-bar-groups-group


So, first things first. I'm reproducing here the steps to the final form I just showed. But of course, it is all customizable. Want to do another sort of decorations? Want to hide the group name? Want to show filenames? Want to navigate differently? Go for it! It is all transparent to you!

Variables configurations

This is personal taste, take a look at each variable's documentation and tweak it yourself, basically:

➖ I do not want the close button, nor the new button, as I seldom use mouse navigation.

➖ I do want tab-hints, which are numbers on each tab-name for better navigation. I do override the internal function, though, to get it "decorated" my way.

➖ I want a clean separator, so, a single space.

➖ We want the tab-group name shown, hence we add to tab-bar-format the tab-bar-format-tabs-groups option.

All of this can be defined with:

(setq tab-bar-close-button-show nil)
  (setq tab-bar-new-button-show nil)
  (setq tab-bar-tab-hints t)
  (setq tab-bar-auto-width nil)
  (setq tab-bar-separator " ")
  (setq tab-bar-format '(tab-bar-format-tabs-groups
					tab-bar-format-tabs tab-bar-separator
					tab-bar-format-add-tab))

A few (IMHO justified) overrides

Tab bar doesn't allow us many customizations. Fortunately, we can override a couple of functions as they're small and easy to keep up with. Of course, this is totally optional; I'm just trying to mimic a more tmux-like UI feel.

First, tab-bar-tab-name-format-hints: I want to put some arrows around the hints number, and NOT show the buffer name.

(defun tab-bar-tab-name-format-hints (name _tab i)
	  (if tab-bar-tab-hints (concat (format "»%d«" i) "") name))

Second, tab-bar-tab-group-format-default: By default, groups show the hint of the first tab under it. I want a clean group name, so:

(defun tab-bar-tab-group-format-default (tab _i &optional current-p)
	(propertize
	 (concat (funcall tab-bar-tab-group-function tab))
	 'face (if current-p 'tab-bar-tab-group-current 'tab-bar-tab-group-inactive)))

Nice QoL Utility functions

With the above config, we can already do something like C-x t G, setting a group name for your current tab and start organizing your life!

You could also have automatically groups created by setting tab-group in your display-buffer-alist, like:

(add-to-list 'display-buffer-alist
			   '("\\*scratch\\*"
				 (display-buffer-in-tab display-buffer-full-frame)
				 (tab-group . "[EMACS]")))

We're not focusing on automatically tab-grouping stuff in this post though.

Truth is, yes, I want groups for my News, Mail, Chat, but most of my work is done in the form of Projects.

And yes, I want these settings to be manually issued. I can recall the pain of having to sneak-peak another project utility function or doc, just to have my crazy custom persp-mode pulling a new persp and messing with everything.

Function to set tab to group based on project

So, I want a function that can "promote" my current tab to the group [ProjectName], creating it if there are none. Of course, if the current buffer is part of a project. This allows me to switch projects, open new splits, without automagic jumps.

Here we have a function to do so, and a suggested bind:

(defun emacs-solo/tab-group-from-project ()
	"Call `tab-group` with the current project name as the group."
	(interactive)
	(when-let* ((proj (project-current))
				(name (file-name-nondirectory
					   (directory-file-name (project-root proj)))))
	  (tab-group (format "[%s]" name))))

  (global-set-key (kbd "C-x t P") #'emacs-solo/tab-group-from-project)

So, recap: I can C-x t G and "add" my tab to a group, and now I can also simply C-x t P and "add" my tab to the project group.

😎 Workflow?

C-x t p p: starts a new tab selecting a project

➖ Select a file, dired or shell...

C-x t P: add your new tab to the project group, creating it

Want some more tabs?

C-x t 2 will automatically add tabs to your current group.

Isn't it nice? Now, you can feel the power in your hands, you open 10 projects, you create a bunch of groups for your inner Emacs is my OS workflow, how do you traverse all this madness?

Function to jump to group

I found my self abusing of the default C-TAB and C-S-TAB to quickly "jump" between closer tabs. Now, I wanna quickly check my Mail, I'd like something more "precise" jumping than eye balling everything.

This is were our second utility function comes to hand:

(defun emacs-solo/tab-switch-to-group ()
  "Prompt for a tab group and switch to its first tab.
Uses position instead of index field."
  (interactive)
  (let* ((tabs (funcall tab-bar-tabs-function)))
	(let* ((groups (delete-dups (mapcar (lambda (tab)
										  (funcall tab-bar-tab-group-function tab))
										tabs)))
		   (group (completing-read "Switch to group: " groups nil t)))
	  (let ((i 1) (found nil))
		(dolist (tab tabs)
		  (let ((tab-group (funcall tab-bar-tab-group-function tab)))
			(when (and (not found)
					   (string= tab-group group))
			  (setq found t)
			  (tab-bar-select-tab i)))
		  (setq i (1+ i)))))))
  (global-set-key (kbd "C-x t g") #'emacs-solo/tab-switch-to-group)

This allows us to "list all available groups", select and switch to the first tab of that group.

tab-bar-group-change

Packing the entire config

The code here presented by parts is now part of my emacs-solo config (hence the prefix on the function names), I usually keep my configuration somewhat organized by use-package blocks, they keep everything in the right place and I suggest you do the same. Also it is a lot faster to grab this code, copy and paste to your config and make it work!

(use-package tab-bar
  :ensure nil
  :defer t
  :custom
  (tab-bar-close-button-show nil)
  (tab-bar-new-button-show nil)
  (tab-bar-tab-hints t)
  (tab-bar-auto-width nil)
  (tab-bar-separator " ")
  (tab-bar-format '(tab-bar-format-tabs-groups
					Tab-bar-format-tabs tab-bar-separator
					tab-bar-format-add-tab))
  :init
  ;;; --- OPTIONAL INTERNAL FN OVERRIDES TO DECORATE NAMES
  (defun tab-bar-tab-name-format-hints (name _tab i)
	  (if tab-bar-tab-hints (concat (format "»%d«" i) "") name))

  (defun tab-bar-tab-group-format-default (tab _i &optional current-p)
	(propertize
	 (concat (funcall tab-bar-tab-group-function tab))
	 'face (if current-p 'tab-bar-tab-group-current 'tab-bar-tab-group-inactive)))


  ;;; --- UTILITIES FUNCTIONS
  (defun emacs-solo/tab-group-from-project ()
	"Call `tab-group` with the current project name as the group."
	(interactive)
	(when-let* ((proj (project-current))
				(name (file-name-nondirectory
					   (directory-file-name (project-root proj)))))
	  (tab-group (format "[%s]" name))))

  (defun emacs-solo/tab-switch-to-group ()
  "Prompt for a tab group and switch to its first tab.
Uses position instead of index field."
  (interactive)
  (let* ((tabs (funcall tab-bar-tabs-function)))
	(let* ((groups (delete-dups (mapcar (lambda (tab)
										  (funcall tab-bar-tab-group-function tab))
										tabs)))
		   (group (completing-read "Switch to group: " groups nil t)))
	  (let ((i 1) (found nil))
		(dolist (tab tabs)
		  (let ((tab-group (funcall tab-bar-tab-group-function tab)))
			(when (and (not found)
					   (string= tab-group group))
			  (setq found t)
			  (tab-bar-select-tab i)))
		  (setq i (1+ i)))))))

  ;;; --- EXTRA KEYBINDINGS
  (global-set-key (kbd "C-x t P") #'emacs-solo/tab-group-from-project)
  (global-set-key (kbd "C-x t g") #'emacs-solo/tab-switch-to-group)

  ;;; --- TURNS ON BY DEFAULT
  (tab-bar-mode 1))

Customizations on tab-bar-properties

You might want to customize the tab-bar line, what I am using in these screenshots is:

(custom-set-faces
  '(tab-bar
	((t (:background "#232635" :foreground "#A6Accd"))))
  '(tab-bar-tab
	((t (:background "#232635" :underline t))))
  '(tab-bar-tab-inactive
	((t ( ;; :background "#232635" ;; uncomment to use this
		  ;; :box (:line-width 1 :color "#676E95")
		  ))))
  '(tab-bar-tab-group-current
	((t (:background "#232635" :foreground "#A6Accd" :underline t))))
  '(tab-bar-tab-group-inactive
	((t (:background "#232635" :foreground "#777")))))

So, time to ditch tmux?

I wish...

This functionality is indeed very useful, the UI mimics tmux-like power. And if this is enough for you, go for it! Ditch tmux!

For my use cases, the sheer possibility of any of my emacs-lisp code locking the one and only Emacs process means my beautifully designed and crafted Emacs session is going bye-bye with it. And yes, while emacs --daemon and restarting clients helps a lot here, let’s not pretend Emacs never goes sideways.

There are still solid reasons to keep tmux around:

Fault tolerance. When you’re SSH’d into a remote machine and something crashes, tmux is still there, your shell lives on. Emacs tabs don’t protect you from network drops or X11/Wayland hiccups.

Shell multiplexing. Sometimes you just want 3 quick shells, nothing fancy, don’t even want to boot up Emacs. tmux wins here. Fast, lightweight, and scriptable. You just install tmux, no fancy config needed to 'just use it'.

System-level process separation. I like to keep long-running REPLs, tailing logs, or even a docker attach session in tmux. If Emacs dies, they don’t.

Startup time. Emacs with heavy configuration can still take a second to feel "fully alive". When I want to attach to a ready-to-go shell session instantly, tmux a is just faster.

Better separation. While the whole tab-bar and tab-group approach is super flexible, sometimes you just need the hard boundary of a terminal session completely isolated from the rest. There are things you do outside Emacs for good reason.

And let’s be honest, you don’t need to choose. These tools complement each other. What this configuration gives you is a powerful Emacs-as-an-OS experience, with clarity, agility, and a clean mental model. Use Emacs for your inner workflows, and tmux as your outer shell guardian.


Wrapping Up

With just a few lines of Elisp, no external packages, and some clever overriding, Emacs’ tab-bar and tab-bar-groups become serious productivity tools. If you’re someone juggling multiple projects, workflows, or simply enjoy clean organization inside your Emacs session, this setup gives you control and clarity.

While we might not throw tmux out of the toolbox just yet, we now have a native Emacs experience that feels modern, fast, and surprisingly intuitive. Use what’s best for your workflow, but know that Emacs is more than capable of stepping up its game.

So go ahead, give it a try, tweak it, theme it, and make Emacs your tmux... and more.

Happy hacking. ✨💻🤓🚀

-1:-- Setting up Emacs native tab-bar and tab-bar-groups for a tmux-like experience (Post Rahul Juliato)--L0--C0--2025-06-24T20:00:00.000Z

Irreal: Making TRAMP Faster

One of Emacs’ really nice features is TRAMP. It allows you to edit files on a remote system and otherwise interact with that system as if you were working on your local machine. The problem with it is that it can sometimes be slow, even unusably so.

Troy Hinckley over at Core Dumped has a very interesting post on making TRAMP faster. Oddly, almost everything he does is merely adjusting settings. He writes a bit of code to implement caching but even that is minimal.

Hinckley’s post is long and complicated because he explains what he’s doing and why it works. He’s spent a lot of time profiling and researching how TRAMP works. That’s nice because it means that he’s not just trying things to see if they work.

This is a short post because rather than simply listing the things that Hinckley did without explaining the reasons for them, I’m going to send you over to his post to get the full story. If you’ve tried using TRAMP but found it too slow for your particular workflow, take a look at Hinckley’s post. His changes are easy to implement so you can try them out without a lot of effort.

Happily, my use of TRAMP mostly involves the local network or remotes connected with a high speed network so I usually don’t experience these slowdowns. Even so, it’s probably worth implementing his changes.

-1:-- Making TRAMP Faster (Post Irreal)--L0--C0--2025-06-24T15:29:01.000Z

Christian Tietze: Emacs Carnival 2025-06: Take Two

To kickstart the Emacs Carnival, and as a hat tip to our recent inspiration, this month’s topic is borrowed from the June IndieWeb Blog Carnival: the topic is “Take Two”, hosted by Nick Simon:

Ever wish for a do-over? “Take two!” (or three, four, etc.) might be shouted by a film director or audio engineer looking to get a somewhat different outcome from a group of actors or musical performers. Would you like a second shot at something that didn’t land?

This is a perfect fit for Emacs users:

We usually don’t nail “it” on our first try – init.el bancruptcy; refactoring hacky Emacs Lisp code; leaving Emacs only to come back englightened much later; running two Emacsens in parallel. There are plenty of possible second takes when it comes to Emacs!

So for this month, our first community blog carnival, I want you to:

  • meditate on “Emacs” and “take two”,
  • blog about it,
  • then send me a link to your blog post.

That’s it! I’ll aggregate submissions for all of June in this post.

Don’t have a blog, yet?

Well, just start one already! :)

It’s the future of the internet!

Blogging is the future, and the future is now!

What’s a Blog Carnival, Again?

This is our first Emacs Carnival, so you may not know the format. A blog carnival is a fun way to tie together a community with shared writing prompts, and marvel at all the creative interpretations of the topic of the month. I’ve provided a couple of interpretations above, but you may think of something else entirely. That’s amazing, roll with it, that’s what makes this fun!

Thanks to Sacha Chua for her post “Working on the plumbing of a small web community” that was part of the May 2025 IndieWeb Carnival, and got me thinking why there’s no Emacs Carnival, yet, and for her encouragement to kickstart this.

For future Carnivals, check out the “Carnival” page on EmacsWiki . It includes instructions, and is our community space to coordinate participants and topic ideas.

Submissions

Comment below or DM/email me with your submission! I’ll collect submissions up to, and including, July 1st (Central European Time), so that every time zone has had a chance to meet the June 30th deadline :)

Bonus: Submit your indie blog post to Nick as well (he really said so)!

These have been submitted as of 2025-06-11:

  1. Kemal: Announcing Brainiac Project
  2. Jeremy Friesen: “Take Two”
  3. Carlos Pajuelo Rojo: “Take two on Emacs (Toma dos de Emacs)”
  4. Mike Hostetler: Emacs: Take 2
  5. Sacha Chua: Thinking about time travel with the Emacs text editor, Org Mode, and backups
  6. Shom: Undo finally clicked with vundo

Hire me for freelance macOS/iOS work and consulting.

Buy my apps.

Receive new posts via email.

-1:-- Emacs Carnival 2025-06: Take Two (Post Christian Tietze)--L0--C0--2025-06-23T20:13:58.000Z

Irreal: Undergrad Notes

Chris Maiorana has an interesting post on note taking with Emacs. He says that Emacs and, in particular Org mode, have all sorts of functions to make taking notes easier but that it’s easy to get bogged down in trying to decide what packages to use and how to organize your notes.

Maiorana’s suggestion is to just take notes and not worry about any details in the beginning. After you have a hundred or so notes you can worry about the best way to organize them and you’ll have a better idea of what packages you should use to take and organize them.

He has another suggestion for the type of note to take. It’s something he calls “The Undergrad Note”. The idea is that each note has

  1. A claim
  2. A quote illustrating the claim
  3. Your explanation or justification for the claim

He gives an example from Dostoyevsky’s Notes From Underground. It illustrates nicely how the method works: You make a suggestion or supposition about something, supply a quote or other evidence about the quote/claim, and, finally, offer a justification for your claim.

His post is a reminder that you can’t do it all with tools. Sometimes you need a method too. The Undergrad Note method probably won’t be a perfect fit for your needs but as Maiorana says, that doesn’t matter. The point is to get use to taking notes and discover the best method for you to do that.

-1:-- Undergrad Notes (Post Irreal)--L0--C0--2025-06-23T14:37:25.000Z

Sacha Chua: 2025-06-23 Emacs news

[2025-06-25 Wed]: Removed reposted video.

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-06-23 Emacs news (Post Sacha Chua)--L0--C0--2025-06-23T13:52:49.000Z

Marcin Borkowski: Making functions interactive

I have to admit that I probably have a bit of OCD, and when I finish my work, I like to put my Emacs in a sort of “clean slate” state. By that I don’t mean closing it or killing all buffers (although when I leave the office, I do kill all work-related buffers). Instead, I mean going back to displaying just one window with the *GNU Emacs* buffer (the one with the logo and links to the tutorial etc.) The problem is, I sometimes accidentally kill that buffer, and then I have no place to go back to;-). Well, some time ago it occurred to me that something in Emacs must create that buffer, and I can find that something and learn how to do that whenever I want.
-1:-- Making functions interactive (Post Marcin Borkowski)--L0--C0--2025-06-23T06:20:20.000Z

Peter Povinec: Fault-tolerant Org Links

I’m sure many Org users have experienced this: you reorganize your notes, maybe renaming a file or moving a section to a different Org file, and a few weeks later you open a link in another note only to be greeted by an unexpected prompt: “No match - create this as a new heading?”. Org tries to be helpful, even creating a new buffer for the non-existent file, assuming all along that you are creating a wiki and normally insert in your text links to targets that don't exist yet. But what if that is not your use-case? What if, instead of popping a new buffer and disrupting your flow, you want to be told that you got a broken link (knowing full well that the link target exists somewhere)? Then you can utter an expletive and carry on reading whatever you were reading, or try to find the intended target and fix the link.

Broken Org links are an unfortunate fact of life when your files and headings change over time. In my case, I kept stumbling on dead links in my org notes that have been curated for decades and survived multiple moves between cloud storage providers, note management systems (remember remember.el?), and other reorgs. I am not a big fan of spending a lot of time migrating my files and rewiring everything proactively. I wished for an Org setup that would detect a broken link and fix it right there and then, as I tried to follow it. In a sense, I wished for Org links to be fault-tolerant. At the same time, I didn't want a heavy solution, with its own consistency and maintenance burden, like globally unique Org Ids or a custom database.

I created a small set of tools to help detect and repair broken links in my Org files on the fly. My Org Link Repair code consists of three little helpers:

  • A checker hook /org-test-file-link that intercepts broken links before Org tries to apply its built-in 'nonexistent target' logic.
  • A transient menu /olr-transient to provide a quick interface for automated and manual broken link recovery tasks.
  • An interactive repair mode /olr-manual-mode that guides a user through fixing broken links one by one.

Together, these additions make it much easier to stay on top of link rot in my notes without altering how I normally create and use Org links. Let’s look at each part and how they work together in practice.

A side note on the UX: One of my design goals was to guide the user to perform the needed actions without relying on their familiarity with Org Link Repair flow. I expect this flow to be exercised rarely enough that even a user who has done it before is not expected to remember key bindings or the steps to repair their broken link. The code should try to make the process seamless and straightforward.

The helpers that I show are meant as a starting point and can be adapted or extended. I implemented detection of broken file links and a manual (user-assisted) repair strategy, because file links were the ones breaking for me and the manual strategy is the most general (the correct target file may be in an abandoned Google drive, an encrypted file bundle, or anywhere). Other link types could be tested and different repair strategies could be implemented, including a fully automated strategy, if the likely target file location is known, or can be easily searched for. Even web links could be handled similarly: detect broken links to web pages that have disappeared, and rewrite them to use a web archive (like the Wayback machine).

If you can’t prevent links from breaking, at least make them easy to find and fix.

Catching Broken Links

The first thing to do is to change the value of org-link-search-must-match-exact-headline from its default setting of query-to-create. That eliminates the wiki-centric query to create a new heading when following a broken link. But it doesn't prevent Org from popping a new buffer for a link pointing to a nonexistent file name. To suppress that, we need to do a bit more work.

Luckily Org developers provided the org-open-at-point-functions hook which makes it straightforward to intercept the link opening flow and detect a broken link due to non-existent file early. Here is my interceptor that checks for broken file links and bails out with a user error on non-existent files. It could be expanded to handle other link types and other broken link scenarios. Note that the error message tells the user what key binding to use to initiate the link repair.

(custom-set-variables '(org-link-search-must-match-exact-headline t))

(defun /org-test-file-link ()
  "Check if the file link target file exists before following it."
  (let ((ctx (org-element-context)))
    (when (and (eq (org-element-type ctx) 'link)
               (string= (org-element-property :type ctx) "file"))
      (let ((file (org-element-property :path ctx)))
        ;; If the file exists, return nil to let org-open-at-point continue
        (if (not (file-exists-p file))
            (user-error (concat "Target file not found; Use "
                                (substitute-command-keys "\\[/olr-transient]")
                                " to repair link to %s") file))))))

(add-hook 'org-open-at-point-functions #'/org-test-file-link)

A Transient Menu for Link Repair Tasks

I am using Emacs’ Transient library (the same engine behind Magit’s menus) to create a one-stop menu for all Org Link Repair activities. The command /olr-transient is a prefix command that, when invoked, pops up a transient menu with several relevant actions. This spares me from memorizing multiple separate commands or key bindings. I just hit one key sequence to get the menu, then select what I need. Here’s my initial definition of the transient menu:

(transient-define-prefix /olr-transient ()
  "Transient menu for Org Link Repair."
  [:description "Org Link Repair transient: fix your broken links\n\n"
   ["Test/Repair"
    ("l" "Lint all links in the buffer" /org-lint-links :transient nil)
    ("m" "Manually find the new target" /olr-manual-mode :transient nil)]
   ["Display/Navigate"
    ("n" "Next link" org-next-link :transient t)
    ("p" "Previous link" org-previous-link :transient t)
    ("d" "Display toggle" org-toggle-link-display :transient t)]
   ["Other"
    ("q" "Quit" transient-quit-one :transient nil)]])

(global-set-key (kbd "<f2> <return>") #'/olr-transient)

The manual repair strategy is the only one offered for now. The menu also offers linting the links in the current buffer (I have a customized version of the built-in org-lint for that), link navigation and display toggling commands.

Using a transient menu here feels like overkill for just a few commands, but I anticipate adding more link-related utilities over time. Even now, it’s nice to have a single “hub” for link management. I don’t use it every day, but when I suspect there might be broken links, I know where to go. It’s also convenient when a broken link does pop up unexpectedly. I can quickly bring up this menu and choose to repair it on the spot.

Manual Repair Strategy — Guided Link Fixing

This is the most general strategy which is why I implemented it first. The tradeoff is that it relies on the user knowing where the intended link target is and navigating to it. I found that I usually remember what happened to my abandoned Org files, even after years of not visiting them. I can usually recover them from an old archive, or one of my no-longer-used Dropbox accounts.

The strategy implements a global minor mode and a set of functions to initiate the repair flow and to complete it. When the user chooses to use this strategy, the code remembers the current location (the location of the broken link) and activates the /olr-manual-mode minor mode while the user is free to do whatever they need to locate the correct target org file and a headline. A mode line lighter provides a visual clue that the repair flow is in progress. Once the target has been located, the user would hit C-c C-c to complete the repair, which will interpret the current point as the intended link target. The code will replace the broken link at the starting location with the new link. The user is free to abandon the flow at any time with C-c C-k.

Here is my code:

;; 
;;; Org Link Repair - Manual (user-assisted) Strategy
;; 
(defvar /olr-manual-marker nil
  "Marker pointing at the original (broken) link.")

(defvar /olr-manual-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "C-c C-c") #'/olr-manual-complete)
    (define-key map (kbd "C-c C-k") #'/olr-manual-abort)
    map)
  "Keymap for `/olr-manual-mode'.")

(easy-menu-define /olr-manual-mode-menu /olr-manual-mode-map
  "Menu for OLR Manual Mode"
  '("OrgLinkRepairManualMode"
    ["Complete" /olr-manual-complete t]
    ["Abort" /olr-manual-abort t]))

(define-minor-mode /olr-manual-mode
  "Global minor mode for Org Link Repair manual strategy.
When enabled, the marker pointing at the link at point is saved.  The user
is expected to navigate to where the link should be pointing at and call
`/olr-manual-complete' to repair the link, or `/olr-manual-abort' to cancel.
Attempting to enable this minor mode outside an Org-mode derivative, or
if the point is not at an Org link will fail with a user error."
  :lighter " LinkRepair"
  :global t

  (if (not /olr-manual-mode)
      (setq /olr-manual-marker nil)
    (unless (derived-mode-p 'org-mode)
      (user-error "Not in an Org buffer"))
    (unless (eq (org-element-type (org-element-context)) 'link)
      (user-error "Not at an Org link"))
    (setq /olr-manual-marker (point-marker))
    (message
     (substitute-command-keys
      "Manual link repair mode initiated.  Navigate to intended link target,
press \\[/olr-manual-complete] to complete, or \\[/olr-manual-abort] to abort."
      ))))

(defun /olr-manual-complete ()
   "Complete Org Link Repair by replacing the broken link at saved marker
with a new link targeted at point.
The user is expected to have navigated to the location of the new link target.
This function will call `org-store-link', then use `org-insert-all-links' to
replace the broken link, location of which was saved by `/olr-manual-mode'."
  (interactive)
  (org-store-link nil t)
  (unless (and /olr-manual-marker (marker-position /olr-manual-marker))
    (error "OrgLinkRepair: Lost marker to the original link location"))
  (switch-to-buffer (marker-buffer /olr-manual-marker))
  (goto-char (marker-position /olr-manual-marker))
  (/olr-manual-mode -1)
  (let* ((oldctx (org-element-context))
         (oldstart (org-element-property :begin oldctx))
         (oldend (org-element-property :end oldctx))
         oldlink newlink)
    ;; Delete the old link at point
    (when (and oldstart oldend)
      (setq oldlink (buffer-substring oldstart oldend))
      (delete-region oldstart oldend))
    ;; Insert the new link
    (org-insert-all-links 1 "" "")
    (let* ((newctx (org-element-context))
           (newstart (org-element-property :begin newctx))
           (newend (org-element-property :end newctx)))
      (goto-char newstart)
      (setq newlink (buffer-substring newstart newend)))
    ;; Notify the user: audibly+visibly (hopefully after auto-revert messages)
    (ding)
    (run-with-idle-timer
     0.2 nil
     (lambda () (message (concat "Modified buffer by replacing link %s with %s."
                            "\nSave the buffer to keep changes!")
                    oldlink newlink)))))

(defun /olr-manual-abort ()
  "Abort manual Org Link Repair."
  (interactive)
  (unless (and /olr-manual-marker (marker-position /olr-manual-marker))
    (error "OrgLinkRepair: Lost marker to the original link location"))
  (switch-to-buffer (marker-buffer /olr-manual-marker))
  (goto-char (marker-position /olr-manual-marker))
  (/olr-manual-mode -1)
  ;; Notify the user
  (message "Org Link Repair aborted."))

Limitations and Next Steps

Not a Complete Solution: This toolkit currently provides early interception for broken file links only. It could be extended to catch other link types if doing it early would be beneficial. For example opening web links may pop a browser window, which is annoying if we could know ahead of time that it will fail. The manual repair strategy will work for any link type, as long as it is supported by org-store-link. Again, not for web links opened in a browser.

Manual Effort: While the repair mode makes fixing easier, it’s still a manual process. I have to find the new targets or decide to remove links. There’s room for automation, e.g. suggesting likely new locations for a file (perhaps by searching for a filename in a known directory). At the moment, I actually prefer the manual control, but smarter suggestions could speed things up.

Workflow UX: I experimented with making a nicer user experience during the manual link repair workflow. I wanted to make it visually clear that the user is in the workflow and is expected to either complete it or abort it. The global minor mode lighter in the mode line doesn't seem to be enough. I tried sticking a header-line at the top, displaying a banner message and key bindings to complete/abort, but it was not reliable, and didn't look great either. I have some other ideas to try, but if you have a suggestion please let me know.

Despite these limitations, the gain in convenience has been huge for me. I can freely rename files or reorganize headings, knowing that if I forget to update a reference, Emacs will help me catch it later. And fixing it is straightforward. This is a relatively small addition to my Emacs config (just a few dozen lines of Elisp), but it solves an annoying real problem that used to steal time and momentum. And by the way, I do have LLM generated test cases for this code (see my previous blog post).

Enjoy the malleability of Emacs and the freedom it gives you!

Discuss this post on Reddit.

-1:-- Fault-tolerant Org Links (Post Peter Povinec)--L0--C0--2025-06-22T18:01:00.000Z

Irreal: Running Emacs On Multiple Platforms

Many of us run Emacs on multiple platforms. Some, like me, simply run several OSs on our various computers, and some have one system type at work and another at home. Regardless, we all want a one true Emacs configuration that adapts itself to whatever system it’s running on. Using a single configuration file means that you can make additions or changes and have them reflected across all your systems. Trying to keep such changes synced across several configuration files will, believe me, end in tears

Over at The Emacs Cat, olddeuteronomy, who deals with three different operating systems, explains how he handles a single Emacs configuration for all his systems. The key to all such systems is using the Emacs variable system-type to discover what OS you’re currently running on and then conditionally executing system specific settings. Since most settings don’t depend on the system type, this makes it easy to adjust general configuration items and have them take effect across all the systems at the same time.

Rather than clutter up my init.el with a bunch of conditionals, I use the following code at the start of my init.el:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Pull in system and platform specific configurations                    ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Just keep on going if the requisite file isn't there.
;; Manipulations in second load is for "gnu/linux" → "linux"

(load (car (split-string (system-name) "\\.")) t)
(load (car (reverse (split-string (symbol-name system-type) "/"))) t)

The first statement loads a file with the same name as the computer’s name. The second loads a file with the name of the OS type. That way I can have a file, named darwin.el, with all the macOS specific settings and another for Linux specific settings. Similarly, if I need some machine specific settings—such as screen size—I simply put them in a file with the machine’s name and the settings get applied automatically. I don’t remember where I got the idea for that strategy but I’ve been using it for a long time and it works well for me.

Whatever method you use, if you’re dealing with more than one machine you probably need to adjust your init.el to handle them all seamlessly.

-1:-- Running Emacs On Multiple Platforms (Post Irreal)--L0--C0--2025-06-22T16:51:43.000Z

Irreal: How To Live Inside Emacs

Over at the Emacs subreddit, kudikarasavasa asks a question dear to the hearts of many of us: How did you start living inside Emacs permanently? He says he keeps hearing people say they live inside Emacs and the idea seems appealing to him but he doesn’t understand how people made the journey. Did they start out to live in Emacs or was it just something that happened along the way?

Of course, it’s the answers not the question that are interesting. There are all sorts of stories. The majority of them say that living in Emacs is just something that happens after you’ve been using Emacs for a while. The usual story is something along the lines of starting to use Emacs, liking the interface, and moving all the easy tasks, coding, writing, note taking, agendas and TODO lists, and maybe even version control via Magit, into Emacs. Later, these people find, as I did, that writing and editing in other applications has become painful.

So they move “harder” applications—like email and RSS readers—into Emacs. Pretty soon, they start thinking, “why should I have to go a separate app to play music?” and they move their music playing into Emacs. Before you know it, they’re doing everything except, perhaps, browsing from within Emacs. Like me, they probably have a couple of tasks that they use other apps for—calendar, texting, and reminders for me—and that remain outside the Emacs sphere but essentially everything else is done from the comfort of Emacs.

Some of the other people are a bit more hardcore. I especially liked this quote:

The alternative journey is you read a lot of stories about Lisp, get hyped about Lisp machines, figure out that Emacs is the best you can actually get, and decide to run with it as far as you can.

I can relate.

It’s worth reading all the comments because there are several useful suggestions for dealing with things like shells and remote editing. As this post shows, it’s pretty easy to move the majority of your computer tasks into Emacs. It’s a really interesting discussion and well worth spending a few minutes reading.

-1:-- How To Live Inside Emacs (Post Irreal)--L0--C0--2025-06-21T14:54:53.000Z

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

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.

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

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

-1:-- Thinking about time travel with the Emacs text editor, Org Mode, and backups (Post Sacha Chua)--L0--C0--2025-06-21T12:48:03.000Z

James Dyer: Ollama-Buddy 0.13.1: Curl backend support and some optimizations

This is more of a maintenance update. The main headline is the addition of an option to select a curl-based backend for those who may encounter networking issues. By default, ollama-buddy uses built-in low-level networking calls, but if you have network issues, you can now switch to curl!

<2025-06-21 Sat> 0.13.1

Refactored content register processing to be more efficient and added a new Emacs package brainstorming prompt file.

<2025-06-15 Sun> 0.13.0

Added curl communication backend with fallback support

  • Added ollama-buddy-curl.el as separate backend implementation
  • Implemented backend dispatcher system in ollama-buddy-core.el
  • Updated all async functions to use backend dispatcher
  • Added curl backend validation and testing functions
  • Maintained full compatibility with existing network process backend

When building Emacs packages that communicate with external services, network connectivity can sometimes be a pain point. While Emacs’s built-in make-network-process works great in most cases, some users have encountered issues on certain systems or network configurations. That’s why now I have added a curl-based communication backend to give you an additional option, who knows, maybe it will solve your ollama communication issues!

The original ollama-buddy implementation relied exclusively on Emacs’s native network process functionality. While this works well for most users, I occasionally heard from users who experienced network process failures/flakiness on certain systems.

Rather than trying to work around these edge cases in the network process code, I took a different approach: I added curl as an alternative communication backend! This gives users a battle-tested, widely-available HTTP client as a fallback option.

Users can enable the curl backend with a simple customization:

(use-package ollama-buddy
  :bind
  ("C-c o" . ollama-buddy-menu)
  ("C-c O" . ollama-buddy-transient-menu-wrapper)
  :config
  ;; Load curl backend first
  (require 'ollama-buddy-curl nil t)

  ;; Then set the backend
  (setq ollama-buddy-communication-backend 'curl))

and then switch backends from the chat buffer C-c e

The curl backend supports everything the network backend does:

  • Real-time streaming responses
  • Vision model support with image attachments
  • File attachments and context
  • All Ollama API parameters
  • Multishot model sequences

If curl is selected but not available, the system automatically falls back to the network process with a helpful warning message.

From a user perspective, the backend choice is largely transparent. The main indicators are:

  • Status line shows [C] for curl or [N] for network
  • Process list shows ollama-chat-curl vs ollama-chat-stream processes
  • Curl backend shows “Curl Processing…” in status messages

Everything else - streaming behaviour, response formatting, error handling - works identically regardless of the backend.

<2025-05-31 Sat> 0.12.1

Optimized Unicode escape function to fix blocking with large file attachments

  • Fixed UI blocking when sending large attached files
  • Used temp buffer with delete-char/insert instead of repeated concat calls
  • Reduced processing time from minutes to milliseconds for large payloads
-1:-- Ollama-Buddy 0.13.1: Curl backend support and some optimizations (Post James Dyer)--L0--C0--2025-06-21T09:05:00.000Z

Sacha Chua: Transforming HTML clipboard contents with Emacs to smooth out Mailchimp annoyances: dates, images, comments, colours

I've recently started handling the Bike Brigade newsletter, so now I'm itching to solve the little bits of friction that get in my way when I work with the rich-text Mailchimp block editor.

I'm not quite ready to generate everything with Org Mode. Sometimes other people go in and edit the newsletter through the web interface, so I shouldn't just dump a bunch of HTML in. (We don't have the more expensive plan that would allow me to make editable templates.) I draft the newsletter as a Slack canvas so more people can weigh in with their suggestions:

2025-06-20_20-58-49.png
Figure 1: Screenshot of Slack canvas

And then I redo it in Mailchimp:

2025-06-20_21-01-08.png
Figure 2: Screenshot of Mailchimp design

My process is roughly:

  1. Duplicate blocks.
  2. Copy the text for each item and paste it in. Adjust formatting.
  3. Update the dates and links. Flip back and forth between the dispatch webpage and Mailchimp, getting the links and the dates just right.
  4. Download images one by one.
  5. Replace the images by uploading the saved images. Hunt through lots of files named image (3).png, image (4).png, and so on. Update their attributes and links.
  6. Change text and link colours as needed by manually selecting the text, clicking on the colour button in the toolbar, and selecting the correct colour.
  7. Change the text on each button. Switch to Slack, copy the link, switch back to Mailchimp, and update the link.

I think I can get Emacs to make things easier.

Automating buttons

The newsletter includes a button to make it easier to volunteer for deliveries. In case people want to plan ahead, I also include a link to the following week's signups.

Dates are fiddly and error-prone, so I want to automate them. I can use a Mailchimp code block to paste in some HTML directly, since I don't think other people will need to edit this button. Here I take advantage of org-read-date's clever date parsing so that I can specify dates like +2Sun to mean two Sundays from now. That way, I don't have to do any date calculations myself.

This code generates something like this:

2025-06-20_21-09-44.png
Figure 3: Screenshot of buttons
Text from the screenshot

SIGN UP NOW TO DELIVER JUN 23-29
You can also sign up early to deliver Jun 30-Jul 6

Here's the code. It calculates the dates, formats the HTML code. I use format-time-string to format just the month part of the dates and compare them to tell if I can skip the month part of the end date. After the HTML is formatted, the code uses xdotool (a Linux command-line tool) to switch to Google Chrome so that I can paste it in.

(defun my-brigade-copy-signup-block ()
  (interactive)
  (let* ((newsletter-date (org-read-date nil nil "+Sun"))
         (current-week (org-read-date nil t "+Mon"))
         (current-week-end (org-read-date nil t "+2Sun"))
         (next-week (org-read-date nil t "+2Mon"))
         (next-week-end (org-read-date nil t "+3Sun")))
    (kill-new
     (format
      "<div style=\"background-color: #223f4d; text-align: center; max-width: 384px; margin: auto; margin-bottom: 12px;\"><a href=\"https://dispatch.bikebrigade.ca/campaigns/signup?current_week=%s\" target=\"_blank\" class=\"mceButtonLink\" style=\"background-color:#223f4d;border-radius:0;border:2px solid #223f4d;color:#ffffff;display:block;font-family:'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif;font-size:16px;font-weight:normal;font-style:normal;padding:16px 28px;text-decoration:none;text-align:center;direction:ltr;letter-spacing:0px\" rel=\"noreferrer\">SIGN UP NOW TO DELIVER %s-%s</a>
</div>
<p style=\"text-align: center; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana\"><a href=\"https://dispatch.bikebrigade.ca/campaigns/signup?current_week=%s\" style=\"color: #476584; margin-top: 12px; margin-bottom: 12px;\" target=\"_blank\">You can also sign up early to deliver %s-%s</a></p>"
      (format-time-string "%Y-%m-%d" current-week)
      (upcase (format-time-string "%b %e" current-week))
      (format-time-string
       (if (string= (format-time-string "%m" current-week)
                    (format-time-string "%m" current-week-end))
           "%-e"
         "%b %-e")
       current-week-end)
      (format-time-string "%Y-%m-%d" next-week)
      (format-time-string "%b %e" next-week)
      (format-time-string
       (if (string= (format-time-string "%m" next-week)
                    (format-time-string "%m" next-week-end))
           "%-e"
         "%b %-e")
       next-week-end)))
    (shell-command "xdotool search  --onlyvisible --all Chrome windowactivate windowfocus")))

Now I can use an Org Mode link like elisp:my-brigade-copy-signup-block to generate the HTML code that I can paste into a Mailchimp code block. The button link is underlined even though the inline style says text-decoration:none, but it's easy enough to remove that with Ctrl+u.

Transforming HTML

The rest of the newsletter is less straightforward. I copy parts of the newsletter draft from the canvas in Slack to the block editor in Mailchimp. When I paste it in, I need to do a lot to format the results neatly.

I think I'll want to use this technique of transforming HTML data on the clipboard again in the future, so let's start with a general way to do it. This uses the xclip tool for command-line copying and pasting in X11 environments. It parses the HTML into a document object model (DOM), runs it through various functions sequentially, and copies the transformed results. Using DOMs instead of regular expressions means that it's easier to handle nested elements.

(defvar my-transform-html-clipboard-functions nil "List of functions to call with the clipboard contents.
Each function should take a DOM node and return the resulting DOM node.")
(defun my-transform-html-clipboard (&optional activate-app-afterwards functions text)
  "Parse clipboard contents and transform it.
This calls FUNCTIONS, defaulting to `my-transform-html-clipboard-functions'.
If ACTIVATE-APP-AFTERWARDS is non-nil, use xdotool to try to activate that app's window."
  (with-temp-buffer
    (let ((text (or text (shell-command-to-string "unbuffer -p xclip -o -selection clipboard -t text/html 2>& /dev/null"))))
      (if (string= text "")
          (error "Clipboard does not contain HTML.")
        (insert (concat "<div>"
                        text
                        "</div>"))))
    (let ((dom (libxml-parse-html-region (point-min) (point-max))))
      (erase-buffer)
      (dom-print (seq-reduce
                  (lambda (prev val)
                    (funcall val prev))
                  (or functions my-transform-html-clipboard-functions)
                  dom)))
    (shell-command-on-region
     (point-min) (point-max)
     "xclip -i -selection clipboard -t text/html -filter 2>& /dev/null"))
    (when activate-app-afterwards
      (call-process "xdotool" nil nil nil "search" "--onlyvisible" "--all" activate-app-afterwards "windowactivate" "windowfocus")))

Saving images

Images from Slack don't transfer cleanly to Mailchimp. I can download images from Slack one at a time, but Slack saves them with generic filenames like image (2).png. Each main newsletter item has one image, so I'd like to automatically save the image using the item title.

When I copy HTML from the Slack canvas, images are included as data URIs. The markup looks like this: <img src='data:image/png;base64,iVBORw0KGgo... With the way I do the draft in Slack, images are always followed by the item title as an h2 heading. If there isn't a heading, the image just doesn't get saved. If there's no image in a section, the code clears the variable, so that's fine too. I can parse and save the images like this:

(defun my-transform-html-save-images (dom dir &optional file-prefix transform-fn)
  (let (last-image)
    (dom-search dom
                (lambda (node)
                  (pcase (dom-tag node)
                    ('img
                     (let ((data (dom-attr node 'src)))
                       (with-temp-buffer
                         (insert data)
                         (goto-char (point-min))
                         (when (looking-at "data:image/\\([^;]+?\\);base64,")
                           (setq last-image (cons (match-string 1)
                                                  (buffer-substring (match-end 0) (point-max))))))))
                    ('h2
                     (when last-image
                       (with-temp-file
                           (expand-file-name
                            (format "%s%s.%s"
                                    (or file-prefix "")
                                    (if transform-fn
                                        (funcall transform-fn (dom-texts node))
                                      (dom-texts node))
                                    (car last-image))
                            dir)
                         (set-buffer-file-coding-system 'binary)
                         (insert (base64-decode-string (cdr last-image)))))
                     (setq last-image nil)))))
    dom))

I wrapped this in a small function for newsletter-specific processing:

(defvar my-brigade-newsletter-images-directory "~/proj/bike-brigade/newsletter/images")
(defun my-brigade-save-newsletter-images (dom)
  (my-transform-html-save-images
   dom
   my-brigade-newsletter-images-directory
   (concat (org-read-date nil nil "+Sun")
           "-news-")
   (lambda (heading)
     (replace-regexp-in-string
      "[^-a-z0-9]" ""
      (replace-regexp-in-string
       " +"
       "-"
       (string-trim (downcase heading)))))))

For easier testing, I used xclip -o -selection clipboard -t text/html > ~/Downloads/test.html to save the clipboard. To run the code with the saved clipboard, I can call it like this:

(my-transform-html-clipboard
 nil
 '(my-brigade-save-newsletter-images)
 (with-temp-buffer (insert-file-contents "~/Downloads/test.html") (buffer-string)))

Cleaning up

Now that I've saved the images, I can remove them:

(defun my-transform-html-remove-images (dom)
  (dolist (img (dom-by-tag dom 'img))
    (dom-remove-node dom img))
  dom)

I can also remove the italics that I use for comments.

(defun my-transform-html-remove-italics (dom)
  (dolist (node (dom-by-tag dom 'i))
    (dom-remove-node dom node))
  dom)

Here's how I can test it:

(my-transform-html-clipboard
 nil
 '(my-brigade-save-newsletter-images
   my-transform-html-remove-images
   my-transform-html-remove-italics)
 (with-temp-buffer (insert-file-contents "~/Downloads/test.html") (buffer-string)))

Removing sections

I put longer comments and instructions under "Meta" headings, which I can automatically remove.

(defvar my-brigade-section nil)
(defun my-brigade-remove-meta-recursively (node &optional recursing)
  "Remove <h1>Meta</h1> headings in NODE and the elements that follow them.
Resume at the next h1 heading."
  (unless recursing (setq my-brigade-section nil))
  (cond
   ((eq (dom-tag node) 'h1)
    (setq my-brigade-section (string-trim (dom-texts node)))
    (if (string= my-brigade-section "Meta")
        nil
      node))
   ((string= my-brigade-section "Meta")
    nil)
   (t
    (let ((processed
           (seq-keep
            (lambda (child)
              (if (stringp child)
                  (unless (string= my-brigade-section "Meta")
                    child)
                (my-brigade-remove-meta-recursively child t)))
            (dom-children node))))
      `(,(dom-tag node) ,(dom-attributes node) ,@processed)))))

Let's try it out:

(my-transform-html-clipboard
 nil
 '(my-brigade-save-newsletter-images
   my-transform-html-remove-images
   my-transform-html-remove-italics
   my-brigade-remove-meta-recursively)
 (with-temp-buffer (insert-file-contents "~/Downloads/test.html") (buffer-string)))

Formatting calls to action

Mailchimp recommends using buttons for calls to action so that they're larger and easier to click than links. In my Slack canvas draft, I use [ link text ] to indicate those calls to action. Wouldn't it be nice if my code automatically transformed those into centered buttons?

(defun my-brigade-format-buttons (dom)
  (dolist (node (dom-by-tag dom 'a))
    (let ((text (dom-texts node)))
      (if (string-match "\\[ *\\(.+?\\) *\\]" text)
          ;; button, wrap in a table
          (with-temp-buffer
            (insert
             (format "<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" data-block-id=\"627\" class=\"mceButtonContainer\" style=\"margin: auto; text-align: center\"><tbody><tr class=\"mceStandardButton\"><td style=\"background-color:#000000;border-radius:0;text-align:center\" valign=\"top\" class=\"mceButton\"><a href=\"%s\" target=\"_blank\" class=\"mceButtonLink\" style=\"background-color:#000000;border-radius:0;border:2px solid #000000;color:#ffffff;display:block;font-family:'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif;font-size:16px;font-weight:normal;font-style:normal;padding:16px 28px;text-decoration:none;text-align:center;direction:ltr;letter-spacing:0px\" rel=\"noreferrer\">%s</a></td></tr></tbody></table>"
                     (dom-attr node 'href)
                     (match-string 1 text)))
            (dom-add-child-before
             (dom-parent dom node)
             (car (dom-by-tag (libxml-parse-html-region (point-min) (point-max)) 'table)) node)
            (dom-remove-node dom node)))))
  dom)

Now I can test those functions in combination:

(my-transform-html-clipboard
 nil
 '(my-brigade-save-newsletter-images
   my-transform-html-remove-images
   my-transform-html-remove-italics
   my-brigade-remove-meta-recursively
   my-brigade-format-buttons)
 (with-temp-buffer (insert-file-contents "~/Downloads/test.html") (buffer-string)))
2025-06-20_21-10-38.png
Figure 4: Screenshot of button

Changing link colours

I also want to change the link colours to match the colour scheme. The newsletter has two parts distinguished by background colours. Bike Brigade updates use black text on a white background, and community updates use white text on a dark blue background so that they're visually distinct. For contrast, I like to use light blue links in the community section, which doesn't match the colour of the links when I paste them in from Slack. This meant manually recolouring the text and each of the links in Mailchimp, which was tedious.

2025-06-20_21-25-51.png
Figure 5: Screenshot of community update colours

This code changes the colours of the links. It also changes the colours of text by wrapping spans around them. It skips the links we turned into buttons.

(defvar my-brigade-community-text-style "color: #ffffff")
(defvar my-brigade-community-link-style "color: #aed9ef")
(defun my-brigade-recolor-recursively (node)
  "Change the colors of links and text in NODE.
Ignore links with the class mceButtonLink.
Uses `my-brigade-community-text-style' and `my-brigade-community-link-style'."
  (pcase (dom-tag node)
    ('table node) ; pass through, don't recurse further
    ('a           ; change the colour
     (unless (string= (or (dom-attr node 'class) "") "mceButtonLink")
       (dom-set-attribute node 'style my-brigade-community-link-style))
     node)
    (_
     (let ((processed
            (seq-map
             (lambda (child)
               (if (stringp child)
                   (dom-node 'span `((style . ,my-brigade-community-text-style)) child)
                 (my-brigade-recolor-recursively child)))
             (dom-children node))))
       `(,(dom-tag node) ,(dom-attributes node) ,@processed)))))

I can add that to the sequence:

(my-transform-html-clipboard
 nil
 '(my-brigade-save-newsletter-images
   my-transform-html-remove-images
   my-transform-html-remove-italics
   my-brigade-remove-meta-recursively
   my-brigade-format-buttons
   my-brigade-recolor-recursively)
 (with-temp-buffer (insert-file-contents "~/Downloads/test.html") (buffer-string)))

Wrapping it up

Now that I've made all those little pieces, I can put them together in two interactive functions. The first function will be for the regular colour scheme, and the second function will be for the light-on-dark colour scheme. For convenience, I'll have it activate Google Chrome afterwards so that I can paste the results into the right block.

(defun my-brigade-transform-html (&optional recolor file)
  (interactive (list (when current-prefix-arg (read-file-name "File: "))))
  (my-transform-html-clipboard
  "Chrome"
  (append
   '(my-brigade-save-newsletter-images
     my-transform-html-remove-images
     my-transform-html-remove-italics
     my-brigade-remove-meta-recursively
     my-brigade-format-buttons)
   (if recolor '(my-brigade-recolor-recursively)))
  (when file
    (with-temp-buffer (insert-file-contents file) (buffer-string)))))

(defun my-brigade-transform-community-html (&optional file)
  (interactive (list (when current-prefix-arg (read-file-name "File: "))))
  (my-brigade-transform-html t file))

And then I can use links like this for quick shortcuts:

  • [[elisp:(my-brigade-transform-html nil "~/Downloads/test.html")]]
  • [[elisp:(my-brigade-transform-community-html "~/Downloads/test.html")]]
  • [[elisp:(my-brigade-transform-html)]]

Since this pastes the results as formatted text, it's editable using the usual Mailchimp workflow. That way, other people can make last-minute updates.

With embedded images, the saved HTML is about 8 MB. The code makes quick work of it. This saves about 10-15 minutes per newsletter, so the time investment probably won't directly pay off. But it also reduces annoyance, which is even more important than raw time savings. I enjoyed figuring all this out. I think this technique of transforming HTML in the clipboard will come in handy. By writing the functions as small, composable parts, I can change how I want to transform the clipboard.

Next steps

It would be interesting to someday automate the campaign blocks while still making them mostly editable, as in the following examples:

Maybe someday!

(Also, hat tip to this Reddit post that helped me get xclip to work more reliably from within Emacs by adding -filter 2>& /dev/null to the end of my xclip call so it didn't hang.)

View org source for this post

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

-1:-- Transforming HTML clipboard contents with Emacs to smooth out Mailchimp annoyances: dates, images, comments, colours (Post Sacha Chua)--L0--C0--2025-06-20T23:26:47.000Z

Paul Jorgensen: Mousing around in orgmode

I do not care for the way #Emacs and #orgmode handle clicking on links by default. I am not alone. What I want is for the left mouse button to behave as when I click anywhere else in the buffer. Middle click or double click should follow the link. Sadly, there is a reported bug about the mismatch between the documentation, the docstring, and the actual behavior of messing with the mouse and links in org.

First, before making any other changes, note that clicking and holding the mouse on a link for at east 450 milliseconds will not open the link. If that’s enough for you, then go with joy!

Thanks again to Charles Choi, there is a solution.

First, set two variables before orgmode loads, probably at the very top of your init:

(setq
 org-mouse-1-follows-link nil
 mouse-1-click-in-non-selected-windows nil
 )

org-mouse-1-follows-link nil means that org should do nothing when the mouse clicks. We’ll address that below. mouse-1-click-in-non-selected-windows nil makes it so accidentally clicking a link in an inactive window or frame doesn’t follow the link—if I don’t want it to happen in the focused window, why would I like it in others?

Stopping here is valid as middle click will follow the link. Wipe your brow and be done with it! Or, one could proceed into a world of menu manipulation for context…

M. Choi wrote an article about manipulating the mouse context menu I will not duplicate here. I followed his recipes and added the whole thing to my config. Between “Open file (buffer) in Finder” and “Look up in dictionary” definitions I inserted this:

(mouse-set-point click)
(define-key-after menu [org-open-at-point]
  '(menu-item "Open the thing" org-open-at-point
              :help "Open thing at point"))

It’s a pretty great function:

Handling Links (The Org Manual)

Open thing at point.

The thing can be a link, citation, timestamp, footnote, src-block or tags.

C-c C-o does the same thing.

There might be some refinements one can suggest.

-1:-- Mousing around in orgmode (Post Paul Jorgensen)--L0--C0--2025-06-20T19:58:00.000Z

Irreal: Running All Code Blocks In An Org Subtree

If you’ve been around the Emacs technical community for a while, you know that Sacha Chua—despite being the mother of a young daughter—is an ever present and incredibly productive member of that community. One of the ways she manages that is by automating as much of her work as she can.

In a recent post she shows one such bit of automation. As almost every Org mode user knows, you can add executable code blocks to an Org file that can be executed to perform various actions, usually—but not always—altering the text of the Org file in some way. In the example that Chua gives, calendar entries are parsed and used to build crontab entries to perform certain actions in a timely manner. Chua gives the details here.

You can check out her code at the post but the aspect I want to focus on is her use of Property ID for an Org subtree. For her purposes, the ID can be used to execute every code block In the subtree. That means that she can mix various languages to build her crontab entry but execute them all with a single call to build the entry. That’s pretty neat.

The other, perhaps more useful, thing you can do with a Property ID is set an anchor point for the subtree if you export it to HTML. For example if you have a document with several subtrees—a blog post, say—that you want to publish to the Web you can have any or all of those subtrees directly addressable through a URL because the Property ID sets an anchor point for the subtree. That’s a really handy thing to know.

-1:-- Running All Code Blocks In An Org Subtree (Post Irreal)--L0--C0--2025-06-20T15:30:03.000Z

The Emacs Cat: Making a Multiplatform Emacs Configuration

These days, I use three different operating systems at work and home: Ubuntu Linux (Dell Latitude 7490 13" laptop), Microsoft Windows (Dell Latitude 7400 13" laptop), and macOS (27" iMac). My primary tool across these platforms is Emacs, which I use for nearly everything—coding (C++, Python), blogging (Hugo), reading (Elfeed), note-taking (Org Mode, org-roam), diary, and agenda management (beorg on iPhone, synchronized via Dropbox across all computers), among other tasks.

Naturally, I want a single Emacs configuration file—.emacs or, if you prefer, init.el—to work seamlessly across all these platforms.

Emacs is arguably the most platform-agnostic software available. However, some OS-specific differences must be addressed. These can be grouped into several categories:

  1. Paths for external executables, particularly on Windows.
  2. OS-specific key bindings, especially on macOS.
  3. Various OS-specific tweaks, particularly for Windows.
  4. Font sizes, which depend on screen size and resolution.

Below, I explore these issues in greater detail.

Introduction

First, we need to determine the operating system type.

;;;; OS Detection
(defvar os-windows (string-equal system-type "windows-nt") "Running on Windows.")
(defvar os-linux (string-equal system-type "gnu/linux") "Running on Linux.")
(defvar os-macos (string-equal system-type "darwin") "Running on macOS.")

Paths

;;; Environment Variables.
(defvar my/home (getenv "HOME"))

(when os-linux
  (setenv "PATH"
          (concat
           my/home "/lib/grpc/bin" path-separator ; gRPC binaries
           (getenv "PATH")))
  (setenv "PKG_CONFIG_PATH"
          (concat
           my/home "/lib/grpc/lib/pkgconfig")))

;; Add Cygwin path on Windows.
(when os-windows
  (setq exec-path (cons "C:/cygwin64/bin" exec-path)))

;; Clang/clangd compiler on Windows.
(when os-windows
  (setq exec-path (append exec-path '("C:/bin/LLVM/9.0.0/bin"))))

;; Clang/clangd compiler on macOS.
(when os-macos
  (setq exec-path (append exec-path '("/usr/local/Cellar/llvm/11.0.0_1/bin"))))

The exec-path built-in variable contains a list of directories Emacs searches to run programs in subprocesses. Each element is either a string (directory name) or nil (indicating the default directory).

OS-Specific Key Bindings

macOS

Disable the Command key as Meta and enable Option as Meta (Alt).

;;;; `macOS' keyboard
(when os-macos
    (setq mac-command-key-is-meta nil
          mac-command-modifier 'super
          mac-option-key-is-meta t
          mac-option-modifier 'meta))

Prevent accidentally closing the frame or Emacs app by disabling ⌘-w and ⌘-q.

(when os-macos
  (global-unset-key (kbd "s-w"))
  (global-unset-key (kbd "s-q")))

OS-Specific Tweaks

Microsoft Windows

Increase the default Windows pipe buffer size. (Note: This may no longer be necessary, but I include it for compatibility.)

(when os-windows
  (setq w32-pipe-read-delay 0)
  (setq irony-server-w32-pipe-buffer-size (* 64 1024)))

Configure Dired to resemble its appearance on Linux/macOS.

(if os-windows
    (setq dired-listing-switches "/A:H /O:NG")
  ;; Linux/macOS
  (setq dired-listing-switches "-laGgh1v --group-directories-first --time-style=long-iso"))

Linux

Set the default browser on Linux.

(when os-linux
  (setq browse-url-browser-function 'browse-url-firefox))

Font Size

I use the same .emacs configuration on both a 13" laptop and a 27" desktop, which have significantly different screen sizes and resolutions. This requires font size adjustments.

(cond
 (os-linux (defvar my/font-height 140 "The default font height for Emacs on Linux (in 1/10th points)."))
 (os-macos (defvar my/font-height 180 "The default font height for Emacs on macOS (in 1/10th points)."))
 (t (defvar my/font-height 140 "The default font height for Emacs on Windows and other systems (in 1/10th points).")))

Set the default font size based on the OS type.

(custom-set-faces
 `(default ((t (:height ,my/font-height :width normal :family "Aporetic Sans Mono"))))
)

Note the use of the backquote macro for dynamic font height.

Summary

With these configurations, I can use a single .emacs file across all my machines, ensuring a consistent Emacs experience regardless of the operating system.

Happy emacsing!

— The Emacs Cat.

-1:-- Making a Multiplatform Emacs Configuration (Post The Emacs Cat)--L0--C0--2025-06-20T10:15:16.000Z

Jakub Nowak: Making Emacs Dashboard Show Treemacs Workspaces

I don't use projectile, or project.el, or any other project management system in Emacs. Maybe I should, but I "grew up" without having these systems and in my opinion they add complexity onto something that your filesystem (and git) already does fine on its own. So, to that end, I use Treemacs extensively for project management.

I generally follow a Workspace -> Project hierarchy. For example, here's a small snippet of my Treemacs persist file, with two workspaces and some varying projects underneath them.

* Website
** cyan.sh
 - path :: ~/.../cyan.sh
* Extending Emacs
** dotfiles
- path :: ~/.emacs.d
** ouroboros themes
- path :: ~/..../ouroboros-emacs-themes

One thing that I wanted for a while was the ability to show workspaces in Dashboard, because I usually want to go back to my most recent workspace when launching a new client instance. Same reason that people want thier projectile projects to show up. Unfortunately, Dashboard doesn't have any such integration already, so I had to hack some together. If I was smarter, I would do this in a way that's more portable. Currently it requires both Dashboard and Treemacs to be installed via Straight, as per the first two lines.

(load-file (concat user-emacs-directory "straight/build/dashboard/dashboard-widgets.el"))
(load-file (concat user-emacs-directory "straight/build/treemacs/treemacs-workspaces.el"))


(add-to-list 'dashboard-item-generators
           '(workspaces . dashboard-insert-workspaces)
           t)

(add-to-list 'dashboard-item-shortcuts
           '(workspaces . "w")
           t)

(defun dashboard-insert-workspaces (list-size)
  "Add the list of LIST-SIZE items of treemacs workspaces."
  (dashboard-insert-section
   "Workspaces:"
   (dashboard-subseq (mapcar (lambda (x) (cl-struct-slot-value 'treemacs-workspace 'name x)) treemacs--workspaces) list-size)
   list-size
   'workspaces
   (dashboard-get-shortcut 'workspaces)
   `(lambda (&rest _) (treemacs-do-switch-workspace ,el))
   (let* ((workspace-name el))
     workspace-name)))
-1:-- Making Emacs Dashboard Show Treemacs Workspaces (Post Jakub Nowak)--L0--C0--2025-06-20T00:00:00.000Z

Irreal: Markdown Comes To Journelly

I’m sure you’ve all missed my Journelly posts. The thing is, it’s working so well for me that I don’t feel the need to comment about every little change but Álvaro Ramírez has just announced Markdown support. That’s nothing that I care about, of course, being a dedicated Org mode user but it’s still important.

Even if you believe, as I do, that there’s no reason for Emacs users to be slumming in the Markdown camp, there’s no reason you have to be an Emacs user to take advantage of Journelly. Indeed, other than storing it’s data in Org format there’s nothing Emacs specific about Journelly and everyone—no matter their editor choice—can take advantage of it.

And, snark aside, there are plenty of Emacs users who, for one reason or another, need or prefer to work in Markdown so this latest change will be welcome to them. Even more to the point, it shows that Ramírez is committed to Journelly and making it valuable to as many people as possible. He’s already shown this by rolling out new releases almost as fast as we can keep up with them

That brings me to another point that I haven’t mentioned before. Ramírez—who is a full time, independent, app developer—makes Journelly available for a one-time fee instead of insisting on a subscription like many developers have started doing. I really hate the subscription model and am extraordinarily grateful to Ramírez for not using it.

But for that to work, Ramírez needs to get a critical mass of users to support his continued development of Journelly. His app is great and he deserves that support so that’s why I may seem like a cheerleader for it. I use it and love and I, selfishly, want him to succeed so that Journelly will continue to evolve and get better. But even if it doesn’t, it’s still a win for me.

-1:-- Markdown Comes To Journelly (Post Irreal)--L0--C0--2025-06-19T15:22:13.000Z

Chris Maiorana: The “undergrad” note style

As I’ve mentioned in previous blog posts, keeping notes in Emacs can be overwhelming, even though Org Mode gives you a rich set of functions for managing notes. It should be easy! But the process can stall before it starts for artificial reasons:

  • Deciding on a package to use
  • Wondering what to write about
  • Deciding how to organize notes

None of these reasons really matter when it comes to the simple, physical action of taking notes.

Keep it simple.

To get over this initial hump of overthinking, I suggest quantity over quality. Just start taking notes and see if you can make a mad dash to the first hundred. By then you’ll have something you can look at and see what’s working.

Try this: different note styles. One easy note style you can try, particularly if you take notes on literature, is what I call the “undergrad” note style.

This type of note follows a simple, repeatable pattern:

  • Make a claim (your own idea)
  • Provide a quote or example
  • Explain or justify your claim

Here’s an example of a undergrad note on “Notes From Underground” by Fyodor Dostoyevsky:

[claim] The underground man uses intellect as an excuse for inaction.

[quote or example] "Now, I am living out my life in my corner,
taunting myself with the spiteful and useless consolation that an
intelligent man cannot become anything seriously, and it is only the
fool who becomes anything. Yes, a man in the nineteenth century must
and morally ought to be pre-eminently a characterless creature; a man
of character, an active man is pre-eminently a limited creature."

[explanation] The "spiteful" belief that only fools succeed in the
world reveals the narrator's frustration with attempting to connect
with others, instead wallowing in his private world---in which he can
safely justify his inaction.

Is that right? Did I interpret the text properly? It doesn’t matter. It’s just a note. I can save it, come back to it, develop it over time, or just let it linger. It only took a few minutes to create it, but it could be the seed of bigger ideas, or just a stray thought.

There is a notion that once you start creating notes you’ll get a magical flash of insight from whatever course of study you’re engaged in. That can happen. But you will have to create a lot of notes to get there. Just like lifting weights or training for a marathon, there’s no instant gratification, but there’s magic and transformation on the other side of that effort.


Thanks for reading, be sure to check out my Emacs For Writers handbook. Or, subscribe to my newsletter.

The post The “undergrad” note style appeared first on The Daily Macro.

-1:-- The “undergrad” note style (Post Chris Maiorana)--L0--C0--2025-06-19T13:29:23.000Z

TAONAW - Emacs and Org Mode: Adjusting my org-mode workflow

One of the things that affects my workflow at work and Emacs recently is the source of my tasks and projects.

In the past, I worked almost exclusively with our ticketing system. My capture templates in org-mode contained properties for ticket numbers and user IDs to identify the users. Each task in org-mode I created this way included a scheduled date for that day, which I would move around later, depending on urgency.

This all went away when I moved to a more managerial position about a year ago, but it took me some time to realize that. My new position means that roughly 80% of my projects come from Emails while the rest come from meetings. I almost never have a ticket assigned to me directly, unless it has to do with my old position as a tech writer, which I still do now and then1.

Habits die hard, and I was trying to fit email chains and meeting notes into my task templates in org-mode with limited success. For example, I was struggling to figure out what properties I need, since emails don’t really point a user with a problem, but often describe a situation that requires a solution.

Meanwhile, I was swapped away by Journelly (and for a good reason) and started to use it in meeting notes, but realized that I tend to keep these notes in a certain manner that would fit nicely into a capture template. These notes, which consisted mostly of bullet points and questions, did not fit well visually with the rest of the journal. More so, since I often write down things to do in meetings, I needed an indicator to flag those meetings later, when I need to go through the notes and re-write those as tasks2. This quickly became cumbersome.

For my meetings then, I created this simple template (for more structured templates, I use org files rather than writing them directly into my settings):

    * MEETING %?
    %^T
    
    *Discussion:*
    -
    
    
    *To Ask:*
    -  
    
    
    *To Do:*
    - [ ] 

I write the points in the meetings as people discuss them. As I listen, I come up with questions/comments which go below (I tend to write the answers as nested items below the questions with a “+” rather than a “-” to indicate an answer). The To Do part is newer, usually containing large items (projects) that need to be broken down further into dedicated tasks. This is still a work in progress.

As for emails, I still don’t have something solid. I mostly rely on my existing project template, but it doesn’t fit as nicely as the meeting template above.

Emails in Outlook are usually a mess of “conversations” by the time I get to read them. I then need to spend time on reading and understanding what’s going on, which is often an issue in itself, as people require urgent answers for complicated matters (Emacs is amazing, but I don’t think it can help me with that). I was looking into a way to identify emails by their email ID and use these as properties, but this is something that can only be done on the back end, which I don’t have access to. I probably need to spend more time with Outlook and its filters, and that’s a mess in itself, as Microsoft tends to change options and controls across its different versions. If anyone reading this has any advice, I’m all ears.

Ideally, Emacs projects stemming from emails should contain the email subject or another identifier (ideally in the header itself), then the people involved (the askers and the doers), location, and then resulting ticket numbers for the tasks created - but this is all flexible.

Footnotes

1 : interestingly, my experience writing technical documents is helpful when I need to delegate work or explain a workflows in meetings - and org-mode is one of the best place I ever had to write such documents because it is an outline tool that breaks processes to steps by default.

2 : For about a week, I used Journelly’s tagging system to tag certain notes as meetings, which helped me realize how ridiculous this was. Journelly is a place for me to see pictures of people I spent time with or capture ideas when I get a chance to stop and think a little, often by dictation. Meetings are nothing like these things, and they shouldn’t. It’s easy for me to write now, clear as day, but a few weeks ago I didn’t know.

-1:-- Adjusting my org-mode workflow (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-06-19T13:22:13.000Z

Rahul Juliato: Minimal Clipboard Support in Terminal Emacs

Terminal Emacs users often face an annoying limitation: copying and pasting between Emacs and the system clipboard doesn’t "just work." While graphical Emacs integrates with the OS clipboard by default, the terminal version leaves that responsibility to you, especially if you're inside tmux, ssh, or simply prefer living in the terminal.

Thankfully, there are excellent packages like: xclip and clipetty, as well as guides like Emacswiki: CopyAndPaste that tackle this. They offer elegant solutions, often with additional features and broader compatibility.

But sometimes, you just want something small and direct, something you can copy-paste into your config and tweak per OS, with no external Emacs dependencies.

That’s what I present here.


✂️ emacs-solo-clipboard: A Minimal Clipboard Bridge

This snippet is part of my emacs-solo configuration. It gives terminal Emacs clipboard support using native system tools (which, of course, you need installed in your system):

pbcopy / pbpaste for macOS

clip.exe / powershell.exe for WSL

wl-copy / wl-paste for Wayland

xclip for X11

It hooks into Emacs' standard interprogram-cut-function and interprogram-paste-function APIs.

;;; EMACS-SOLO-CLIPBOARD
;;
;;  Allows proper copy/pasting on terminals
;;
(use-package emacs-solo-clipboard
  :ensure nil
  :no-require t
  :defer t
  :init
  (cond
   ;; macOS: use pbcopy/pbpaste
   ((eq system-type 'darwin)
	(setq interprogram-cut-function
		  (lambda (text &optional _)
			(let ((process-connection-type nil))
			  (let ((proc (start-process "pbcopy" "*Messages*" "pbcopy")))
				(process-send-string proc text)
				(process-send-eof proc)))))
	(setq interprogram-paste-function
		  (lambda ()
			(shell-command-to-string "pbpaste"))))

   ;; WSL (Windows Subsystem for Linux): Use clip.exe for copy and powershell.exe for paste
   ((and (eq system-type 'gnu/linux)
		 (getenv "WSLENV"))
	(setq interprogram-cut-function
		  (lambda (text &optional _)
			(let ((process-connection-type nil))
			  (let ((proc (start-process "clip.exe" "*Messages*" "clip.exe")))
				(process-send-string proc text)
				(process-send-eof proc)))))
	(setq interprogram-paste-function
		  (lambda ()
			(string-trim (shell-command-to-string "powershell.exe -command Get-Clipboard")))))

   ;; Linux with wl-copy/wl-paste (Wayland)
   ((and (eq system-type 'gnu/linux) (executable-find "wl-copy"))
	(setq interprogram-cut-function
		  (lambda (text &optional _)
			(let ((process-connection-type nil))
			  (let ((proc (start-process "wl-copy" "*Messages*" "wl-copy")))
				(process-send-string proc text)
				(process-send-eof proc)))))
	(setq interprogram-paste-function
		  (lambda ()
			(shell-command-to-string "wl-paste -n"))))

   ;; Linux with xclip (X11)
   ((and (eq system-type 'gnu/linux) (executable-find "xclip"))
	(setq interprogram-cut-function
		  (lambda (text &optional _)
			(let ((process-connection-type nil))
			  (let ((proc (start-process "xclip" "*Messages*" "xclip" "-selection" "clipboard")))
				(process-send-string proc text)
				(process-send-eof proc)))))
	(setq interprogram-paste-function
		  (lambda ()
			(shell-command-to-string "xclip -selection clipboard -o"))))))

⚠️ This Is Hacky, and That's the Point

This is not a library or polished package. It's a minimal Emacs Lisp snippet that works well if your system already provides the right clipboard utilities. It doesn't abstract anything and won't handle edge cases. But that also means:

✅ Easy to audit

✅ Easy to tweak

✅ Easy to share small pieces

You can literally just copy the macOS or Wayland part and ignore the rest.


🖥️ Bonus: OSC 52 Might Be All You Need

If you’re using a modern terminal emulator that supports OSC 52 (like Kitty, Windows Terminal, or Xterm with proper settings), you might not need any of the above clipboard setup.

Emacs has built-in support for OSC 52 clipboard integration via this simple line:

(setq xterm-extra-capabilities '(getSelection setSelection modifyOtherKeys))

This tells Emacs to assume your terminal supports some extra features, including:

  • getSelection: paste from the system clipboard
  • setSelection: copy to the system clipboard
  • modifyOtherKeys: unlock more reliable keybindings (like C-,)

This works without any external tools as long as your terminal supports OSC 52 and your Emacs was built with Xterm support (most builds are).

💡 Tip: Try it! Copy something in Emacs with M-w and paste outside. If it works, you’re set. Otherwise, fallback to one of the system-specific setups above.

🔒 tmux users: OSC 52 support may require this to your ~/.tmux.conf:

set-option -s set-clipboard on
set -g allow-passthrough on

✅ Wrap-Up

If you’re running Emacs in the terminal and want clipboard support without extra layers or packages, this might be just what you need.

It’s not pretty. It might be not that smart. But it works.

And sometimes, that’s exactly the kind of config you want.


Let me know if you run into clipboard quirks I didn’t cover here, or if you’ve built your own minimalist setup, I’m always curious about tiny hacks that punch above their weight. 🧠🛠️🔥


Edit:

2025-06-20: Added the Bonus section reffering to xterm-extra-capabilities as suggested by user u/passkyw on r/emacs.

-1:-- Minimal Clipboard Support in Terminal Emacs (Post Rahul Juliato)--L0--C0--2025-06-18T20:00:00.000Z

Sacha Chua: Run source blocks in an Org Mode subtree by custom ID

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

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

-1:-- Run source blocks in an Org Mode subtree by custom ID (Post Sacha Chua)--L0--C0--2025-06-18T19:54:18.000Z

Alvaro Ramirez: Markdown is coming to Journelly

18 June 2025 Markdown is coming to Journelly

When Journelly launched, I asked users to get in touch if they were interested in Markdown support.

Since then, Markdown has by far been the most requested feature.

Today, I’m excited to share that Journelly beta builds now include initial Markdown support! If you’ve been in touch, you likely already have access. If not, let me know you’re interested.

Journelly still defaults to Org as its preferred markup, but you can now switch to Markdown from the welcome screen or the menu.

md.png

While Org is my own markup of choice, it remains fairly niche. As I work to build a sustainable iOS app as a full-time indie developer, I need to reach a wider audience, without resorting to subscriptions. Luckily, I think we can have our cake and eat it too.

Here's how I see Journelly's audience breaking down:

For anyone who just wants to write (regardless of markup)

This has always been Journelly’s main goal. I've worked hard to keep the serialization format in the background, focusing instead on delivering a smooth, friction-free experience. The primary goal: just write.

I think this is working well. Ellane's post sums it up: Journelly is the iOS Org App You’ll Love (Even if You Don’t Do Org).

If you just want a quick way to take notes or journal privately, Journelly already offers that. Adding quick notes, ideas, recipes, checklists, music, links, etc. is really easy and fast even if you don't do org ( Brandon says so too).

Org mode enthusiasts

I got this one pretty well-covered also. I'm an Emacs org mode enthusiast myself and regularly share my Journelly entries between my iPhone and Mac. You don't need to take my word for it though. jcs is seasoned Emacs enthusiast. From Irreal, he's covered Journelly pretty well. While journelly.com quotes and links to posts from happy users, I've been collecting posts from different users. I should share a post with all of them too!

Markdown enthusiasts

Which brings me back to this post: there are a lot of Markdown users out there. While Journelly’s UX has caught the interest of some Markdown fans, many prefer to stick with their favorite format. Your interest was heard! I did say, the more requests I get, the sooner I'll get Markdown support out the door, and so here we are.

You can now try Markdown support via TestFlight. I look forward to your feedback.

New to Journelly and want to join the Markdown beta? Get in touch.

-1:-- Markdown is coming to Journelly (Post Alvaro Ramirez)--L0--C0--2025-06-18T16:23:01.000Z

Irreal: Emacs For Writers Handbook

Chris Maiorana has just announced the release of his Emacs for Writers handbook. It’s a 22 page handbook for the writer who would like to escape the confines of “word processors” like Word and its ilk. The handbook doesn’t assume any knowledge of Emacs on the part of the reader but explains how to start from scratch.

After a quick summary of basic navigation commands, Maiorana covers the fundamentals of writing with Org mode. That’s important because by using Org it’s easy to export your writing to whatever format you need. Maiorana concentrates on the ODT export because most publishers these days want to receive Word documents.

Maiorana doesn’t explain how he uses Emacs for his writing; he just explains how to use Emacs so that readers can develop their own style of working. Along the way he covers using abbreviations, word counting, and the installation of packages. He doesn’t say much about packages and the types of things that are available for writers but there are a lot of resources that cover that.

The handbook is offered on a “pay what you like” basis but no matter what you want to pay—even nothing—you’re going to have to provide a credit card number. Still, it’s nice to support efforts like this so even small contributions can help.

If you have any Emacs experience at all this handbook probably won’t tell you anything you don’t already know. If you’re a beginner and want to experience the joys of unfettered writing and editing without an opinionated work processor insisting on your doing things its way, take a look at Maiorana’s handbook and fire up Emacs. It’s not nearly as difficult as some would have you believe to get started, and as you learn more your writing efficiency will improve far beyond what’s possible with a word processor.

-1:-- Emacs For Writers Handbook (Post Irreal)--L0--C0--2025-06-18T15:41:01.000Z

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

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

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

-1:-- Using Org Mode, Emacs Lisp, and TRAMP to parse meetup calendar entries and generate a crontab (Post Sacha Chua)--L0--C0--2025-06-18T14:23:21.000Z

Paul Jorgensen: Org Babel and AppleScript with a little vc-use-package

I don’t often write in AppleScript. I used to when I first moved into the OS X lifestyle, but for whatever reasons I never connected with it the way I did shell scripting or Perl or Python or Lua.

Recently some of my ancient scripts failed. The scripts were fine, just the world changed around them. I put off doing the update work. Today, however, I reached my prime annoyance level with the breakages.

I fired up a Denote buffer and added a source block for AppleScript. Sad trombone. Of course Org Babel doesn’t have AppleScript by default. On to the DDG to see what the prevailing take on this is!

More sad trombones—there wasn’t much.

Here is what I sorted out that seems to work, or at least does for my use case for now. As always, YMMV.

It seems to require two pieces:

  1. GitHub – stig/ob-applescript.el: AppleScript integration for Org Babel
  2. GitHub – emacsorphanage/applescript-mode: Emacs mode for editing AppleScript

The first makes calling one’s AppleScript block executable. The second makes editing the script nicer (and possible). Here’s how I load them in my config:

;; AppleScript

(use-package ob-applescript
  :vc (:fetcher github :repo stig/ob-applescript.el)
  )

(use-package applescript-mode)

That’s it. Not a lot of configuration magic. applescript-mode appears semi-actively maintained, ob-applescript not so much.

Setting up the Org Babel block is similarly easy once one installs those packages:

#+begin_src applescript
  -- AppleScript goes here; this is a comment
  display dialog “Hello World”
#+end_src

Yay!

※ I run Emacs 29.4, so do not have vc built in to use-package. That’s a 30.1+ addition. I use vc-use-package:

;; When emacs-mac updates to 30+ I can remove this

(unless (package-installed-p 'vc-use-package)
  (package-vc-install "https://github.com/slotThe/vc-use-package"))
(require 'vc-use-package)

GitHub – slotThe/vc-use-package: Primitive package-vc integration for use-pac…

Also, the syntax is slightly different in the use-package definitions:

Instead of a MELPA-style (=<29.4) :vc (:fetcher github :repo oantolin/math-delimiters), the built-in keyword uses ELPA-style (=>30.1) :vc (:url "https://github.com/oantolin/math-delimiters") package specifications. Please refer to the use-package and the Emacs manual for more details. If you want to use the ELPA-style syntax with vc-use-package, see here.

Maybe you have a better approach to writing and running AppleScript in Emacs. If so, please consider sharing.

-1:-- Org Babel and AppleScript with a little vc-use-package (Post Paul Jorgensen)--L0--C0--2025-06-18T12:25:00.000Z

Troy Hinckley: Making TRAMP go Brrrr….

I recently changed jobs and found myself in a position where I would need to do a lot of work on remote machines. Since I am Emacs user, the most common way to do this is using TRAMP (Transparent Remote access, Multiple Protcol). TRAMP is an Emacs package that let’s you treat a remote host like a local system, similar to VSCode Remote Development Extension. I had used TRAMP before and it tended to be slow.
-1:-- Making TRAMP go Brrrr…. (Post Troy Hinckley)--L0--C0--2025-06-18T00:00:00.000Z

Irreal: Advice To A New Emacs User

Over at the Emacs subreddit, Informal-Silver-2810 says he’s been a Vim/NeoVim user for the last 20 years but decided to try Emacs so he committed himself to a month of using only Emacs. As part of that experiment, he asked the Emacs subreddit what advice they have for a new Emacs user.

The thing that stood out to me was how varied the advice was. Some folks suggested starting with vanilla Emacs, others said start with Evil mode but not packages like Spacemacs or Doom. Still others suggested starting with Spacemacs or Doom or perhaps one of other starter packages.

You have to feel sorry for Informal-Silver-2810: the advice he got included just about every possibility. On the other hand, that probably means there’s no royal road to learning Emacs and that everyone has to discover their own best path.

My own path worked well for me and looking back, I can’t see that any other path would have been better or even as good. As a Vim user I was all in on modal editing and—after more than two decades—Vim’s keybindings were burned deeply into my muscle memory. Nevertheless, when I finally took the plunge, I just jumped into vanilla Emacs and didn’t bring any Vim habits with me. Except, that is, for that muscle memory. For a long time I would do things like use Ctrl+k to move up a line. It almost looks like it’s doing the right thing so by the time I realized what was happening, I had deleted several lines. Thank goodness for undo.

A couple of the commenters had stories like mine but most other strategies were represented too. It seems to me that the best answer to Informal-Silver-2810 is to just feel your way and do what seems easiest for you.

-1:-- Advice To A New Emacs User (Post Irreal)--L0--C0--2025-06-17T15:53:33.000Z

Chris Maiorana: The Emacs For Writers handbook is available now (and ever)

Finally, after many years of using Emacs for writing, I have assembled what I believe are the key points necessary to take any non-programmer writer gently by the hand into Emacs. This handbook goes ALL THE WAY from “Why plain text” to exporting your Org Mode files to crisp, standard manuscript Microsoft Word formatted documents for the writer’s market.

If you are a technical writer, blogger, copywriter, novelist, or just a weekend short story writer like yours truly, Emacs is the superior composition and editing tool you need to more easily get your ideas out of your head and into the world.

You can get the PDF handbook right here.  It’s a “pay what you want”, including FREE, download.

If you’re already an Emacs user, and you have a friend who might benefit from this, be sure to send them the link.  Thanks, everybody.

The post The Emacs For Writers handbook is available now (and ever) appeared first on The Daily Macro.

-1:-- The Emacs For Writers handbook is available now (and ever) (Post Chris Maiorana)--L0--C0--2025-06-17T13:53:47.000Z

Thanos Apollo: Major update for Gnosis: 0.5.0 release

Gnosis just got a major update with this new release. With the long promised support of org-mode being added along with support for linking notes to org-gnosis nodes.

Org Mode Support

After hacking on org-gnosis and gaining a better understanding of how org-mode works, I decided to use org-mode for creating and exporting gnosis notes.

Notes, no matter their note type, will now be as follows:

Cloze type example.

* Thema                      :pharmacology:antimicrobials:penicillin:
:PROPERTIES:
:GNOSIS_ID: NEW
:GNOSIS_TYPE: cloze
:END:
** Keimenon 
What is the treatment for Actinomyces infection?

Penicillin

** Hypothesis 
- drug type

** Answer 
- Penicillin

** Parathema 
Sulfonamides → Nocardia
Actinomyces → [[id:680ca944-8ee9-4513-92d6-25696ee01f48][Penicillin]] 
(treatment is a SNAP)

Note that anki-like syntax is still supported for clozes.

MCQ example


* Thema                                                            :clinical:
:PROPERTIES:
:GNOSIS_ID: NEW
:GNOSIS_TYPE: mcq
:END:
** Keimenon 
A 60-year-old man comes to the physician with chills, nausea, and
diffuse muscle aches for 3 days. His niece had similar symptoms 2
weeks ago and H3N2 influenza strain was isolated from her respiratory
secretions. He received his influenza vaccination 2 months ago. His
temperature is 38.5°C. A rapid influenza test is
positive. Which of the following mechanisms best explains this
patient's infection despite vaccination?

** Hypothesis 
- Random point mutations within viral genome
- Complementing with functional viral proteins
- Exchange of viral genes between chromosomes
- Reassortment of viral RNA segments
- Acquisition of viral surface proteins

** Answer 
- Random point mutations within viral genome

** Parathema 
Random point mutations within the viral genome are responsible for
antigenic drift, which creates a new virus strain.

Each note is a thema consisting of the following components:

  • Keimenon; The main text or question.
  • Hypothesis; Assumptions/hints for the text to guide to the right answer
    • For example this is used as hints for cloze type or as choices in MCQs
  • Answer; The correct response or solution to the keimenon.
  • Parathema; expansion of keimenon, that can include links to e.g org-gnosis topics or even files, serving as a way to link org-gnosis topics to current thema.

Optionally, you can use also add tags for each note.

Notes/themas can have multiple hypotheses and answers (depending on their type), separated by gnosis-export-separator, which defaults to "\n-" (a new line followed by a dash).

Support for exporting decks

Support for exporting decks as org files via gnosis-export-deck has been added.

Support for org-gnosis notes

With this update org-gnosis is finally part of gnosis. The goal is not to just have a spaced repetition tool, but an all-in-one learning tool.

With this new update you can do reviews for specific org-gnosis topics.

  • Use M-x gnosis-review-topic to select a topic from your org-gnosis notes to review all linked themas.
  • You can link a thema to an org-gnosis topic by adding an org id link to either the parathema (recommended) or keimenon, using M-x org-gnosis-insert.

Future versions will further expand on this functionality.

Deprecated support for images

  • Image support will be rewritten in the next minor version
    • Support to display file links of images will be added.

Deprecated support for y-or-n notes

With this update your current y-or-n notes will be converted into MCQ type, with “Yes” & “No” choices.

-1:-- Major update for Gnosis: 0.5.0 release (Post Thanos Apollo)--L0--C0--2025-06-17T00:00:00.000Z

Sacha Chua: 2025-06-16 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-06-16 Emacs news (Post Sacha Chua)--L0--C0--2025-06-16T16:30:56.000Z

Irreal: Becoming An Emacs Power User

Over at the Emacs subreddit, Cultural_Mechanic_92 asks How did you become an Emacs power user? It is, I suppose, a reasonable question even if the answer is pretty obvious. The comments are interesting but basically boil down to “you’ll learn by doing”. Of course, the advice is a bit more nuanced: you increase your skills little by little by practicing with Emacs, learning to configure it, and making the tool your own.

It was sort of interesting to read the answers but what struck me was the meta-question of what does it mean to be an Emacs power user and how would you know if you are one. In terms of longevity, I would rate myself as intermediate. I’ve been using Emacs for about 17 years and while that may seem like a long time, plenty of others have 30 or more years in.

In terms of skill, it’s harder to assess. Certainly I can be said to have at least journeyman status and perhaps more but as any experienced Emacs user will tell you, there’s always something else to learn—indeed, hardly a day goes by that I don’t learn something new— so how do you know where you are on the road to power user?

If by “power user” we mean “able to solve virtually any problem that naturally fits into Emacs” then I’m probably a power user. If it means being able to solve any problem that fits in the Emacs sphere, then probably not.

What do you all think? What does it mean to be an Emacs power user? Is it entirely subjective or can we say “be able to do everything on this list and you’re a power user”?

-1:-- Becoming An Emacs Power User (Post Irreal)--L0--C0--2025-06-16T15:22:32.000Z

Jeremy Friesen: Re: The Cult of Hard Mode

I read Joan’s The Cult of Hard Mode: Why Simplicity Offends Tech Elites, something I had skipped over until I read Jack’s Hard Mode and status (and follow-up Hard Mode and blogging).

Jack writes quick thoughts, fluttering between changing out entire tool sets for his various workflows. Joan writes extended thoughts, creating a pathway that explores the thought and relations.

I love both of their blogs: one an excited friend sharing their moments another a more quiet person, but one that when they speak, I know I should give a listen. Crucial to both is that I receive their thoughts on my terms.

Interviews as Opportunities

For the last few weeks I’ve been part of a team interviewing candidates Sight Reliability Engineers and/or DevOps positions. I have nominal experience with Kubernetes, but I’m gaining an understanding of the response to questions around Kubernetes.

Namely that there are responses that are a ritual invocation of a litany of phrases that will sequentially check off some bingo card. And there are responses that will skip over most of that and leave mental energy for follow-up questions.

The former, an explanation of the ad nauseam variety; the other an acknowledgment that its complex, with an implicit “what specifically would you like to know?” And the most flowing interviews are those that dive into specifics.

In other words, the candidates that can paint a picture of the problems they solved, not the tools they used, are far more interesting. They become a chance for bi-directional learning.

Can We Learn Something?

I think to an interview years ago, for a different role (Copyright Librarian for those keeping track). I don’t remember the specifics, but I remember leaving with a solid understanding of an Academia-endemic organizational problem, along with a straightforward strategy of addressing that problem.

Later, I would go on to build a software application that applied the strategy of addressing the problem. The result, a collaborative workflow application that had a data driven state machine, with optional and required activities which upon completion would expose different actions to advance state. It also enforced state based permissions.

The participants in this workflow were:

  • Graduate students submitting their master’s thesis or doctoral dissertation.
  • Professors, both research advisors and committee members; some of whom may not be at the organization but were on the committee that would approve the submission.
  • The Graduate School administrator who would both nudge things along; helping people at their stage, and also themselves performing specific duties.
  • An IT Department agent that would poll the data to understand the where things were at in regards to graduation and credentialing.
  • A library unit who would catalog the research papers.
  • An library agent who would ingest the cataloged papers into the institutional repository.

The machinery was complicated, but the tool was designed to be simple for each participant.

Back to My Tools

I wrote about Emacs: Take Two; and what might have nudged me to adopt Emacs 📖 earlier. A long-standing joke is that Emacs is a great operating system that needs a better text editor.

Denote

I appreciate the simplicity of Denote 📖 ’s naming scheme. The file to which I’m writing this blog post is named 20250612T081424--re-the-cult-of-hard-mode__blogPosts_emacs_responses.org=

20250612T081424
the identifier as well as date and time I created the file.
--re-the-cult-of-hard-mode
the title of the document I’m working on. With dashes separating words, and replacing most non-alphabetical characters with dashes. The leading double dash is a separating between identifier of title.
__blogPosts_emacs_responses
the tags of this document, each tag separated by an underscore.
.org
the file extension (e.g., indicating Org-Mode 📖 ).

This simple naming schema, which itself is configurable, brings a strategic consistency to filenames; one independent of Emacs and Org-Mode ; leaning on a lower level standard: POSIX.

The brilliance of Denote is in its facilitation of this simplicity, making it easy to follow and utilize a file naming convention. Sidenote I worked at a library, and it was maddening how inconsistent the department was in regards to naming and organizing their files. If you do one simple thing, prefix all of your files with YYYYMMDD (year, month day). The ability to sort (and search) on that alone is worth the discipline. And from that convention allowing the emergence of tools.

Integration

Emacs , for me, simplifies the integration of different computering activities. Were Emacs a toy, it would be a jumbled of old Lego pieces (or an Erector Set or Lincoln Logs) and other apps would be things like Play Mobile.

In isolation, each application may be simpler, but the unifying computering simplifies the cognitive load of my interaction with computer tasks. Consistent keybindings, fully customizable, allow me to build a muscle memory that persists across computer tasks…so long as they happen in Emacs.

There is the allure of shaving the Emacs yak; that is fiddling with features and tweaks—which comes in the form of writing Lisp. Something I find myself doing on occasion. But even that fiddling is like practicing musical scales with my foundational computering environment.

The Bespoke Beast

As much as I love the idea of static websites, it’s hard to argue that they’re not the “hard mode” of blogging.

I fully agree with this statement. Yet, for me, blogging is an activity that springs from a myriad of my computering interactions:

  • Personal journal
  • Reading an article from my RSS 📖 feed
  • Analyzing and gathering material for documentation at work
  • Stringing together some code snippets
  • Playing a solo role-playing game session

And the act of publishing is two fold:

  • Transforming/projecting that writing into an acceptable file format (e.g., Markdown)
  • Sending it forward into the world.

Yet the blog posts remain within the context of what and where I’ve written. Something that my Bespoke Beast allows. And for myself I value that cohesion. Which is not to say that I couldn’t maintain that cohesion using a blogging platform; but it would push the complexity elsewhere.

Hosted Services

I pay for a static host; agreeing that all I have is a file system that can serve up files. I have offloaded to the hosting company the complexity of keeping up-to-date a web server (Apache or Nginx). I pay them to keep that simple for me.

I can pay for them to manage more of my complexity, thus creating a localized and relative-to-me simplicity. I can invoke the wizard’s spell Transmute Cash to Simplicity. And do so often.

Simplicity as Honeypot

But that invocation means becoming beholden to the simplifier. Perhaps what I send to the simplifier becomes no longer available in the form I sent it. Perhaps the receiver operates with an agenda that remains opaque to me; and perhaps run contrary. And maybe they themselves simplify through spell, by casting a complexity to another.

Collectively perhaps we’re navigating the web of inter-dependency hoping to push complexity elsewhere and to secure localized simplicity. We can see this in Vibe Coding, in which simplicity of developing something new stands on the mathematically mulched and composted remains of the shoulders of giants.

And my hope is not that this is read as an excoriation of Jack…who moves amongst different tool sets with an admirable fluidity, demonstrating an adaptive mindset and mental plasticity. Instead, to push a bit on potential false flags of simplicity.

As there are honeypots out there, offering simple experiences, that ensnare; seeking to feast on the weary or ease seekers.

So, I look to simplicity with caution, especially in regards to the breadth of computering. Something offered to me as a simple approach may be a tar pit. Perhaps luring me in. Or perhaps for which I am the bait.

Better Off

I often think of Eric Brende’s Better Off. In part as I’ve long lived in Amish country, and his story is one of Amish-mindsets in regards to technology adoption.

In short, in my area of Amish communities, many have flip phone, electric bikes, solar panels, and some electric machinery. And these are “allowed” their governing bishop. Because as a community they have assessed that this technology is a net positive. Making easier the business of woodworking and errand running.

My computering needs have greatly narrowed: Emacs is my go to for computering except when I need Javascript enabled web-browsing functionality. I do leverage supporting infrastructure for privacy: namely a Virtual Private Network (VPN 📖) and Open Snitch.

On Status

I wrote this to think through my principles of computer tool adoption. And as I was writing this the known-to-me complexity of our present state of computer use became clearer and grew. The world wide web (of technology) is complex and massive, with participants operating with a myriad of agenda.

While writing I didn’t reflect on the other aspect…the “Hard-Mode as status.” I know that I’ve been proud of the once hard-to-me things that I’ve done; sharing the results in part seeking the affirmation of others.

Listening and hoping to hear the whispers of “That Jeremy, he’s not afraid to tackle the hard things. And share what he’s done.” My ability to solve problems is what pays my bills, and these “hard things” are my signaling a capacity for problem-solving.

An admixture of hubris and attention-seeking. One that I’ve sought to temper by interrupting my problem solving preferences of idea generation and implementation to introduce up front clarification. To be able to name an observed problem and sit with the impact of that problem. Some problems dissolve under a bit of reflection.

Conclusion

I cannot express enough the joy of reading articles that are in conversation. Also in having a space in which to respond—thinking aloud—and thus enjoin the conversation.

To think and write not in interruption but as birdsong in the forest that is this world.

-1:-- Re: The Cult of Hard Mode (Post Jeremy Friesen)--L0--C0--2025-06-14T23:39:38.000Z

Paul Jorgensen: My Emacs Elfeed Configuration

※ Links are at the bottom of the post.

Elfeed is an Emacs package for experiencing one’s RSS feeds. There are others. I leave it as an exercise for the reader to learn more.

(use-package elfeed
  :custom
  ;; (elfeed-db-directory (expand-file-name "elfeed" user-emacs-directory)) ; uncomment this line and 
  (elfeed-db-directory "~/plrjorg/elfeed") ; comment this line if you want elfeed to write to your .emacs.d
  (elfeed-enclosure-default-dir "~/plrjorg/Downloads/") ; or wherever you want downloads to go
  (elfeed-search-remain-on-entry t)
  (elfeed-search-title-max-width 100)
  (elfeed-search-title-min-width 30)
  (elfeed-search-trailing-width 25)
  (elfeed-show-truncate-long-urls t)
  (elfeed-sort-order 'descending)
  :bind
  ("C-c w e" . bjm/elfeed-load-db-and-open)
  (:map elfeed-search-mode-map
        ("w" . elfeed-search-yank)
        ("R" . elfeed-update)
        ("q" . bjm/elfeed-save-db-and-bury)
        ("Q" . elfeed-kill-buffer)
        )
  (:map elfeed-show-mode-map
        ("S"     . elfeed-show-new-live-search) ; moved to free up 's'
        ("c"     . (lambda () (interactive) (org-capture nil "capture")))
        ("e"     . email-elfeed-entry)
        ("f"     . elfeed-show-fetch-full-text)
        ("w"     . elfeed-show-yank)
        )
  :hook
  (elfeed-show-mode . visual-line-mode) ; make reading pretty
  (elfeed-show-mode . olivetti-mode   ) ; make reading pretty
  )

elfeed-org helps me with adding tags to my feeds. Need refinement as it’s not doing exactly what I want at the moment.

;; Configure Elfeed with org mode

(use-package elfeed-org
  :after elfeed
  :custom
  (rmh-elfeed-org-files
   (list "~/.emacs.d/elfeed.org"))
)

(with-eval-after-load 'elfeed
  (elfeed-org))

~/plrjorg/ is my Nextcloud Files directory. I use it for synchronizing things, including elfeed.

When we talk about syncing elfeed, there are two components: feed sync and database sync. They are independent of each other. One does not need either.

First is feed synchronization. There are many platforms that provide a protocol to do this, and some of them will work with elfeed et al. See elfeed-protocol for what’s supported. One can chose to not use a protocol and put their feeds in their Emacs config or in an elfeed-org file (see below). One can then copy them to other Emacsen one may have.

Second is database synchronization, which is specific to elfeed and Emacs. This is only sort-of supported by elfeed. I use a combination of some functions and Nextcloud sync to make this part happen, but the reader can find articles about using syncthing or other tools.

WARNING!!! Not all modern file sync methods are appropriate for this! iCloud, Box, Synology Drive, and others don’t really like permanently keeping files on a system. While one can mark a folder as “Make available offline” or similar, it may still cause problems. I use Nextcloud because it works like Dropbox used to (still does? I dunno) and doesn’t rely on 🍎’s or M$’s newer APIs. Again, feel free to search for more information as my understanding may be faulty &| out of date.

NOTE Nextcloud sync is not a perfect solution out of the box. The client wants to notify about every. little. thing., so one will want to mute all but urgent notifications. Also, while Nextcloud has some version control for files, it is far from robust. Backups are still a must.

I like the modern convenience of syncing state in and out of Emacs, so I make use of elfeed-protocol. I’ve used it to connect to a Nextcloud News install, but currently point it at my self-hosted FreshRSS install. For the sake of this article, it’s url is http://freshrss.example so edit that as needed. Eventually I will move the authentication into .authinfo.gpg where it belongs.

(use-package elfeed-protocol
  :after (elfeed elfeed-org)
  :bind
  (:map elfeed-show-mode-map
        ("s" . elfeed-protocol-fever-star-tag)
        )
  :custom
  (elfeed-use-curl t)			; My FreshRSS wants curl
  (elfeed-curl-extra-arguments '("--insecure"))
  (elfeed-curl-max-connections 10)
  (elfeed-protocol-feeds '(("fever+http://paul@freshrss.example/" :api-url "http://freshrss.example/api/fever.php" :password "somecoollongpassword")))
  (elfeed-protocol-fever-fetch-category-as-tag t)
  (elfeed-protocol-fever-maxsize 10000)
  (elfeed-protocol-fever-update-unread-only t)
  (elfeed-protocol-lazy-sync t)
  (elfeed-set-timeout 36000)
  ;; (elfeed-log-level 'debug)  ; for debugging when needed
  ;; (elfeed-protocol-fever-maxsize 10)  ; for debugging when needed
  ;; :hook
  ;; (after-init . toggle-debug-on-error)   ; for debugging when needed
  )

(with-eval-after-load 'elfeed
  (elfeed-protocol-enable))

I’ll take this moment to describe the with-eval-after-load items. Basically, these packages don’t load quickly as a whole. I don’t need them until I actually do something with elfeed. My config essentially keeps them hanging around until I do do something with elfeed, then they load.

I don’t kill my Emacs often. As such I don’t care a lot about load times. But I do have a geriatric Intel MacBook Air I use when and where I don’t want to take my fancy M3 MBA. For her sake and my eventual foray into Emacs on Android I will care a little bit. Also, Dr Peter Prevos and Bozhidar Batsov put me on to how to improve my use of use-package in this regard, but it’s outside of the scope of this article.

Again, for the sake of completeness, here is the rest of my elfeed config:

(use-package elfeed-tube
  :after elfeed
  :bind (:map elfeed-show-mode-map
         ("F" . elfeed-tube-fetch)
         ([remap save-buffer] . elfeed-tube-save)
         :map elfeed-search-mode-map
         ("F" . elfeed-tube-fetch)
         ([remap save-buffer] . elfeed-tube-save)
         )
  ;; :config
  ;; (setq elfeed-tube-auto-save-p nil) ; default value
  ;; (setq elfeed-tube-auto-fetch-p t)  ; default value
  ;; (elfeed-tube-setup)
  ;; :hook
  ;; (after-init . elfeed-tube-setup)
  )

(with-eval-after-load 'elfeed-tube
  (elfeed-tube-setup))

(use-package elfeed-tube-mpv
  :bind (:map elfeed-show-mode-map
              ("C-c C-f" . elfeed-tube-mpv-follow-mode)
              ("C-c C-w" . elfeed-tube-mpv-where)
              )
  )

(use-package elfeed-webkit
  :after elfeed
  :bind (:map elfeed-show-mode-map
              ("%" . elfeed-webkit-toggle)
              )
  )

(with-eval-after-load 'elfeed
  (elfeed-webkit-auto-toggle-by-tag))

(use-package elfeed-curate
  :after elfeed
  :bind
  (:map elfeed-search-mode-map
        ("a" . elfeed-curate-edit-entry-annoation)
        ("x" . elfeed-curate-export-entries)
        )
  (:map elfeed-show-mode-map
        ("a" . elfeed-curate-edit-entry-annoation)
        ("m" . my/elfeed-curate-toggle-star)
        )
  )

;; Easy insertion of weblinks

(use-package org-web-tools
  :bind
  (("C-c w w" . org-web-tools-insert-link-for-url)))

;; Internet

(use-package emacs
  :custom
  ;; (shr-fill-text nil)			; Emacs 30.1+
  (url-queue-parallel-processes 10)
  (url-queue-timeout 30)
  :ensure nil
  )

Note that not all of these bits are working as I’d like. elfeed-webkit works but needs refining in my config, as does elfeed-org.

This is where things get dodgier: my functions, most of which I copied from an older config. I might not be using them all, especially as elfeed has seen it’s own refinement over the years.

First are the functions I’m using to sync:

(defun elfeed-protocol-fever-sync-unread-stat (host-url)
  "Set all entries in search view to read and fetch latest unread entries.
HOST-URL is the host name of Fever server with user field authentication info,
for example \"https://user@myhost.com\".

Author: Andrey Listopadov
URL https://github.com/fasheng/elfeed-protocol/issues/71#issuecomment-2483697511
Created: 2024-11-18
Updated: 2025-06-12"
  (interactive
   (list (completing-read
          "feed: "
          (mapcar (lambda (fd)
                    (string-trim-left (car fd) "[^+]*\\+"))
                  elfeed-protocol-feeds))))
  (save-mark-and-excursion
    (mark-whole-buffer)
    (cl-loop for entry in (elfeed-search-selected)
             do (elfeed-untag-1 entry 'unread))
    (elfeed-protocol-fever--do-update host-url 'update-unread))
  )

;;functions to support syncing .elfeed between machines
;;makes sure elfeed reads index from disk before launching
(defun bjm/elfeed-load-db-and-open ()
  "Wrapper to load the elfeed db from disk before opening

URL https://pragmaticemacs.wordpress.com/2016/08/17/read-your-rss-feeds-in-emacs-with-elfeed/
Created: 2016-08-17
Updated: 2025-06-13"
  (interactive)
  (elfeed)
  (elfeed-db-load)
  ;; (elfeed-search-update--force)
  (elfeed-update)
  (elfeed-db-save)
  )

;;write to disk when quiting
(defun bjm/elfeed-save-db-and-bury ()
  "Wrapper to save the elfeed db to disk before burying buffer

URL https://pragmaticemacs.wordpress.com/2016/08/17/read-your-rss-feeds-in-emacs-with-elfeed/
Created: 2016-08-17
Updated: 2025-06-13"
  (interactive)
  (elfeed-update)
  (elfeed-db-save)
  (quit-window))

These are the rest.

;; Elfeed functions

(org-link-set-parameters "elfeed"
  :follow #'elfeed-link-open
  :store  #'elfeed-link-store-link
  :export #'elfeed-link-export-link)

(defun elfeed-link-export-link (link desc format _protocol)
  "Export `org-mode' `elfeed' LINK with DESC for FORMAT.

Author: Jeremy Friesen
URL https://takeonrules.com/2024/08/11/exporting-org-mode-elfeed-links/
Created: 2024-08-11
Updated:20205-06-10"
  (if (string-match "\\([^#]+\\)#\\(.+\\)" link)
    (if-let* ((entry
                (elfeed-db-get-entry
                  (cons (match-string 1 link)
                    (match-string 2 link))))
               (url
                 (elfeed-entry-link entry))
               (title
                 (elfeed-entry-title entry)))
      (pcase format
        ('html (format "<a href=\"%s\">%s</a>" url desc))
        ('md (format "[%s](%s)" desc url))
        ('latex (format "\\href{%s}{%s}" url desc))
        ('texinfo (format "@uref{%s,%s}" url desc))
        (_ (format "%s (%s)" desc url)))
      (format "%s (%s)" desc url))
    (format "%s (%s)" desc link)))

;;
;; I think elfeed handles buffers and the scrolling better now, so disable these.
;;
;; (defun elfeed-display-buffer (buf &optional act)
;;   "URL https://karthinks.com/software/lazy-elfeed/"
;;   (pop-to-buffer buf)
;;   (elfeed-show-refresh)
;;   (set-window-text-height (get-buffer-window) (round (* 0.7 (frame-height))))
;;   (elfeed-show-refresh)
;;   )

;;   (defun elfeed-search-show-entry-pre (&optional lines)
;;   "Returns a function to scroll forward or back in the Elfeed
;;   search results, displaying entries without switching to them."
;;       (lambda (times)
;;         (interactive "p")
;;         (forward-line (* times (or lines 0)))
;;         (recenter)
;;         (call-interactively #'elfeed-search-show-entry)
;;         (select-window (previous-window))
;;         (unless elfeed-search-remain-on-entry (forward-line -1))))

;; (defun elfeed-scroll-up-command (&optional arg)
;;   "Scroll up or go to next feed item in Elfeed"
;;   (interactive "^P")
;;   (let ((scroll-error-top-bottom nil))
;;     (condition-case-unless-debug nil
;;         (scroll-up-command arg)
;;       (error (elfeed-show-next)))))

;; (defun elfeed-scroll-down-command (&optional arg)
;;   "Scroll up or go to next feed item in Elfeed"
;;   (interactive "^P")
;;   (let ((scroll-error-top-bottom nil))
;;     (condition-case-unless-debug nil
;;         (scroll-down-command arg)
;;       (error (elfeed-show-prev)))))

(defun elfeed-tag-selection-as (mytag)
    "Returns a function that tags an elfeed entry or selection as
MYTAG"
    (lambda ()
      "Toggle a tag on an Elfeed search selection"
      (interactive)
      (elfeed-search-toggle-all mytag)))

(defun elfeed-show-eww-open (&optional use-generic-p)
  "open with eww"
  (interactive "P")
  (let ((browse-url-browser-function #'eww-browse-url))
    (elfeed-show-visit use-generic-p)))

(defun elfeed-search-eww-open (&optional use-generic-p)
  "open with eww"
  (interactive "P")
  (let ((browse-url-browser-function #'eww-browse-url))
    (elfeed-search-browse-url use-generic-p)))

(defun browse-url-mpv (url &optional single)
  (start-process "mpv" nil "mpv" (shell-quote-argument url)))

(defun email-elfeed-entry ()
  "Capture the elfeed entry and put it in an email.

URL https://github.com/jkitchin/scimax/blob/master/scimax-elfeed.el
Created: 2021-11-16
Updated: 2025-06-10"
  (interactive)
  (let* ((title (elfeed-entry-title elfeed-show-entry))
         (url (elfeed-entry-link elfeed-show-entry))
         (content (elfeed-entry-content elfeed-show-entry))
         (entry-id (elfeed-entry-id elfeed-show-entry))
         (entry-link (elfeed-entry-link elfeed-show-entry))
         (entry-id-str (concat (car entry-id)
                               "|"
                               (cdr entry-id)
                               "|"
                               url)))
    (compose-mail)
    (message-goto-subject)
    (insert title)
    (message-goto-body)
    (insert (format "You may find this interesting:
%s\n\n" url))
    (insert (elfeed-deref content))

    (message-goto-body)
    (while (re-search-forward "<br>" nil t)
      (replace-match "\n\n"))

    (message-goto-body)
    (while (re-search-forward "<.*?>" nil t)
      (replace-match ""))

    (message-goto-body)
    (fill-region (point) (point-max))

    (message-goto-to)
    (ivy-contacts nil)))

;; * store links to elfeed entries
;; These are copied from org-elfeed
(defun org-elfeed-open (path)
  "Open an elfeed link to PATH."
  (cond
   ((string-match "^entry-id:\\(.+\\)" path)
    (let* ((entry-id-str (substring-no-properties (match-string 1 path)))
           (parts (split-string entry-id-str "|"))
           (feed-id-str (car parts))
           (entry-part-str (cadr parts))
           (entry-id (cons feed-id-str entry-part-str))
           (entry (elfeed-db-get-entry entry-id)))
      (elfeed-show-entry entry)))
   (t (error "%s %s" "elfeed: Unrecognised link type - " path))))

(defun org-elfeed-store-link ()
  "Store a link to an elfeed entry."
  (interactive)
  (cond
   ((eq major-mode 'elfeed-show-mode)
    (let* ((title (elfeed-entry-title elfeed-show-entry))
           (url (elfeed-entry-link elfeed-show-entry))
           (entry-id (elfeed-entry-id elfeed-show-entry))
           (entry-id-str (concat (car entry-id)
                                 "|"
                                 (cdr entry-id)
                                 "|"
                                 url))
           (org-link (concat "elfeed:entry-id:" entry-id-str)))
      (org-link-store-props
       :description title
       :type "elfeed"
       :link org-link
       :url url
       :entry-id entry-id)
      org-link))
   (t nil)))

;; Duplicate of above!
;;  ⮯⮮
;; (org-link-set-parameters
;;  "elfeed"
;;  :follow 'org-elfeed-open
;;  :store 'org-elfeed-store-link)

;; my/elfeed-show-entry-advice: ensures we store the entry and refresh the article after it has been rendered. The run-at-time trick delays just enough to avoid incomplete rendering.
;;
;; my/elfeed-refresh-on-resize: triggers refresh if the window is resized and you're currently in elfeed-show-mode.
;;
;; It tracks the current entry so you don’t get errors from nil.

(defvar my/elfeed-current-entry nil
  "Currently displayed Elfeed entry (used to trigger refresh on resize).")

(defun my/elfeed-show-entry-advice (entry)
  "Store the currently shown ENTRY and reflow after display."
  (setq my/elfeed-current-entry entry)
  ;; Run refresh a bit later so it's sure the buffer is fully rendered
  (run-at-time "0.05 sec" nil
               (lambda ()
                 (when (eq major-mode 'elfeed-show-mode)
                   (elfeed-show-refresh)))))

(advice-add 'elfeed-show-entry :after #'my/elfeed-show-entry-advice)

(defun my/elfeed-refresh-on-resize (_frame)
  "Reflow Elfeed article when the window is resized."
  (when (and (eq major-mode 'elfeed-show-mode)
             my/elfeed-current-entry)
    ;; this ensures we re-render the article on resize
    (elfeed-show-refresh)))

(add-hook 'window-size-change-functions #'my/elfeed-refresh-on-resize)

(defun elfeed-show-fetch-full-text ()
  "Fetch the full text of the current Elfeed entry using eww-readable."
  (interactive)
  (let* ((entry elfeed-show-entry)
         (url (elfeed-entry-link entry)))
    (eww url)  ;; Open the URL in eww
    (run-at-time "0.5 sec" nil
                 (lambda ()
                   (with-current-buffer "*eww*"
                     (eww-readable))))))  ;; Call eww-readable after a short delay

(defvar elfeed-curate-exit-keys "C-x C-s"
  "Save the content from the recursive edit buffer to an entry annotation."
  )

(defun my/elfeed-curate-toggle-star ()
  "Wrapper to refresh the entry to see the star tag in the entry."
  (interactive)
  (elfeed-curate-toggle-star)
  (elfeed-show-refresh)
  )

(defun elfeed-curate-edit-annotation (title default-string)
  "Edit annotation string for the group TITLE with the DEFAULT-STRING.
  Returns the annotation buffer content."
  (with-temp-buffer
    (org-mode)
    (setq buffer-read-only nil)
    ;; (setq mode-line-format nil)
    (outline-show-all)
    (rename-buffer elfeed-curate-capture-buffer-name t)
    (insert default-string)
    (goto-char (point-max))
    (let ((title-str (propertize (concat " '" (elfeed-curate-truncate-string title elfeed-curate-title-length) "'")
                                 'face 'mode-line-buffer-id)))
      (setq header-line-format
            (list
             (elfeed-curate--key-emphasis "Annotate")
             title-str
             " --> Save: '" (elfeed-curate--key-emphasis elfeed-curate-exit-keys)
             "', Delete: '" (elfeed-curate--key-emphasis elfeed-curate-delete-keys)
             "', Abort: '"  (elfeed-curate--key-emphasis elfeed-curate-abort-keys)
             "'")))
    (switch-to-buffer (current-buffer))
    (use-local-map (elfeed-curate--annotation-keymap))
    (font-lock-mode)
    (recursive-edit)
    (buffer-substring-no-properties (point-min) (point-max))
    )
  )

The main dependency outside of Emacs is curl. For the elfeed-tube stuff, mpv and yt-dlp. Check the relevant documentation.

-1:-- My Emacs Elfeed Configuration (Post Paul Jorgensen)--L0--C0--2025-06-14T13:49:00.000Z

Listful Andrew: Focused navigation in Markdown

Fold and Focus can now navigate Markdown buffers, besides the original Org and Elisp.
-1:-- Focused navigation in Markdown (Post Listful Andrew)--L0--C0--2025-06-14T13:30:00.000Z

Marcin Borkowski: Automatically converting timestamps in Emacs

This is a third post in a short series of posts about code which helps me deal with numerical timestamps in Emacs. Last time I wrote a function which decides if a number is a Unix timestamp or a JavaScript Date and converts it to a human-readable ISO-8601 timestamp. What would be even more useful is to see the ISO representation of the timestamp at point without even doing anything. Emacs already has support for showing stuff related to the thing at point in the modeline – ElDoc. Several years ago I had some thoughts about using the ElDoc machinery to do something for me, but I found it too complicated then. Well, either they modified ElDoc to make it easier to program, or I matured as a programmer, or both. It turned out that it took me about 15 minutes to come up with a working prototype, which is extremely cool!
-1:-- Automatically converting timestamps in Emacs (Post Marcin Borkowski)--L0--C0--2025-06-14T13:26:47.000Z

James Dyer: Emacs dired with Ultra-Lightweight Visual Icons

If you spend any time browsing files in Emacs, you’ve probably wished for a bit more visual distinction in dired buffers?. While packages like all-the-icons-dired provide beautiful icon sets, they come with dependencies, font requirements, and potential compatibility issues. What if you could get meaningful visual file type indicators with just a few simple lines of pure Elisp? I have adapted the original idea provided by Emacs-solo and focused more on the earlier Unicode characters that are most likely to always be present in an Emacs environment.

Popular dired icon packages often require:

  • Installing specific icon fonts
  • Managing font fallbacks across different systems
  • Dealing with alignment issues in terminal Emacs
  • Large dependency chains that slow down startup
  • Compatibility headaches when sharing configs

I have also noticed that Emacs can just crash if it is dealing with an icon that isn’t installed on the system.

Here’s an implementation that adds visual file type indicators using only standard Unicode characters. Of course the icons-map isn’t exhaustive but might be a sensible minimal starting point.

(defvar dired-icons-map
  '(("el" . "λ") ("rb" . "◆") ("js" . "○") ("ts" . "●") ("json" . "◎") ("md" . "■")
    ("txt" . "□") ("html" . "▲") ("css" . "▼") ("png" . "◉") ("jpg" . "◉")
    ("pdf" . "▣") ("zip" . "▢") ("py" . "∆") ("c" . "◇") ("sql" . "▦")
    ("mp3" . "♪") ("mp4" . "▶") ("exe" . "▪")))

(defun dired-add-icons ()
  (when (derived-mode-p 'dired-mode)
    (let ((inhibit-read-only t))
      (save-excursion
        (goto-char (point-min))
        (while (and (not (eobp)) (< (line-number-at-pos) 200))
          (condition-case nil
              (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
                (when (and (> (length line) 10)
                           (string-match "\\([rwxd-]\\{10\\}\\)" line)
                           (dired-move-to-filename t)
                           (not (looking-at "[▶◦λ◆○●◎■□▲▼◉▣▢◇∆▦♪▪] ")))
                  (let* ((is-dir (eq (aref line (match-beginning 1)) ?d))
                         (filename (and (string-match "\\([^ ]+\\)$" line) (match-string 1 line)))
                         (icon (cond (is-dir "▶")
                                    ((and filename (string-match "\\.\\([^.]+\\)$" filename))
                                     (or (cdr (assoc (downcase (match-string 1 filename)) dired-icons-map)) "◦"))
                                    (t "◦"))))
                    (insert icon " "))))
            (error nil))
          (forward-line))))))

(add-hook 'dired-after-readin-hook 'dired-add-icons)

The icons use only standard Unicode geometric shapes and mathematical symbols that have been supported in every font since the 1990s. No special fonts required, it works identically in:

  • Terminal Emacs
  • GUI Emacs on any platform
  • Ancient and modern Emacs versions

There are some advantages to be had:

  • Zero dependencies: Pure Elisp, no external packages
  • Tiny footprint: 30 lines vs hundreds in full icon packages
  • Instant startup: No font loading or icon caching
  • Robust error handling: Gracefully skips problematic files
  • Performance limits: Processes max 200 files to prevent freezing but of course that can be modified to taste

Once added to your config, it just works. No font updates, no package maintenance, no breaking changes from upstream dependencies.

Here is a typical example:

After:

Before:

Want different icons? Just modify the dired-icons-map:

;; Prefer asterisks for code files?
("js" . "*") ("py" . "*") ("rb" . "*")

;; Like filled shapes for documents?
("md" . "▪") ("txt" . "▪") ("pdf" . "▪")

;; Add your own file types
("log" . "□") ("cfg" . "○") ("bak" . "▫")

Simply copy the code into your init.el and off you go!

-1:-- Emacs dired with Ultra-Lightweight Visual Icons (Post James Dyer)--L0--C0--2025-06-12T21:37:00.000Z

Anand Tamariya: Plan 9 Remote File Access from Emacs

 


Plan 9 Operating System uses 9p protocol for file access. This is an Elisp implementation of the protocol.

Plan 9 (9front distribution) is running in QEMU with NAT networking. Local port 12564 is forwarded to 9fs service port 564 in the virtual machine.


 

Code

 

Troubleshooting Plan 9 connection

  1. Ensure you are booting with  -a tcp!*!564  parameters. (Tip: You can add these to your /n/9fat/plan9.ini)
  2. Ensure you have configured the network interface   ip/ipconfig  
  3. Ensure you have an IP address  cat /net/ndb 
  4. Ensure you are running cpu+auth server  cpurc 
  5. (Optional) Start graphics mode  screenrc  
  6. (Optional) Start window manager  rio  
  7. (Optional) List open connections  netstat  
  8. (Optional) Monitor network traffic  snoopy  
  9. (Optional) Debug authentication  auth/debug 
  10. (Optional) Run network configuration tests  netaudit 
plan9.ini
After mounting 9fat partition using  9fs 9fat , you can find plan9.ini in /n/9fat/plan9.ini
 
bootfile=9pc64
monitor=vesa
mouse=p2intellimouse
vgasize=1280x768x24

bootargs=local!/dev/sdC0/fs -a tcp!*!564

user=glenda
authdom=virtual
auth=10.0.2.15
cpu=10.0.2.15
service=cpu
  
 

Useful Plan 9 Tools

  • riow - Keyboard based window management
  • bar - Display date and time in a bar
  • stats - Graphical stats for CPU, memory and network
  • winwatch - Display labels of open windows in a stack layout (aka task bar)
  • sam and acme - Editor
  • mothra and abaco - Web browser
  • hget - CLI client for web

 

 References

  1. 9P Protocol
  2. 9p server in Go
  3. Plan 9 authentication protocol 
  4. No Bridge networking on wireless interface 
     

 

-1:-- Plan 9 Remote File Access from Emacs (Post Anand Tamariya)--L0--C0--2025-06-12T04:08: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!