Recently:

Charles Choi: Capturing an Org note via macOS Shortcuts

With a new update of Scrim v1.1.0 on the App Store, it seems an opportune time to show a nice macOS Shortcut integration with it.

The use case is this: You're on your Mac, working on something else other than Emacs (it happens!) and you want to make a quick note in an Org file without having to context switch to Emacs.

You can accomplish this with a macOS Shortcut. Shortcuts is an Apple tool that lets you orchestrate different apps to achieve a custom workflow.

Shown below is the shortcut named “Make Note Entry” to accomplish the above use case. It will:

  1. Prompt the user to enter text.
  2. Run a Python script on that text and construct an Org protocol capture request with it using the template named "note".
  3. Issue (open) the Org protocol request.

If the shortcut is configured so that “Pin in Menu Bar” is turned on, you will see some variant of its entry in the Shortcuts bar icon menu.

Running the shortcut “Make Note Entry” will prompt you with a dialog where you can enter your note:

Pressing the “Done” button will result in the entry being inserted at the top of the file “~/org/notes.org” as shown below. Note however that Emacs is not raised with this shortcut. The screenshot below is only shown to illustrate that the entry is there.

Installation

Want to try this out? Here’s what to do:

1. Understand Org protocol

If you don’t already then read up about it at org-protocol.el – Trigger actions in Emacs via a custom URL scheme.

2. Have Scrim installed and configured

Learn more about Scrim at http://yummymelon.com/scrim.

3. Add the capture template “note”

On the Emacs side, add the following template entry to org-capture-templates. This template named “note” defines an entry with a heading that has an inactive Org timestamp (“%U”) and the body containing the entered text (“%i”). The destination of this note entry is in the file “~/org/notes.org”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
("note"
 "Note (Org Protocol)"
 entry
 (file "~/org/notes.org")
 (function (lambda ()
             (string-join
              '("* %U"
                "%i")
              "\n")))
 :prepend t
 :immediate-finish t
 :empty-lines-after 1)

Observe that this template uses the keys :immediate-finish to not make the entry interactive and :prepend to insert the entry at the top of the file.

4. Add the Shortcut “Make Note Entry”

Download the shortcut file Make Note Entry.shortcut and double-click on it to install. It should now be in your Shortcuts collection.

5. Run “Make Note Entry”

On the first run of this shortcut, you will be prompted twice to give it permissions: 1) to run the shortcut and 2) to open Scrim. Inspect that its contents correspond with its screenshot above and grant them.

Closing Thoughts

Readers are invited to take these ideas and customize them for their own purposes.

-1:-- Capturing an Org note via macOS Shortcuts (Post Charles Choi)--L0--C0--2025-07-02T21:30:00.000Z

Irreal: Charles Choi’s Second Take On Eshell

If you’re a regular Irreal reader, you’ll be familiar with Charles Choi. He often has useful takes on Emacs matters and, of course, he’s the author of the excellent Casual Suite that helps you use all the arcane features of many familiar Emacs commands.

Early on in his Emacs career, Choi tried Eshell but it didn’t take. That’s because, he says, he was expecting it to act like a normal shell, which it most certainly doesn’t. Now Choi has a post that offers a second take on Eshell. It’s a take informed by Choi’s many years of using Emacs and learning to appreciate Eshell.

Choi says that you should begin by thinking of Eshell as offering an Emacs REPL that can easily reference the command line utilities traditionally accessed through a conventional shell. He gives several examples of how that works.

He also mentions something that I’ve noticed too. He now does most of the things he used to do in a conventional shell through Emacs functionality like Dired and Magit. He has a table in his post that compares various shell tasks with their corresponding Eshell equivalents.

I try to use Eshell whenever I need a shell but I haven’t internalized the Eshell way as Choi has. Reading his post has inspired me to do better and try to use Eshell more often. One way to do that is to use Eshell instead of using a key shortcut or Meta+x to invoke Emacs functions such as Dired.

If you’d like to improve your Emacs game, take a look at Choi’s post. There’s a lot of meat in it.

-1:-- Charles Choi’s Second Take On Eshell (Post Irreal)--L0--C0--2025-07-02T15:17:20.000Z

Christian Tietze: Emacs on Second Try

In 2018, Sascha Fast of zettelkasten.de was on a minimalist task management journey and dug into Emacs org-mode, and pressured me for days (we were sharing an apartment back then) to give it a shot, because it surely would be my jam.

While I knew Emacs existed, I didn’t feel compelled to even try it.

I did use Emacs as part of a compulsory University course on UNIX and terminal program basics. We went through the Emacs tutorial on our own terms there. I picked up a couple of shortcuts that turned out to be available system-wide on Macs, too, like Ctrl-D to delete forward, for example, and I used that a lot on laptop keyboards that only come with a dedicated backspace key. But using Emacs for anything outside that course didn’t feel too appealing. I did use it for assignments, because I liked it somewhat better than the other options for text editing on our computer terminals at Uni.

Still, I was very happy with TextMate and other “Mac-assed Mac apps”, so I didn’t bother using the software we had to use from our University terminals for more than absolutely necessary, even though it was fun to hack around in such a bare-bones environment. – Oh, if only I knew how not-bare-bones Emacs in particular was! Nobody told me about the package system at University! For example, I used Emacs to edit source files, then Ctrl-Z to move it to the background, compile from the shell, then move Emacs to the front again. I could’ve done all of this and more from within Emacs, and I didn’t know. So that was my first Emacs experience as a terminal-based text editor that had some weird but learn-able text editing shortcuts. (If only I knew …)

When I got my first Mac, I already had a laundry list of must-have apps. That was in 2008. And during that time, OmniFocus came out (work-in-progress page from 2007), and I just needed to have that app. I used it as my project and task management tool for years, with short stints into trying out Things actu. So in 2018, I had all my stuff in OmniFocus: private tasks, programming projects everything. I didn’t want to switch. It took Sascha a lot of convincing until I caved because of that. But I gave it a try eventually the more features and tweaks and packages he brought up. A digital nerd cave was promised, and boy did it deliver.

I found Emacs Lisp weird, but the malleability of the system sounded just too appealing. I was amazed that plain text UI’s could be ab-used to render so many details. Eventually, I managed to make Emacs look okay (I’m a software developer, so my standards for ‘okay’ are quite low to be productive). It eventually didn’t feel like a downgrade. OmniFocus handled copy-pasting of images and other files as task attachments nicely, and org-mode …. well, I gave up on using attachments and inline images/screenshots as much early on to focus on other things, and 7 years later still haven’t bothered to look into this for most of my .org files.

What I did adopt early on was a modal key binding by Xah Lee. All web searches for Emacs Lisp tips went to his site (or Sacha Chua’s), and Xah clearly is into ergonomics, so I gave it a shot and loved it.

Especially the B key in command mode, bound to xah-toggle-letter-case. It cycles the region or the word next to the point between capitalizing the first letter, lowercase, and all caps. I used this a lot when proof-reading and editing book manuscripts and blog posts in our collaboration at zettelkasten.de. Really, it’s not funny how often I pressed this key.

The Xah Fly Keys command mode (aka default/non-insert mode) also comes with very nice text movement keys without having to press any modifiers. It’s easier to visualize when you check out the key binding graphic:

You end up with arrow keys on I, J, K, L, on QWERTY. And then above the left arrow (J) and the right arrow (L), you have U and O bound to move by word boundary backward and forward, respectively. And to the outside, left and right of J and L, you have H and : to move to beginning and end of line, respectively.

It’s like an arrow key cluster, but enlarged.

When proof-reading, this and the case toggle key binding did a lot of the heavy lifting. It’s much nicer to have all the movement keys next to the right-hand home row than having to hold the Opt key and use arrow keys to move by word boundary on Mac. Much, much nicer.

This sold me to the idea that if I’m spending such an ungodly amount of time in a text editor anyway, I should be able to do these kinds of customizations to make it fit my work.

Eventually, I uninstalled TextMate (after more than a decade of use) and OmniFocus.

When I did, I also went down other rabbit holes to move email, version control, shell interaction, … into Emacs. Because I could, so why wouldn’t I …?


Hire me for freelance macOS/iOS work and consulting.

Buy my apps.

Receive new posts via email.

-1:-- Emacs on Second Try (Post Christian Tietze)--L0--C0--2025-07-02T07:04:59.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-30:

  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
  7. Eugene Andrienko: My Emacs configuration (common parts)
  8. Ross A. Baker: Take Two: Coming Home to Emacs
  9. Greg Newman: Emacs Take Two (Eglot)
  10. Charles Choi: Take Two: Eshell
  11. Dibyashanu Pati: Take Two - A new Start
  12. Rodion Goritskov: Take two to blogging with Emacs
  13. My own post, Emacs on Second Try

Thanks everyone for participating! July is being hosted by Greg Newman: Writing Experience. You can also host of a future carnival yourself, don’t be shy :)


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-07-02T06:59:25.000Z

Jakub Nowak: Mixing Code Styles with org-babel

I've really started making good use of org-babel recently as part of my job, and org-babel-tangle has been particularly invaluable. I never honestly expected to get any use out of org-babel outside of taking lecture notes or working through textbooks. Don't get me wrong, it's a very useful tool - and I want to explain how I'm using it now that I've found a really solid use-case for it.

If you're unfamiliar, org-babel is a literate programming extension for org-mode. On a surface level, it works similar to a Python notebook. It allows you to specify what major mode to treat a code block as and execute code directly from within your org-mode file. It also allows you to rip code out of code-blocks and place it into another file. So, for example, I can have two code-blocks at different points in my org file, both with a :tangle file.cpp header, and have them both be inserted (in order) into file.cpp. This feature is often used for literate configs within Emacs - where one org file contains all the configuration code, with suitable annotations.

I don't use a literate config (currently). Actually, when it comes to Lisp I'm generally against being too literate in your code, either in code comments or literal literate programming. There's a quote out of Riastradh's Lisp Style Rules that I generally adhere to:

Write comments only where the code is incapable of explaining itself. Prefer self-explanatory code over explanatory comments. Avoid `literate programming' like the plague.

Rationale: If the code is often incapable of explaining itself, then perhaps it should be written in a more expressive language. This may mean using a different programming language altogether, or, since we are talking about Lisp, it may mean simply building a combinator language or a macro language for the purpose. `Literate programming' is the logical conclusion of languages incapable of explaining themselves; it is a direct concession of the inexpressiveness of the computer language implementing the program, to the extent that the only way a human can understand the program is by having it rewritten in a human language.

In Lisp, via macros, we usually have the luxury of completely changing the "language" (not literally) of the code we write, which means that code can, in my opinion, actually be self-documenting. Whenever I feel the impulse to annotate something with a comment, I try to take time to reflect on whether that line could instead be written in a better way that requires less external explanation. There are exceptions, like marking where a section of some logic begins and ends, but that's my general philosophy. It's also why I like Racket so much - being able to switch as required between effectively any syntax you want for a particular problem is very useful for maintaining readability in my opinion. Or just because I want to write something using APL syntax.

However, at work, I don't have the luxury of writing in Lisp. Currently I'm doing data engineering with dbt, and I'm mostly writing in SQL (with Jinja macros sprinkled on top). There's also been a lot of hubbub around code style recently, for a number of reasons I won't get into, and now we are recommended to use sqlfluff to fix up formatting on SQL files. So, my two issues here are:

  1. SQL can be very opaque without sufficient commentary (something that is very true for a lot of older dbt models in this codebase).
  2. I do not like the standard sqlfluff formatting rules. This is really just a personal grip, but having functions and keywords be CAPITALISED has always seemed a bit pointless to me, as though it's a relic from a time before syntax highlighting.
  3. Bonus: dbt tests are often undocumented and (this is a problem with a lot of test suites, not just dbt) are abstracted away from the context of the code they're testing.

org-babel helps me fix all of these issues in one fell swoop. Better yet, it lets me do so without ever bothering other people working in the repository with org-mode or Emacs.

Let's tackle the first issue: opaqueness and literate programming. There is more to this than just explaining that "Oh, line X does Y and Z". The business context of a particular decision can also be very helpful to keep track of, and so is the story or task in Jira that a model or code block is a part of. While the former of these two could be done with just code comments, the latter benefits greatly from org's structural editing. For example, if a bunch of user stories are related and should be (per the system design) in one folder in the tree, we can structure the file like so (DE- is the user story prefix in Jira):

* DE-1: Building some pipeline
** Model 1
** Model 2

Even better, some models are made up of a massive chain of SQL CTEs. We can add those to our structure too:

* DE-1: Building some pipeline
** Model 1
*** CTE 1
*** CTE 2

For each model, we can specify the file we want the model to untangle to via header properties for each code-block.

* DE-1: Building some pipeline
** Model 1
:PROPERTIES:
:header-args:sql: :tangle model_1.sql :comments no :padline no
:header-args:yaml: :tangle _model_1.yml :comments no :padline no
:END:

*** CTE 1
*** CTE 2

This way, every SQL code-block within Model 1 automatically gets tangled into the right file, and the YAML (which is used for test specification in dbt) goes to its own file. I can write as much as I want around a code block, and it will also be ignored during the tangle. But, if I want a specific piece of text to be inserted as a comment in the output file (which I might want to do, since the output is going into version control and no one but me will ever see the 'master file'), I can do that by adding :comments org to the header of the code-block.

OK, so literate programming is solved. Let's look at the bonus issue next since that's still more in the realm of literate programming as a whole. One benefit of this setup is that I can write my YAML tests directly next to the actual SQL source code. For example:

#+begin_src sql
  select something from somewhere
#+end_src

:testing:
This goes to the yaml.
#+begin_src yaml
  - name: some_column
    data_Type: INTEGER
    tests:
      - not_null
#+end_src
:end:

This of course requires setting up the boilerplate for the YAML file, which I do in a separate code-block before the first CTE section. I also use this preface to specify the dbt materialization and table alias, as well as add a general comment as to the purpose of the model.

If you're familiar with YAML, you will know that indentation is used to determine scope, and tangling org files tends to mess up the whitespace rules. The solution here is to set the org-src-preserve-indentation variable to t - but we want this to be on a per-file basis. And we have some other Elisp that we want to run, so that we can fix the second issue of code style. I have an Elisp code block that looks like this at the start of the org file:

#+name: startup
#+begin_src elisp :results silent
  (setq org-src-preserve-identation t)

  (defun sql-format ()
    "Run sqlfluff on the current file."
    (call-process-shell-command (concat "sqlfluff fix --dialect snowflake" buffer-file-name)
                            nil
                            nil))

  (add-hook 'org-babel-post-tangle-hook (lambda () (when (string-match-p ".sql\\" (buffer-file-name))
                                                   (beginning-of-buffer)
                                                   (flush-lines "-- :end:")
                                           (flush-lines "^$")
                                           (save-buffer)
                                           (sql-format))))
#+end_src

The sql-format function just runs sqlfluff over the file of the current buffer, our team is using Snowflake so that's the dialect I've selected. I want a hook to run this function on the output whenever I tangle an SQL file, so that's what the add-hook block is doing. The reason for the two flush-lines calls is to get rid of any :end: comments (which come up when I want a comment after a :testing: section) and to remove any empty lines (since sqlfluff will handle padding on it's own). This means that I can write the SQL however I like, and then not care about formatting the output. In fact, until I send the PR for review, I never even have to read the actual output of the master file. I can just work in it the whole time.

We can run this code-block from within org-mode with C-c C-c, but it's a bit of a chore to do this each time I enter the file. There is a better way: with local variables I can execute this code-block when I enter the file. This is also the reason for #+name: startup at the top:

# Local Variables:
# org-confirm-babel-evaluate: nil
# eval: (progn (org-babel-goto-named-src-block "startup") (org-babel-execute-src-block))
# End:

Chuck this at the end of the org file, and now every time you open it that startup block will run.

That's basically it. There's a few caveats: obviously, once this gets merged into the version control then any changes won't synchronise with the org file. This could be fixed with link comments and org-babel-detangle, but that goes against the making sure no one else in the team ever needs to interact with org. This workflow is supposed to be for myself only, and not interrupt anyone else in their reading or writing of the code. This does mean that once it starts going through review, and especially once any code is actually merged, the master file is essentially useless as it won't stay up to date. But that's OK, because usually any changes that are made after merge are minor, and I can still keep the org file updated with any information that shouldn't be a file comment. Or, I can export the whole thing as a word document to describe the implementation in plain English to the reviewer. Not sure if that will ever come up, but it's nice to have the option.

-1:-- Mixing Code Styles with org-babel (Post Jakub Nowak)--L0--C0--2025-07-02T00:00:00.000Z

Irreal: A Journey To Vanilla Emacs

There are many, many posts on Irreal about the journey that people make from the hinterlands to Emacs. This post is about the journey from uber-distributions like Spacemacs and Doom to plain old vanilla Emacs. Irreal doesn’t hold any position, political or otherwise, on what distribution you should use. We’re content to let each user find his or her own Nirvana.

Still, it’s true that many converts come first to Doom or Spacemacs because they find it a bit more familiar and comfortable. Plenty of those people find a permanent home in one of those distributions and live happily ever after. Others, though, still hear the siren song of traditional Emacs and eventually complete their journey by adopting vanilla Emacs.

Donovan Ratefison has a post that recounts his own journey. He has a nice metaphor involving drinking tea. When he first started drinking tea he liked it with lots of sugar. Eventually he discovered that the sugar was masking the true taste and aroma of the tea. The raw tea was, perhaps, an acquired taste but once acquired it opened up a whole new world or tastes and sensations to him.

So it was with Emacs. Ratefison tried both Spacemacs and Doom and liked them both but vanilla Emacs kept calling to him. He like may others decided that he wanted a minimal configuration where he understood everything and there were no packages that he didn’t actually need and use.

He includes a copy of his init.el and it is, indeed, simple. He loads only a few packages. He will probably find the need for additional packages as his Emacs use matures. That, at least, has been my experience but it seems that he has found, as with tea, that unsweetened Emacs is better.

-1:-- A Journey To Vanilla Emacs (Post Irreal)--L0--C0--2025-07-01T15:48:47.000Z

Anand Tamariya: Plan 9 Keybindings

Plan 9 OS user interface (UI) is mouse oriented. However, thanks to Common User Access (CUA) specification, we have come to expect keyboard shortcuts to work for certain repeated actions. e.g. Ctrl+x, Ctrl+c and Ctrl+v for cut, copy and paste respectively. This is an attempt to introduce the same in Plan 9.

 

Code (Work in Progress)


 References

  1. Typing Unicode Characters (see also man keyboard(6))
     
-1:-- Plan 9 Keybindings (Post Anand Tamariya)--L0--C0--2025-07-01T04:04:00.000Z

Sacha Chua: 2025-06-30 Emacs news

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

View org source for this post

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

-1:-- 2025-06-30 Emacs news (Post Sacha Chua)--L0--C0--2025-06-30T19:26:59.000Z

Charles Choi: Take Two: Eshell

This is a contribution to the Emacs Carnival 2025-06: Take Two collection of posts on Christian Tietze’s blog.

My first take with Eshell many years back did not leave a good impression. My early expectations was that it should act like any other shell, only to be unpleasantly surprised by it. It took a long time for me to warm up to Eshell. Upon reflection, it was because I wasn’t ready for it.

Now Eshell is an inseparable part of my Emacs experience. Paradoxically though, I find little occasion to use Eshell in the same way I’ve used shells in the past. Much of what I used to use the shell for, I do today with Emacs modes instead.

It was not always this way. Like so many users who started Unix computing in the 80’s, I started with the command line (for myself ksh and later bash), studiously internalizing command line tools and their arcane syntaxes to support file management, running programs, and systems administration. Overwhelmingly though, it was file management and running programs that I did through the command line.

For file management these days, Dired is my interface of choice. It is simply the more elegant tool for the job.

Case in point: renaming a bunch of files in a directory to arbitrary names. With a shell, my standard approach would have been to run some variant of ls -1 > foo.sh, edit foo.sh to insert mv on each line to a renamed target file, chmod u+x foo.sh to be executable, run foo.sh, delete foo.sh and call it a day.

With Dired, the above steps are replaced as such: make the buffer writable (via Wdired). Change the target filenames to a desired result, then commit the changes. This is even easier if there is a pattern you can use a regexp for. Unsurprisingly, all the basic file management operations that you would want are supported by Dired. Add Casual Dired to help make these operations discoverable and you’ll soon consider how quaint using mv, cp, rm, and ls are.

Running a simple command on a file? Again, you can do that with Dired. Move the point to the Dired line containing the file. Type “!” and enter a command in the prompt. You can optionally reference the filename in the command with the “?” character if you want to redirect its output to a file (e.g. cmd ? > outfile).

Taking file management and running simple commands out of what you would use a shell for, what’s left over? Source code management (SCM)?

Like for many others, git is my goto tool for SCM. Much has been said about the terrible user experience of the git command line and for years I suffered with its design decisions. Thankfully for Emacs users, there is Magit and VC mode which both provide a saner experience for it.

Running a Makefile? Use Emacs compile, which provides command completion for the targets defined in the Makefile and error/warning navigation if needed. (Try out Casual Make for editing Makefiles in Emacs.)

Do you need to run a command with arcane arguments repeatedly? Again, Makefile + compile to the rescue where it can be used as a task runner for that command.

Need robust terminal emulation? Eshell will fail you here and you are likely better off using a dedicated terminal emulator app, separate from Emacs. That said, I’ve found the built-in term to be good enough for many curses-style utilities.

Mining data files with piped commands? Eshell is a power tool here: used wrongly and you could figuratively cut off a limb. But used cleverly, Eshell offers great value. More on this later.

So with all these use cases described, why even use Eshell?

Using Eshell becomes a win only if you know Elisp. With that, you can think of Eshell as an Elisp REPL that can secondarily act as a shell.

Let’s not mince words. For many users this is a high bar, as it takes into account a lot of prerequisite knowledge. That said, learning basic Elisp is trivial for readers already conversant with programming and manageable for those that aren’t. Personally, I was very late (decades!) in taking the time to learn Elisp because I didn’t see the reward in it and so, prioritized accordingly. That hesitancy turned out to be unfounded. Elisp is easy to learn and the reward (or punishment) for knowing it is having the ability to really use the full capabilities of Emacs. (Know Python? Here’s a cheatsheat for Elisp that I wrote, hop in, the water’s fine…)

Once gained, bringing Elisp knowledge to using Eshell gives you a command line super power: the ability to improvise shell commands with Elisp functions.

Consider the following Eshell example where we wish to apply a command on a set of png files in a directory. Let’s start with a simple example that simply echoes filenames with suffix .png.

$ for i in *.png { echo $i }

In Eshell, you can replace echo $i with an Elisp expression.

$ for i in *.png { (format "%s" i) }

You can also mix shell commands and Elisp via dollar ($) expansion. Note that $ expansion in Eshell is different from other shells. A deeper reading is on it is recommended to avoid confusion with what works in conventional shells.

$ for i in *.png { echo $(format "%s" i) }

If you have ImageMagick, installed, you can use the convert utility to change this to a jpeg file, using the Elisp file name components functions to do the work of getting a desired target name.

$ for i in *.png { convert $i $(file-name-with-extension i "jpg") }

Contrast this with the equivalent expression in Bash which I consider to have much more forgettable syntax.

$ for i in *.png; do convert $i ${i/.png/.jpg}; done

Another significance with mixing shell commands and Elisp together is the ability to incorporate Emacs buffers into an improvised workflow. Shell commands are typically designed to work with data organized as files. In contrast, Elisp (rather, Emacs) treats files as a persisted, secondary form of data. Instead, Buffers are the primary data type in Emacs.

Recall the Dired naming example above? Consider the added steps due to using a temporary file foo.sh compared to using a writeable Dired buffer.

Eshell shifts you into thinking differently about working with a prompt, where instead of thinking of output being only a file or (stdout, stderr), the output could also be an Emacs buffer.

With that change in thinking, Eshell is better thought of as a “prompt” interface for Emacs which in turn serves as the interface to all the command line utilities made available to it. In this light, a whole swath of common shell workflows get replaced by Emacs.

Workflow Shell Eshell
File management Orchestrate cd, ls, cp, rm, mv Run dired {optional path}
View a file Run more or less Run view-file
SCM with git Run git command(s) Run magit {optional path}
View man page Run man Run Emacs man
Run Makefile Run make Run make & to send output to a compile buffer
Remote login Run ssh Run Eshell Tramp
Edit a file Run $EDITOR Run find-file
Manage processes Run htop Run proced
Search for pattern in a file Run grep Run Eshell grep to send output to a compile buffer
Locate a file Run locate Run Eshell locate to send output to locate buffer

Suffice to say, Eshell reinforces and rewards keeping Emacs users only in Emacs.

Another common shell workflow is using grep with pipes (|) for data mining files. For example:

$ grep {pat1} {file} | grep -v {pat2} | grep {pat3} >outfile

Using this pattern indiscriminately in Eshell is a recipe for unpleasant surprise if the file to be mined is large. This is because Eshell will ingest said file into Emacs as part of the pipeline processing. This will be slow. To avoid this, you can prefix each | with an * as follows:

$ grep {pat1} {file} *| grep -v {pat2} *| grep {pat3} >outfile

Why store a result in a file (outfile) that you have to open to see it and may not care to keep around? Here Eshell gives you the ability to redirect to a buffer.

$ grep {pat1} {file} *| grep -v {pat2} *| grep {pat3} >#<buffer "*my grep results*">

Note that Eshell supports creating/overwriting (>) and appending (>>) for redirection. Use > with care when working with an existing buffer.

What about piping shell command output to an Elisp function? At the time of this writing, you really can’t though according to this Reddit thread, this may change in the future.

If you are used to workflows that bleed out megabytes of data to stdout, you really shouldn’t use Eshell. But with some cleverness, Eshell can help you get insights to your data faster by leveraging all the tools Emacs can offer for this (occur, highlighting, grep, etc.).

Closing Thoughts

IMHO, Eshell is best thought primarily as a prompt for Elisp/Emacs functionality with the secondary benefit of running command line utilities with a “shell-like” experience. It should not be thought of as a “drop-in” replacement for an actual terminal shell. If full terminal emulation is desired, my guidance is to run a dedicated terminal emulator outside of Emacs.

To get the full benefit of using Eshell, you really need to be conversant with Elisp. That said, Eshell is actually a great way to start learning Elisp as it provides you a prompt interface (REPL) in much the same way that other languages like Python, JavaScript, Ruby, Java, Swift, etc do.1 Especially if you are comfortable with programming, take a day to learn Elisp. You won’t regret it.

Bringing Eshell into my Emacs journey has been amazing and I’m happy to have given it a second take.

Thanks to all the contributors of Eshell.

Footnotes

1 To clarify, these languages adopted the idea of a REPL from Lisp development which Elisp is descended from.

-1:-- Take Two: Eshell (Post Charles Choi)--L0--C0--2025-06-30T16:30:00.000Z

Marcin Borkowski: Interacting with an external process via stdin and stdout

For a project I’ve been recently working on, I needed Emacs to interact with an external process, sending things to it via its stdin and receiving responses via its stdout. I never did anything like that before, so I started with checking my options.
-1:-- Interacting with an external process via stdin and stdout (Post Marcin Borkowski)--L0--C0--2025-06-30T15:46:19.000Z

Irreal: Fold And Focus Comes To Markdown

Flandrew likes Org mode but he wanted to improve his workflow with it a bit. So he wrote some code that he called Fold and Focus—Focused Navigation. The idea was that when in Org mode a single key would move to the next or previous subtree, position that subtree at the same place on the screen, and have everything else folded so that he could concentrate on the current subtree.

That worked out pretty well but after a while wanted to bring the same sort of functionality to Elisp buffers, which he did. Recently, while working in a Markdown buffer, he automatically tried to navigate in the same way but, of course, it didn’t work. So he did the natural thing and ported focused navigation to work in Markdown buffers.

I don’t use Markdown so I have no use for this functionality but it seems like a really useful addition to your toolkit if you do. Take a look at flandrew’s posts. He has animations that show how it works in all three environments. If the project seems interesting to you, take a look at the project page.

-1:-- Fold And Focus Comes To Markdown (Post Irreal)--L0--C0--2025-06-30T15:36:26.000Z

Greg Newman: Emacs Carnival 2025-07: Writing Experience

It is month two of the Emacs Carnival and I am hosting July with the topic of "Writing Experience".

This is such a wide ranging topic but one I know the Emacs community and beyond loves to discuss. It usually produces some interesting posts and workflows. Whether you are writing novels, zettlekasten or simply taking notes, what is your writing experience in Emacs. Denote, Howm, Orgmode, Org-Roam, Journal, it doesn't matter. How does your writing workflow in Emacs translate to your mobile devices? What are your pain points? What are your successes? Lay it all out there.

I'm hoping the July topic produces new ideas and inspiration. Have fun with it.

What’s a Blog Carnival, Again?

(Copied from Christian Tietze introductory post)

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!

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

We are still needing more hosts so please volunteer if you want to host one of the months by adding your name to the EmacsWiki page.

Submissions

I'll aggregate all the submissions for July in this post. I do not yet have a contact form on this new site so you can email me at greg@gregnewman.org or DM me on Mastodon with a link to your submission.

-1:-- Emacs Carnival 2025-07: Writing Experience (Post Greg Newman)--L0--C0--2025-06-30T00:00:00.000Z

Donovan R.: My 2025 Note-taking workflow

Taking digital notes is something I do a lot. Not because I particularly enjoy writing all the time or because I’m especially good at it, but because I want to understand myself better and gain a little more control over my life.
Taking notes is like building a time machine that tracks how my mind works.
I do it also because it’s fun to rediscover my old self through my notes.
Sometimes I made silly decisions, other times I appeared wiser than I do today.
Sometimes I find my belief swinging from one extreme to the opposite like a pendulum.

It’s June 2025, and I want to share with you what kind of notes I take and an overview of my current workflow for note-taking.

As for today, I’m using both Logseq and Emacs to take notes.

Logseq

Logseq still holds most of my old notes, and I rely heavily on it for capturing media-related content (videos, Excalidraw diagrams, and more).

What notes do I take in Logseq?

Quotes

I capture quotes from other people that resonates with me, as well as my own insights.
I use the Logesq banner plugin to randomly display these qutes in the banner.
I love this mechanism of resurfacing quotes.

Contacts and People info

When I interact with someone, I note the interaction in the journal page, using their name as a tag (which also creates a page in Logseq).
Later, I can fill in more details about the person, such as email addresses or phone numbers on their page.
From their page, I can quickly check every interaction in the backlink section.

Youtube links

I use the youtube timestamps when learning something new or bookmarking some interesting part of a video.
This allows me to quickly jump to the relevant part later.

Excalidraw

I use the build-in excalidraw to brainstorm or create presentations.
I maintain a vertical calendar in Excalidraw where I jot down some notes about what I might want to do each month.
I use the green color to separate what’s done from the rest.
I find this gives me a convenient overview of the year.

Presentation

Logseq is excellent at structuring ideas with bullet points.
The Bullet Threading plugin combined with the built-in zoom in / out feature of Logseq makes it a very good tool for presentation.

Emacs

I rely extensively on Org mode for note-taking on Emacs.

What notes do I take in Emacs?

Long-form notes

Insights, Technical documentation, Blog Posts, Journals, etc, when I’m in the mood for a long-form writing, I switch to Denote and start typing.
I don’t use many of Denote’s features. I just run M-x denote , fill in the title and tag, and start writing.
It’s simple and gets the job done!

Project

I have a folder named project where I put org file for each project I’m working on.
I don’t use a specific structure yet. Just bullet points for todos and scheduled items with notes for each project task.
The org files are linked to Org agenda for convenience.

Time tracker

Similarly, I have org files for tasks. For example work.org for work-related tasks.
I track the time I’m working on tasks using org-timer-set-timer combined with org-clock-in and org-clock-out.
And I use Org capture template for convenience.

Event Scheduling and Agenda

As above, I have an agenda.org file that is linked to my Org agenda for all my scheduled event.
Using of course org-schedule for scheduling a task.

Synchronization

Wether I’m using Logseq or Emacs, all my notes are linked to a private Git repository. This setup ensures that my data is always synchronized and backed up.
It’s simple and reliable.

In summary

This is my current workflow. Nothing set in stone, just how I do things right now.
Like everything else, it changes and evolves over time as I learn and grow.
Who know what it’ll look like in the next month or next year?
For now, this is what works for me.

-1:-- My 2025 Note-taking workflow (Post Donovan R.)--L0--C0--2025-06-29T19:40:48.000Z

Irreal: How To Stop Emacs From Asking For Aliases

Today (Saturday) appears to be a slow day on the technical Internet or perhaps everyone is hiding out from the AI apocalypse. Whatever the reason, I can’t find anything to write about that’s up to the refined standards of Irreal readers.

I offer, therefore, this short post on a niche problem that most of you probably don’t have but that may be useful to those that do. If you’re an Eshell user you may or may not know that if you repeatedly try to run a command that doesn’t exist, Eshell will ask you if you want to set an alias. Over at the Emacs subreddit, Both_Confidence_4147 complains about this and asks how to stop it.

Presumably, Eshell thinks you’re repeatedly mistyping the command name—as in the old time common error or typing “moer” for “more” and similar errors—and is offering you a way to avoid the error going forward. Eshell will ask you for an alias after 3 failed commands but, of course, this is configurable. You can also arrange to disable the prompt completely as explained in this comment to Both_Confidence_4147’s post.

Again, this is a niche problem unless, of course, you’re the own who’s experiencing it. If you are, now you know how to fix things.

-1:-- How To Stop Emacs From Asking For Aliases (Post Irreal)--L0--C0--2025-06-29T16:11:39.000Z

Jakub Nowak: Goodbye LanguageTool, Hello Harper

I found out about Harper the other day. Ever since I heard about it, I've been itching to set it up in my config and get rid of the horrible LanguageTool setup I had going. Now that I've got it working, I've gotta say I'm quite impressed. My previous LanguageTool setup was not smooth at all, and definitely not as fast as Harper is. I'm not sure how anyone else is doing spellchecking in Emacs (and particularly in org-mode) at the moment, but I recommend anyone using Emacs for most of their writing to give it a shot.

I won't embarrass myself with a comparison of my LanguageTool config. Just know that it was slow and terrible. Copied from the Harper docs, this is what my config looks like:

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

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

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

Some caveats: it doesn't look like Harper has support for org-mode specifically yet, but it seems to generally work OK despite that. I have however noticed that it doesn't lint property tags, so for example your #+title: won't be spellchecked. Another minorly annoying little niggle is that straight seems to misbehave with eglot, or eldoc more specifically, printing eldoc error: (invalid-function incf) in the minibuffer instead of an actual error message. I was using lsp-mode before this - although I'm beginning to like the eglot interface more and more as I write this, so I think I may swap over to it where possible - so I never realised this issue. Per that GitHub issue link, (use-package eldoc :straight (:type built-in)) seems to be the solution.

The only thing that's missing, and to be fair LanguageTool doesn't implement this either (in fact to my knowledge nothing implements this, I don't even think there's a Word plugin), is text style metrics analysis. Every so often I get the impulse to try to untangle what the source code for that website is doing, and then write a simple elisp wrapper for it, but I always get sidetracked before I start. Maybe now is the time.

Addendum

I've discovered another few quirks in setting this up (mostly to do with eglot and company), so I figured I'd go back to update this post and catalogue what I've had to do for future reference.

eglot overwrites company-backends by default. This can be disabled with (setq eglot-stay-out-of '(company)). Figuring this out also convinced me to finally fix company-ispell, which I could not get working last time I tried. To get it working with both Ispell and flyspell it seems to need to following code-block:

(if (file-exists-p "/usr/bin/hunspell")
    (progn
      (eval-after-load "ispell"
        '(progn
         (setq ispell-program-name "hunspell"
               ispell-dictionary   "en_AU"
               ispell-alternate-dictionary (file-truename (concat user-emacs-directory "en_AU.dict"))
               ispell-local-dictionary-alist '(("en_AU" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_AU,en_AU-med") nil utf-8)))
         (defun ispell-get-coding-system () 'utf-8)))
      (eval-after-load "flyspell"
        '(progn
         (setq ispell-program-name "hunspell"
               ispell-dictionary   "en_AU"
               ispell-alternate-dictionary (file-truename (concat user-emacs-directory "en_AU.dict"))
               ispell-local-dictionary-alist '(("en_AU" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_AU,en_AU-med") nil utf-8)))
         (defun ispell-get-coding-system () 'utf-8)))))

I'm using Hunspell here - if you don't also eval-after-load for flyspell, then ispell-program-name gets overwritten (in my case, to enchant-2, which wasn't working). The alternate dictionary needs to be created, Hunspell doesn't ship a plaintext dictionary. You can make it by going to /usr/share/hunspell/ and running unmunch en_AU.dic en_AU.aff >> /~//.emacs.d/en_AU.dict. You'll notice that I'm using file-truename to turn that user Emacs directory into an absolute path - for some reason, it wasn't working with just the relative path.

-1:-- Goodbye LanguageTool, Hello Harper (Post Jakub Nowak)--L0--C0--2025-06-29T00:00:00.000Z

Greg Newman: Emacs Take Two (Eglot)

This post is my contribution to the Emacs Carnival June 2025 topic "Take Two" set by Christian Tietze. This is also a cross post for the IndieWeb Carnival: Take Two which was the original inspiration behind the Emacs Carnival. You can get more information about the Emacs Carnival on the wiki.

I started using Emacs about 20 years ago. From the start I was heavily involved in the Emacs IRC channel and the Org mode mailing list. I contributed the refresh of the Org mode logo to Dr. Carsten Dominik and the community as a small thank you for such a great package. All my notes were stored in Org mode files, along with my task lists. For development I used Elpy for Python and Webmode for anything HTML and Javascript. Elpy was good enough for me back then. Webmode was just ok but had a lot of problems with javascript. I jumped over to LSP mode pretty early and that solved a lot of problems with Python but it was a pain to configure and broke at the most inconvenient times. I was frustrated a lot of the time I was trying to work.

Around 2015 I was introduced to PyCharm by a client. The debugger was fantastic and hooked me quickly. It replaced a lot of what I was using Emacs for. I still used Emacs but jumped back and forth between the two (a lot of things are just easier in Emacs). Around the same time I started working with React and I could never get anything working well in Emacs, but PyCharm just worked. So I stayed with PyCharm for many years (mostly). All my notes were still stored in Org mode files (and most still are). I still have notes in org files from nearly 20 years ago. I can't say that about any other notes apps.

When Eglot started gaining momentum I decided to see what it could do. I was pleasantly surprised but it did not fully bring me back. It was still easier to just use PyCharm. PyCharm is a great IDE but I find IDEs too opinionated. I always had the itch to return to Emacs for all my development needs. A year or two ago I decided to give Eglot another try. It had matured - or perhaps I had. I played around with the available options for LSP and found Pyright to be very good. I was able to get my Emacs config to provide me with most of what PyCharm was handling. When I finally upgraded Emacs from 29 to 30, I stopped using PyCharm. With Eglot, the current options for Language Servers, and Tree-sitter I now have a stable environment for Python/Django work as well as React. I'm now comfortable with my environment.

Currently, I'm using Eglot with Basedpyright Language Server and Ruff for linting. For React/Javascript I'm using the Typescript Language Server. Below is my current Eglot config. It has taken many iterations to get it where I'm happy with it. I still need to add JS linting to it but for now that's mostly covered in my pre-commit hooks.

  (use-package eglot
    :straight t
    :ensure t
    :defer t
    :custom
    ;; Optimize performance
    (eglot-send-changes-idle-time 0.5)
    (eglot-extend-to-xref t)
    ;; Configure language servers
    (eglot-server-programs
     '((python-ts-mode . ("basedpyright-langserver" "--stdio" 
                          :initializationOptions (:basedpyright (:plugins (
                             :ruff (:enabled t
                                   :lineLength 88
                                   :exclude ["E501"]  ; Disable line length warnings
                                   :select ["E", "F", "I", "UP"])  ; Enable specific rule families
                             :pycodestyle (:enabled nil)  ; Disable other linters since we're using ruff
                             :pyflakes (:enabled nil)
                             :pylint (:enabled nil)
                             :rope_completion (:enabled t)
                             :autopep8 (:enabled nil))))))
       ((js-ts-mode typescript-ts-mode tsx-ts-mode) .
        ("typescript-language-server" "--stdio"))))
    :config
    (setq-default
       eglot-workspace-configuration
       '(:basedpyright (
           :typeCheckingMode "off"
         )
         :basedpyright.analysis (
           :diagnosticSeverityOverrides (
             :reportUnusedCallResult "none"
           )
           :inlayHints (
             :callArgumentNames :json-false
           )
         )))
    :bind (:map eglot-mode-map
                ("C-c l a" . eglot-code-actions)
                ("C-c l r" . eglot-rename)
                ("C-c l f" . eglot-format)
                ("C-c l d" . eldoc)
                ("C-c l o" . eglot-code-action-organize-imports)
                ("C-c l h" . eglot-inlay-hints-mode)
                ("C-c l q" . eglot-shutdown-all))
    :hook ((python-ts-mode . eglot-ensure)
           (js-ts-mode . eglot-ensure)
           (typescript-ts-mode . eglot-ensure)
           (tsx-ts-mode . eglot-ensure)
           ;; Python-specific settings
           (python-ts-mode . (lambda ()
                               (setq-local indent-tabs-mode nil
                                           tab-width 4
                                           python-indent-offset 4)
                               (superword-mode 1)
                               (hs-minor-mode 1)
                               (set-fill-column 88)
                               (display-line-numbers-mode 1)))))
-1:-- Emacs Take Two (Eglot) (Post Greg Newman)--L0--C0--2025-06-29T00:00:00.000Z

Irreal: More Emacs Startup Time

Because I know you guys never stop jonesing for another episode of the endless debate on Emacs startup time, here’s yet another post to feed your addiction. Over at the Emacs subreddit, prothtuahel complains that Emacs is taking 3 seconds to load 412 packages1 when all he wanted to do was add a TODO to a file.

There’s so much to say. First, why are you restarting Emacs for each file you want to edit? I know it can be disorienting for new users but when you’re using Emacs, you should leave it running all the time and not restart it every time you want to edit a file.

Second, if you must restart it for each file, start Emacs in server mode and use emacsclient to invoke it for each editing session. Emacsclient will start instantly because Emacs is already waiting in the background. But really, see the first point.

Third, you’re complaining about taking 3 seconds to load 412 packages? That’s actually pretty fast, but see the first and second points.

The comments mostly concentrate on the three points above but, of course, someone points out that you can speed things up with lazy loading. To me, the most cogent argument is that who cares?. If, as you should, you only load Emacs a few times a month or year, who cares how long it takes to load? I always get pushback when I write that but unless you’re one of the few people with special needs that require starting Emacs often, it remains true.

Footnotes:

1

Sadly, once again reddit has chosen to delete the post because reasons but the headline and comments are still there. Someone really needs to sit the reddit mods down and have a polite but firm discussion.

-1:-- More Emacs Startup Time (Post Irreal)--L0--C0--2025-06-28T15:28:09.000Z

Peter Tillemans: Consolidating Secrets in Pass

Background

Like a lot of people I've had a long history managing passwords and secrets over the years. From a little black book, over an Excel sheet, using a GPG encoded secrets file (works really well with Emacs gpg support), 1password (till they racked up their prices), lastpass (till they got bought by the Evil LogMeIn Corp), KeepassXC and lately pass.

I was perfectly happy with KeepassXC for a very long time, except for the command line integration. So I kept ending up with passwords in .envrc files in folders and excluded in the global .gitignore to avoid too many red cheeks. While this does keep secrets out of harms way mostly, it kept nagging that I had them in plain text in those files. In theory there is keepassxc-cli to query the passwords from the command line, but let's say the experience does not spark joy. It has no easy way to cache the password between calls and it is optimized for interactive use. (AFAICT, just the giant size of the command to type gives me dread).

Some day I stumbled over pass and found that after setup I could just pass snamellit/website to get the password on stdout. I wrote about the setup and emacs integration in a previous post. Since it leverage gpg, password caching is handled by the gpg-agent and my .envrc files quickly were purged of blasphemous secrets, replace by pure bliss:

export MY_SECRET=$(pass my/secret)
export OTHER_SECRET=$(pass other/secret)

similarly in emacs I can consistently get my passwords and related info with:

(org-gcal-client-id (auth-source-pass-get 'secret "snamellit/org-gcal-client"))
(org-gcal-client-secret (auth-source-pass-get "id"
"snamellit/org-gcal-client"))

When needed the gpg-agent will launch the appropriate pin-entry program whether in terminal or in the GUI and the caching will not force me to login several times when entering the folder.

So I ended up with my interactive use covered by KeepassXC and automated use by pass.

However, after some time I ended up with hundreds of secrets in KeepassXC, hundreds in pass, it is not always clear whether use is interactive or automated so confusion and duplication starts and things become harder to manage. In addition KeepassXC was using historically Dropbox to make it available on all my devices, recently migrated to Nextcloud, which has issues with dealing with conflicts which occasionally bite me in the behind. On the other hand pass secrets are stored encrypted in git where conflict punch you in the face. I prefer the latter. And started contemplating whether to move everything to pass.

Thanks to the encouragement of SummerEmacs, one of the more enthusiastic SystemCrafters, ensuring the great experience in browsers and iOS mobile devices I had no more excuses to keep postponing it.

Preparation

I started out with keeping my pass passwords as part of my dotfiles. This was convenient when they were few. However this is weird so this will attract weirdness when configuring all integrations I'll need.

Also pass supports a git command to manage the password-store with git which is not really useful when it is part of something else. So the first order of the day is to move all secrets to a separate repository and update the dotfiles to check for presence and clone the repo if missing (and do a gently pull when it is). A quick visit to each of the machines in my machine park to apply this change. Everything still seems to be working.

Migration of the KeepassXC data

I used the pass-import tool which adds in import command to pass which supports a crazy amount of password managers, including keepassxc. For keepassxc it need the pykeepass. If you're running on Arch, everything is a yay -S away. However on Ubuntu and its derivatives it is the usual slog we start to get accustomed to. It's all in the pass-import README , note that on Ubunty the pykeepass library is available with apt install python3-pykeepass.

Once it is installed I tried a dry run (with the -d flag) to see if basic functionality is working

pass import -a -d keepassxc ~/Nextcloud/Apps/Keepassxc/Passwords.kdbx
Password for /home/pti/Nextcloud/Apps/Keepassxc/Passwords.kdbx:
  w  Data would be imported from keepassxc to pass
  .   Passwords imported from: /home/pti/Nextcloud/Apps/Keepassxc/Passwords.kdbx
  .   Passwords exported to: /home/pti/.password-store
  .   Number of password imported: 2035
  .   All data imported
  w  Weak password detected: eDGQqipE might be weak.  Score 2 (100000001 guesses).  This estimate is based on the sequence eDGQqipE(bruteforce)
  w  Weak password detected: eDGQqipE might be weak.  Score 2 (100000001 guesses).  This estimate is based on the sequence eDGQqipE(bruteforce)
  w  Weak password detected: eDGQqipE might be weak.  Score 2 (100000001 guesses).  This estimate is based on the sequence eDGQqipE(bruteforce)
...  large list of names of secrets

This asks for the password of the Keepass file and some remarks it has.

This all looks reasonable. So we can try the import. Since the password-store is a git repo no real damage can be done to it (as it is safely pushed somewhere else where the import tool cannot touch it) and any damage done can be reverted....

Now is a good time to check if the mooring lines of your laptop are properly secured as encrypting all the secrets will spin up the propellors if the number is large enough.

I run it again without the -d flag and after several minutes the noise dies down and I am left with a lot of additional folders in my ~/.password-store which match the grouping in KeepassXC. The files contain the secrets and the expected metadata. This looks good so I add/commit the things to complete the level.

Integration with iOS for my iPhone

Let's start with the most scary one : the iPhone.

Upon recommendation I had installed passforios which needs to be configured.

Configuring the host, repo and username to use for the git repository is straightforward enough.

I always use ssh to access my repos so we need to add an ssh keypair for this purpose. There is no support to generate key-pairs in passforios for reasons, so I have to do it externally and upload the key. A quick ssh-keygen , uploading the public key to the forge, allowing access to the repo and if I can get the private key on my phone we can access the repo.

passforios has a nice feature to load ascii armored keys via a QR code. A bit digging surfaced the asc-key-to-qr-code-gif tool which was made for this specific purpose. The ssh key is already in the appropriate format so this can be directly converted

./asc-to-gif.sh ~/.ssh/id-passforios ssh-pub.gif
display ssh-pub.gif

Then go to the repository settings, press the circled i on the SSH Key button, select the ASCII-Armor Key and click to scan the QR code. Point the camera to the QR code on the screen and it should appear in the key field in the app.

We have to repeat this 2 more times to get the private and public key for the password-store into the app. First exporting the keys

gpg --export -a 1234ABCD >gpg.pub
gpg --export-secret-key -a 1234ABCD >gpg.key

converting to a gif, displaying them and scanning them in *Settings -> PGP Key -> ASCII-Armor Key in the respective fields.

If, after synching, you go now to the Passwords you should be greeted with a listing of all folders and keys and the secrets should be visible if made visible by tapping the eye icon.

I needed to enable passforios as a source for autofill : Settings -> Passwords -> Autofill Passwords and slide the toggle for Pass. I also disabled the toggle for Strongbox which I was using for integration with the Keepass database.

Now I see the option to select the secrets from the passforios app. It does not narrow down to the right key, but that is a problem for future me.

Ok, the hard part is done. Or at least the most risky part, ... in my eyes... whatever. Moving on...

Integration with FireFox

Checking at the bottom of the pass website we find that passff is the good stuff for integration with FireFox. From previous adventures with KeepassXC and NativeMessaging I assumed there had to be a host part to be installed too.

Indeed we are directed to the passff-host github repo to get an install-script which generates the native messaging json for the different browsers and a small executable python script which contains remarkable clean and no-dependency code. Similarly the install script is straightforward. I do not understand why it support half a dozen browser, mostly chrome based as for the life of me I cannot find an extension which uses this host program. So either I need bigger glasses or there is some knowledge beyond my grasp.

Running the installer, installing the extension, restarting firefox for good luck and the extension appears and offers passwords on the sites I try.

Out of curiosity I check the configuration in ~.mozilla/native-messaging :

pti@tuxedo ~> ls .mozilla/native-messaging-hosts/
org.keepassxc.keepassxc_browser.json  passff.json  passff.py*
pti@tuxedo ~> cat .mozilla/native-messaging-hosts/passff.json
{
  "name": "passff",
  "description": "Host for communicating with zx2c4 pass",
  "path": "/home/pti/.mozilla/native-messaging-hosts/passff.py",
  "type": "stdio",
  "allowed_extensions": [ "passff@invicem.pro" ]
}
pti@tuxedo ~> cat .mozilla/native-messaging-hosts/passff.py
#!/usr/bin/python3
"""
    Host application of the browser extension PassFF
    that wraps around the zx2c4 pass script.
"""

import json
...

Nothing out of the ordinary, the passff.py python is the same as in the repo. My old keepassxc extension support is still there.

Firefox is installed natively on this machine, not with a flatpak which I assume will come with its own challenges.

Chromium Support

Time to tackle the Chrome family. Chrome is required to put food on the table so we have to get that going eventually. But Chrome is distributed as a flatpak (or a snap but I am NOT going to deal with that), and I can install Chromium natively, and apparently native installs are MUCH better supported than the versions in wrappers so let's start with that one first.

From the pass website we find that browserpass is the way to go for the chrome family. The browser extension installs from the usual places without drama and starts promptly complaining it cannot find the native host to talk to.

The native host in question is from the browsaerpass-native sister repo . As usual for all distro's there are packages ready to install but because Ubuntu-derivative I can compile from source. Downloading the source for version 3.1.0 from the releases page. Again this repo refers to all browsers including firefox although I cannot for the life of me find a Firefox Extension supporting this host app.

Then building and installing timelapse :

tar -xzvf ~/Downloads/browserpass-native-3.1.0.tar.gz
cd browserpass-native-3.1.0
ls
less README.md
PREFIX=/usr/local make configure
sudo make PREFIX=/usr/local install
which browserpass

which shows the executable lives at /usr/local/bin/browserpass and this totally went fine the first time (NOT!!!!).

The Makefile has support to install the magic json to enable native messaging for the different browsers.

PREFIX=/usr/local make hosts-chromium-user
PREFIX=/usr/local make hosts-chrome-user

The second invocation is a hail-mary because I already know the Chrome flatpak does not look in the same places and will require some additional finnagling

For now focus on Chromium and check if the configuration looks reasonable:

pti@tuxedo ~> cd .config/chromium/NativeMessagingHosts/
pti@tuxedo ~/.c/c/NativeMessagingHosts> ls
com.github.browserpass.native.json@
pti@tuxedo ~/.c/c/NativeMessagingHosts> cat com.github.browserpass.native.json
{
    "name": "com.github.browserpass.native",
    "description": "Browserpass native component for the Chromium extension",
    "path": "/usr/local/bin/browserpass",
    "type": "stdio",
    "allowed_origins": [
        "chrome-extension://naepdomgkenhinolocfifgehidddafch/",
        "chrome-extension://pjmbgaakjkbhpopmakjoedenlfdmcdgm/",
        "chrome-extension://klfoddkbhleoaabpmiigbmpbjfljimgb/"
    ]
}

Cool, the executable is looked at where it is installed (this is not obvious, don't ask how I know). The rest looks also like how these things should look. Let's try...

The extension settings page is no longer complaining the native host is missing and there are password entries visible. Checking with some website shows the password is injected. yay!.

Level complete, ready for the final boss.

Enabling Chrome Support, now with more Flatpak!

Ok, we have a working chromium support so repo access, host app, native host configuration et al are proven working. We can only focus on jumping over the Flatpak Firewall...

As a good cargo cultist I do a literature study and find that I should

  • find the config location of the flatpak app

  • use flatpak-spawn to spawn the native messaging host app

  • enable D-Bus Session socket access for chrome

  • Package up the calling of the host app in a single script to configure in the json.

    Not necessarily in that order....

For the permission to access D-Bus Session start up flatseal from flathub, navigate to com.google.Chrome and enable the D-Bus Session socket. This should be possible with some additional cursing in the manifest file of Chrome. I cannot find decent reference documentation in a reasonable time, so flatseal it is.

The configuration of the flatpak app is easy too, painful experience seared in my brain that flatpaks look in ~/.var/app/ folder so for Chrome this will be ~/.var/app/com.google.Chrome . From the hail-mary install for chrome done above I know that it just creates a symbolic link to /usr/local/lib/browserpass/hosts/chromium/com.github.browserpass.native so we can start from there. We will have to edit that so copy it. We also need a wrapper to call the native host app

cd ~/.var/app/com.google.Chrome/config/google-chrome/NativeMessagingHosts
cp /usr/local/lib/browserpass/hosts/chromium/com.github.browserpass.native
ec browserpass.sh

Add the content of the wrapper

#!/bin/sh
cd ~
/usr/bin/flatpak-spawn --host /usr/local/bin/browserpass 2>/tmp/browserpass-error.log

I added the optional redirect of stderr to an error logfile because from experience I know nothing ever goes wrong if you enable error reporting beforehand.

chmod +x browserpass.sh
pwd
pwd | wl-copy
ec com.github.browserpass.native.json

Installing the browserpass extension in Chrome after restarting it (I am not superstitious, just careful) and I can bask in the glory of seeing proposals for passwords when trying to log in. Most of the proposals are pretty garbage, but that is a problem for future me.

Conclusion

I have access to my password-store secrets on my phone, my browsers on laptop and desktop, and most importantly Emacs. Narrowing of the proposed secrets is, euhmmm, sub-optimal, but since it is sub-optimal in the same way on all platforms I assume that some TLC in the password-store and cleaning of the migrated secrets will fix that in time.

In the process I gained much more confidence in configuring flatpak apps. I can decommission the keepassxc system including dealing with the sync conflicts (which was admittedly super easy with the merge database feature in KeepassXC). I no longer have to deal with giving the KeepassXC window a place on the desktop and autostarting it.

I am a bit puzzled about the host-apps referring to supporting browsers for which no extensions are available. This probably might warrant some additional investigation.

Big step forward

-1:-- Consolidating Secrets in Pass (Post Peter Tillemans)--L0--C0--2025-06-28T11:15:00.000Z

Emacs APAC: Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, June 28, 2025

This month’s Emacs Asia-Pacific (APAC) virtual meetup is scheduled for Saturday, June 28, 2025 with BigBlueButton and #emacs on Libera Chat IRC. The timing will be 1400 to 1500 IST.

The meetup might get extended by 30 minutes if there is any talk, this page will be updated accordingly.

If you would like to give a demo or talk (maximum 20 minutes) on GNU Emacs or any variant, please contact bhavin192 on Libera Chat with your talk details:

-1:-- Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, June 28, 2025 (Post Emacs APAC)--L0--C0--2025-06-28T04:19:43.000Z

Protesilaos Stavrou: Emacs: beframe version 1.4.0

beframe enables a frame-oriented Emacs workflow where each frame has access only to the list of buffers visited therein. In the interest of brevity, we call buffers that belong to frames “beframed”.

Below are the release notes


Version 1.4.0 on 2025-06-28

This version adds some minor improvements to a stable package.

Per-frame Xref histories

The built-in Xref mechanism is typically used to navigate to the definition of the symbol at point in a programming major mode. The command xref-find-definitions (M-. by default) jumps to the source, while xref-go-back (M-, by default) goes back in the history of visited positions until it reaches the starting point.

When beframe-mode is enabled, each new frame has its own Xref history. This means that finding a definition in one frame does not interfere with the Xref history of another frame.

“Beframed” buffer prompt text is now optional

When beframe-mode is enabled, it sets the standard read-buffer-function to one that filters buffers by frame. Any command that uses that, such as switch-to-buffer (C-x b by default) is thus beframing its completion candidates.

Such prompts get a prefix to inform the user of their behaviour. By default this is [Beframed]: it is subject to the user option beframe-prompt-prefix. Users who do not wish to have any prefix can set this option to nil or an empty string.

When there is a string, it is styled with the face beframe-face-prompt-prefix.

-1:-- Emacs: beframe version 1.4.0 (Post Protesilaos Stavrou)--L0--C0--2025-06-28T00:00:00.000Z

James Dyer: Setting up a C# Language Server (csharp-ls) on an Air-Gapped Windows System

For a little while now, I have been developing C# applications at work and on Windows!, possibly the perfect storm for Emacs, but actually, it copes pretty well, especially with eglot and the LSP server OmniSharp.

But recently my work setup has become even more air-gapped, and unfortunately, I reached a point where I couldn’t get eglot and OmniSharp working together. I gave it a really good try, but I think the mishmash and dependency installation madness finally took a toll.

It has something to do with trying to find MSBuild.exe, but due to my potentially wonky installation, I couldn’t figure out how to detect it. If Visual Studio is installed on the system, there are no problems; however, offline installation, even of the Community edition, is not an easy task.

So, the solution? Well, I thought I would try another C# LSP in the form of csharp-ls. Its deliverable form is a NuGet package, which can be extracted, placed in the relevant location, and then you’re pretty much off and running!

So here is how I did it:

Step 1: Download csharp-ls nuget Components

On a machine with internet access (which fortunately I do have access to):

Download csharp-ls

  • Go to the csharp-ls NuGet page
  • Click “Download package” to get the .nupkg file
  • Rename the file from .nupkg to .zip
  • Extract the contents

Step 2: Install csharp-ls (Airgapped Machine)

  1. Create a directory in your Emacs installation:

          mkdir "c:\path\to\emacs\bin\csharp-ls"
    
  2. Copy the extracted csharp-ls contents to this directory. The key file you need is:

          tools\net9.0\any\CSharpLanguageServer.dll
    

Step 3: Configure Emacs

Add this configuration to your Emacs init file:

;; Configure csharp-ls as the language server for C#
(setq eglot-server-programs
      '((csharp-mode . ("dotnet" "c:/path/to/emacs/bin/csharp-ls/tools/net9.0/any/CSharpLanguageServer.dll"))))

Step 4: Set Up Your C# Project Structure

Basically defining a source code collection as a project in Emacs. This can be a solution file, a source code control directory or as I generally have set up, an empty .project file

Step 5: Set up paths

This may not be needed, but for my own sanity and just in case, absorb the new directory in to the PATH and exec-path variables.

(when (eq system-type 'windows-nt)
  (let ((xPaths
         `(
           ,(expand-file-name "~/bin/csharp-ls/tools/net9.0/any")
           )))
    (setenv "PATH" (concat
                    (mapconcat 'identity xPaths ";")))
    (setq exec-path (append (split-string winPaths ";") xPaths (list "." exec-directory)))))

So it works as I had it with OmniSharp and that really is all I can ask for, lets see how I get on!

-1:-- Setting up a C# Language Server (csharp-ls) on an Air-Gapped Windows System (Post James Dyer)--L0--C0--2025-06-27T21:27:00.000Z

Irreal: Why Did You Switch From Vim To Emacs?

Over at the Emacs subreddit, floofcode asks those who moved from Vim to Emacs to tell their stories. On the one hand, it’s pretty much what you’d expect. The majority of the commenters said it was either Org mode or Magit (or both) that convinced them to move.

The second most popular response was that Evil mode—with or without Doom or Spacemacs—was the selling point. Those users felt that they got all the familiar benefits of Vim along with the power of Emacs.

For me, the most interesting responses where from those users who said that they found Emacs to be faster and easier to use than Vim. That’s a bit of a surprise. It’s received wisdom that Vim is faster and easier to use than Emacs, yet these users found just the opposite.

Oddly, none of these answers apply to me. I migrated from Vim to Emacs before the time of Org mode or Magit. And, although I was a long time Vi/Vim user, I didn’t embrace Evil mode but jumped right into the conventional Emacs milieu.

So what was the attraction? At this point, I don’t really remember. I think it was about the time I started using Lisp and Scheme seriously so it seemed like the Lisp based Emacs was a natural choice. As I’ve written before, I’d made tentative attempts to move to Emacs before but one silly thing or another always stopped me. Suddenly, the universe and I were ready and I moved to Emacs without looking back.

-1:-- Why Did You Switch From Vim To Emacs? (Post Irreal)--L0--C0--2025-06-27T15:21:53.000Z

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

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

Donovan R.: My Emacs journey (2) - Vanilla

My Emacs Journey

I’m from Madagascar, the world’s largest producer of Vanilla.
But today we are not here to discuss the Vanilla spice.
Instead, this is part of the My Emacs Journey series, and I’m going to talk about Vanilla Emacs!

If you’ve read my article on My note taking journey, you know I didn’t actually start with Vanilla Emacs. My journey began with Spacemacs, then moved to Doom Emacs before I finally decided to go Vanilla.
If you are curious about that last transition, then I invite you for another long read where I explain my reasoning in more depth.

-1:-- My Emacs journey (2) - Vanilla (Post Donovan R.)--L0--C0--2025-06-22T12:54:36.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.

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

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

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

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

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

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

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

Into the future

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

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

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

Things I want to try

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

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

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

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

View org source for this post

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

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

Sven Seebeck: Until the Coffee gets Cold

Until the Coffee gets Cold

I have been journaling for quite some time, but consistently daily in one form or the other for a couple of years, since 2019 I guess. Since I am, it seems, chronically undecided about everything, these entries are, much to my own annoyance, either on paper - mostly in Leuchtturm notebooks which had been a personal go-to for a while (there had been various Japanese stationery as well - but more on that in a future post) - or digitally in either Emacs or Obsidian, depending on which part of the year we are.

Don't ask...

To be honest, it's a bit of a mess, but regardless of the medium, I journal usually first thing in the morning. Not having had my journaling time already feels as if there is something missing from the day.

I think that it doesn't take that much of time, but every now and then things get either out of hand in terms of time spent, or I simply don't know what to come up with in the first place. Either way, it seems a good idea to have some sort of time-frame to focus on writing.

I have tried to use timers in the past, but found the idea of having to set a timer, even if it's automated, too annoying and somewhat intrusive. This got extra obvious in those moments when I managed to forget the existence of said timer in the first place and then wonder why on earth an alarm fires off when I was already doing something else.

After reading "Tales From The Cafe" by Toshikazu Kawaguchi (only the second book had been available in the store when I was looking for something to read, so that’s what I bought) I got the idea to steal borrow the concept and simply fill a cup of coffee, which I, given the time of day, would do anyway, and then write until it's cold (or empty for that matter) and then stop.

That's it. One coffee, one entry.

I have been doing this routine of the coffee-timer so to say, only for an admittedly short while - a week or so - but so far it felt far more natural and whole lot less stressful than using a timer or similar. I simply take a cup and write until the cup is either cold or empty. Done! Easy!

And should the time not be enough I can refill or simply choose a larger cup to begin with - stay tuned for the inevitable "my-favourite-pint-sized-coffee-mug" post.

100 Days to Offload 14/100

-1:-- Until the Coffee gets Cold (Post Sven Seebeck)--L0--C0--2025-06-19T14:00:43.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

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!