Irreal: Where Should Your Configuration Live, Redux

A couple of weeks ago, I published a Red Meat Friday article about the proper place for your Emacs configuration to reside. Like many Red Meat Friday posts, it was tongue in cheek but it got quite a bit of traction. Most, but not all, commenters were against putting it n the ~/.entconfig/emacs directory.

Bozhidar Batsov just published a post in favor of using ~/.config/emacs instead of just ~/.emacs or ~/.emacs.d. The argument is that it reduces home directory clutter by putting all the configuration files in a single place. I don’t find that a particularly compelling argument.

The thing is, ~/.emacs.d contains a lot more than just your Emacs configuration files. There’s all kinds of things in there including random packages, ELPA files, all kinds of state data, and plenty of other things. Sure, you could put that all into ~/.config but that seems wrong. The vast majority of it isn’t configuration data.

The XDG standard provides places for all that other data too, but that’s a non-starter for me. Who wants your Emacs data scattered among many different directories?

The commenters to Batsov’s post seem mainly to agree with him but we here in the Irreal bunker are going to continue putting all our Emacs stuff in ~/.emacs.d. It was hard enough giving up ~/.emacs.

Of course, as I and others pointed out in the original Red Meat Friday post, none of this matters very much because you can always use symlinks to put your configuration wherever you want it and make it seem as if it’s in the same old place. But if you do that, you’ve recluttered your home directory and added a level of indirection to your files.

Batsov says, and I agree, that ~/.emacs.d is probably never going away. He’s sorry about that.
I’m not.

-1:-- Where Should Your Configuration Live, Redux (Post Irreal)--L0--C0--2025-01-16T19:14:54.000Z

Sacha Chua: Changing planet.emacslife.com

For the longest time, planet.emacslife.com was generated with the Planet Venus aggregator, which required Python 2.7. My recent blog post about Yay Emacs 8: which-key-replacement-alist had some emojis that the parser couldn't handle, though (unichr() arg not in range(0x10000) (narrow Python build)). I decided to rewrite the aggregator using NodeJS.

Here are some differences between the current implementation and the previous ones:

  • The website shows the last two weeks of posts, since I can filter by date. It should also ignore future-dated posts.
  • The list of feeds on the right side is now sorted by last post date, so it's easier to see active blogs.
  • I can now filter a general feed by a regular expression.
  • I've removed a number of unreachable blogs.
  • The feed list is loaded from a JSON instead of an INI.

The Atom feed and the OPML file should validate, but let me know at sacha@sachachua.com if there are any hiccups (or if you have an Atom/RSS feed we can add to the aggregator =) ).

View org source for this post
-1:-- Changing planet.emacslife.com (Post Sacha Chua)--L0--C0--2025-01-16T15:50:31.000Z

Kisaragi Hiu: RIME 停用它自己的英數輸入模式

我電腦上習慣用 RIME 輸入法引擎提供的注音輸入法,但它預設(就像其他注音輸入法一樣)有自己的英數模式。因為系統的輸入法引擎(我是用 fcitx5)也
-1:-- RIME 停用它自己的英數輸入模式 (Post Kisaragi Hiu)--L0--C0--2025-01-16T14:55:32.000Z

Lars Ingebrigtsen: WordPress Statistics for Emacs

A few days ago, I posted about writing a new WordPress stats thing because the Jetpack stats layout annoyed me too much.

I’ve continued to tinker with it, and I think it should now basically be usable. I renamed it to wse (WordPress Statistics for Emacs), and it’s on Microsoft Github if anybody’s interested in trying it out.

The Emacs portion updates asynchronously in the background now.

I added one thing that doesn’t have anything to do with statistics per se — it now also displays recent comments, because that’s something one has to keep track of (if for no other reason than to delete any spam that slips through the (very good, but not perfect) Akismet anti-spam filter).

One thing that I’ve totally missed is that in 2020, Chrome changed how they do inter-site Referrers: They only send over the domain name, but leave off the path part. I thought I had a bug in my code, or that almost all domains had installed “referrer scrubbers” (like Google has always had). But nope: All you get is the domain name.

This is, of course, good for privacy. And I’m totally sure that Google did this Chrome change out of the goodness of their hearts, and not because this forces marketers to sign up for Google-based user tracking even harder than before. Good on ’em!

Speaking of browsers… I couldn’t help adding this little table. It’s not that it’s useful, but I was just kinda sorta curious about what people use these days. And the audience for this blog is just 50% Chrome users, which is unusual, of course. Such a discerning audience!

The desktop to mobile ratio surprises me, though — I thought that would be more 50/50, but nope. And, of course, tablets have stopped being a thing, but that’s not really surprising.

The sample is very small (one day), though, so drawing any conclusions here wouldn’t be a good idea.

-1:-- WordPress Statistics for Emacs (Post Lars Ingebrigtsen)--L0--C0--2025-01-16T06:52:28.000Z

Protesilaos Stavrou: Emacs: Org todo and agenda basics

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

In this ~45-minute video I cover the basics of managing your task list with Org mode. The idea is to write tasks in a simple file and then use the Org agenda views to display and filter the tasks you are interested in. Throughout the video I also comment on relevant points about the overall workflow. The basic configuration I show in the video is below:

;; These are the defaults we want to change.  We do so in the
;; following `use-package' declaration.
(setq org-M-RET-may-split-line '((default . t)))
(setq org-insert-heading-respect-content nil)
(setq org-log-done nil)
(setq org-log-into-drawer nil)


(use-package org
  :ensure nil ; do not try to install it as it is built-in
  :config
  (setq org-M-RET-may-split-line '((default . nil)))
  (setq org-insert-heading-respect-content t)
  (setq org-log-done 'time)
  (setq org-log-into-drawer t)

  (setq org-directory "/tmp/testing-org/")
  (setq org-agenda-files (list org-directory))

  ;; Learn about the ! and more by reading the relevant section of the
  ;; Org manual.  Evaluate: (info "(org) Tracking TODO state changes")
  (setq org-todo-keywords
        '((sequence "TODO(t)" "WAIT(w!)" "|" "CANCEL(c!)" "DONE(d!)"))))
-1:-- Emacs: Org todo and agenda basics (Post Protesilaos Stavrou)--L0--C0--2025-01-16T00:00:00.000Z

Srijan Choudhary: 2025-01-15-001

I had been facing an issue in #Emacs on my work Mac system: C-S-<tab> was somehow being translated to C-<tab>. I tried to look into key-translation-map to figure out the issue, but could not find anything.

Finally, turned out that I had bound C-<tab> to tab-line-switch-to-next-tab and C-<iso-lefttab> to tab-line-switch-to-prev-tab, but the actual C-S-<tab> was unbound. C-<iso-lefttab> only works on linux: something to do with how X11 sends the event to the application (and probably some compatibility mode due to which wayland was doing the same).

On Mac, once I explicitly bound C-S-<tab> in my Emacs config, it started working correctly.

-1:-- 2025-01-15-001 (Post Srijan Choudhary)--L0--C0--2025-01-15T02:20:00.000Z

200ok: Deep Merging YAML with Ruby Made Easy

Introduction

In the world of configuration management, YAML has become a de facto standard. However, when dealing with complex configurations, the need for better inheritance and merging capabilities becomes apparent. Today, I want to introduce you to YamlExtensions, a Ruby gem that solves this exact problem.

The Problem with Standard YAML Merging

Standard YAML offers basic merging with the << operator, but it falls short when dealing with nested structures. This limitation often leads to duplicate configurations or complex workarounds.

Enter YamlExtensions

YamlExtensions introduces a powerful feature: deep merging using the <<< operator. This simple yet effective enhancement allows for more sophisticated configuration management.

How It Works

Let's look at a practical example:

defaults: &DEFAULTS
  hello: world
  this:
    is:
      even: deeper

main:
  <<<: *DEFAULTS
  this:
    is:
     somewhat: deep

The gem intelligently merges nested structures, maintaining both the original and overridden values where appropriate.

Multiple Deep Merges

One particularly powerful feature is the ability to perform multiple deep merges by appending numbers to the <<< operator:

main:
  <<<1: *DEFAULTS
  <<<2: *OVERRIDES
  this:
    is:
     somewhat: deep

Getting Started

After installation, simply require the gem in your Ruby code:

require 'yaml_extensions'

YamlExtensions will freedom patch Ruby's YAML implementation to support the aforementioned features.


If you liked this post, please consider supporting our Free and Open Source software work – you can sponsor us on Github and Patreon or star our FLOSS repositories.

-1:-- Deep Merging YAML with Ruby Made Easy (Post 200ok)--L0--C0--2025-01-15T00:00:00.000Z

Irreal: Installing By Default With =use-package=

Bozhidar Batsov has a handy tip in his latest post. He, like me, organizes the package management of his init.el around use-package. It works well for him but he has a complaint. He almost always wants to install the packages by default but has to specify :ensure t for each package that he wants loaded by default. He has, he says, more than 50 packages so this is a nuisance.

There is, however, an easy solution. You can simply set use-package-always-ensure to t and all packages except those with :ensure nil will be loaded by default. Batsov notes that some people don’t like loading packages by default because of version issues but that this has not been an issue for him.

It hasn’t been an issue for me either. But neither has specifying :ensure t for each package. I have, according to a quick check with keep-lines, 70 instances of use-packages. All of them specify :ensure t and I’ve never considered this an irritant even though I know about use-package-always-ensure.

Still, I understand and support the urge to make everything as simple as possible so if you’re an use-package user and want to load all or most of your packages by default, Batsov shows a way of doing that with a little less effort.

-1:-- Installing By Default With =use-package= (Post Irreal)--L0--C0--2025-01-14T15:42:02.000Z

Yi Tang: Use Ledger-Cli to Track DIY Project Expenses

Table of Contents

  1. Personal Technical Challenge
  2. Baby Steps
  3. Why? - Effort Estimation

Personal Technical Challenge

I used ledger-cli1 before and it was a painful experience. The problem was not rooted in the tool but in how I intended to use it: I wanted to track all my expenses, from buying a cup of coffee to booking a holiday package. When I started this journey, there was a massive jump from knowing little to nothing about personal finance to doing double-entry accounting in plain text.

Though I gave up, it introduced me to the idea of owning my bank transaction data in text files on my personal computer. So over the years, I manually curated about 8 years of historical transaction data.

If you haven’t done so, I strongly recommend you go to your banks’ website and download the transaction data manually, going as far back as you can. You will notice that the banks only give access to 3-5 years of data2. It’s a shame that banks use outdated technologies but it is better than having nothing.

Since I had the data, I did some analysis and charts in Python/R. But I kept wondering what ledger-cli can offer. I occasionally saw blog posts on ledger-cli in the Emacs communities, so there must be something out there.

It also has become a personal challenge. I turned not to give up but put it aside to tackle it again after I got older.

Baby Steps

Hopefully, I had become smarter as well. This time, to ensure I can successfully adopt the tool, I am going to reduce the scope to limit to only tracking DIY project expenses.

I love DIY and I wish I had more days for DIY projects. It is usually labour-intensive and I feel hyped and extremely confident after a couple of DIY. Pairing it with learning ledger-cli, a cognitive-intensive activity, would make them a nice bundle3.

Though the usage is simple, the question it can answer is important. I want to know, during or after the DIY project, how much it exactly costs. I could use a much simpler tool, like spreadsheets or a pen/notebook, but I want it to be a stepping stone to acquire ledger-cli properly in the future.

Why? - Effort Estimation

I need an accurate answer to the actual costs so that I can use the data to train myself in cost estimation. This is an very important skill to have as a homeowner, it would put me in a much better position in negotiation with the tradesman. A lot of the people in the UK complained that they or their relatives got ripped off by tradesman.4

In general, house repairs and improvements are getting much more expensive every year, due to the shortage of labourers, inflation and Brexit etc. To give an example using my last two quotes, adding an electrical socket costs £240 and replacing a small section of water pipes costs £500.

I have a good habit of using org-mode to track time, my goal to add ledger-cil to my system to track the expenses. After that, I would know if it is really worth doing the DIY or finding a proper tradesman. The total cost itself is not the only metric that matters, but n very essential one to have.

Footnotes

1 https://ledger-cli.org/

2 Why don’t banks give access to all your transaction activity?

3 I might pick it up from Atomic Habit

4 How many of you have been ripped off by builders / tradesmen? (or know someone closely that has)

-1:-- Use Ledger-Cli to Track DIY Project Expenses (Post Yi Tang)--L0--C0--2025-01-14T00:00:00.000Z

Marcin Borkowski: A minor Org Clive improvement

It’s been a while since I touched Org Clive, but I’m still using it. (Side note: after writing about a dozen articles on my Doctor Who weblog it went a bit dormant. I’m still thinking about it – in fact, even more than thinking, I’m slowly writing it – and I really do hope to come back to publishing there with some regularity. Stay tuned, and if you are interested, keep its RSS feed in your RSS reader.)
-1:-- A minor Org Clive improvement (Post Marcin Borkowski)--L0--C0--2025-01-13T20:27:55.000Z

Srijan Choudhary: Triggering Orgzly sync on Android when Org file changes

Introduction

To use my org GTD system on the go, I use the excellent Orgzly Revived mobile app for Android. To sync the org files between my laptop and my phone, I use syncthing (specifically, the Syncthing Android Fork by Catfriend1).

This allows me to quickly capture things from my phone, get reminder notifications of time-sensitive tasks, and mark tasks complete from my phone. The widgets are also useful for quickly looking at some contexts like errands or calls.

Sync Issues

However, Orgzly works by contructing a copy of the contents in it's own database and synchronizing it against the files periodically or when some action is taken in the app. Right now, it does not support synchronizing the data when the file changes.

For me, this has sometimes lead to a conflict between the Orgzly database and the actual files in the org folder. This only happens if the org file is edited on my laptop and something is also edited in the Orgzly app before syncing.

But, the Orgzly app supports intents that can be used from Tasker, Automate, etc to trigger an event-based sync.

Tasker Profile

So, I created a tasker profile to do this. It was surprisingly easy (I've used tasker before, though not too much). It can be found here: https://taskernet.com/?public&tags=orgzly&time=AllTime called "Run Orgzly Sync When Org Files Change".

Here's the basic flow of the profile:

Profile: Run Orgzly Sync When Org Files Change
Settings: Notification: no
Variables: [ %orgfolder:has value ]
    Event: File Modified [ File:%orgfolder Event:* ]



Enter Task: Orgzly Sync
Settings: Run Both Together

A1: If [ %evtprm1 ~ *.org & %evtprm2 ~ ClosedWrite/MovedTo ]

    A2: Send Intent [
         Action: com.orgzly.intent.action.SYNC_START
         Cat: None
         Package: com.orgzlyrevived
         Class: com.orgzly.android.ActionReceiver
         Target: Broadcast Receiver ]

A3: End If

How it works

  1. When importing this profile, it will ask for your org folder in the local Android filesystem.
  2. Once selected and the profile is activated, it will start monitoring this folder for inotify events.
  3. When any file is modified (or created or moved to/from the folder or deleted), this profile is triggered. It received the parameters: full path of the affected file, and the actual event.
  4. Then, it checks if the affected file is an org file (ending in .org) AND if the event is one of ClosedWrite or MovedTo. I filtered to these events because they are usually the last event received for an edit.
  5. If yes, then Orgzly sync is triggered using an intent.

I've been using this for the last several months, and it works well as long as both devices are online when making edits. Conflicts can still happen if, for example, I make some edits on my laptop and subsequently on my phone but the phone is not online or the syncthing app is not running due to data saver or battery saver active. In those cases, eventually syncthing creates a conflicted file that I can manually resolve.

Limitations

  1. It does not support recursive files inside the folder. Tasker right now does not support recursively watching a folder, but if it's added this can be a good edition; specially because Orgzly Revived supports that.
  2. Since this watches the files in the folder, it also triggers a sync if Orgzly app was used to change the file. Not sure if this can be filtered somehow. Maybe based on foreground app? But it seems flakey.

Alternatives

  1. Orgzly Revived has a git sync backend in beta. This might work better with auto-commit & push.
  2. Using Emacs on Android instead of Orgzly is also an option, but I felt it did not work very well without an external keyboard. Also, it does not have widgets.
-1:-- Triggering Orgzly sync on Android when Org file changes (Post Srijan Choudhary)--L0--C0--2025-01-13T18:20:00.000Z

Emacs APAC: UPDATED Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, January 18, 2025

This month’s Emacs Asia-Pacific (APAC) virtual meetup is scheduled for Saturday, January 18, 2025 with Jitsi Meet 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:-- UPDATED Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, January 18, 2025 (Post Emacs APAC)--L0--C0--2025-01-13T17:30:00.000Z

Sacha Chua: 2025-01-13 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, communick.news, 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
-1:-- 2025-01-13 Emacs news (Post Sacha Chua)--L0--C0--2025-01-13T13:04:42.000Z

Emacs Redux: Emacs and XDG sitting on a tree

Where to place my Emacs configuration? That is the question!

This fairly simple question has surprisingly many answers, as it often happens with projects as old as Emacs:

  • Historically Emacs’s user config was a file called .emacs, placed in your home directory
  • Later you could use the name init.el as an alias to .emacs
  • And then there’s the option that’s probably most common today - placing your configuration under the .emacs.d folder, where you usually have an init.el that potentially refers to other Emacs Lisp libraries

But wait, there’s more! Emacs 27.1 introduced XDG_CONFIG_HOME support, and Emacs 29.1 extended the level of XDG base directory specification support in Emacs.

XDG, which stands for X Desktop Group, is a set of standards and specifications developed by freedesktop.org to promote interoperability among different desktop environments, primarily in Unix-like operating systems. The group focuses on ensuring that applications can work seamlessly across various desktop environments by adhering to common guidelines.

Their most notable work to date is the XDG base directory specification. In a nutshell this specification outlines the environment variables that define where user-specific data files, configuration files, and cache files should be stored. For instance, it specifies directories like $XDG_CONFIG_HOME for configuration files and $XDG_DATA_HOME for data files.1

Most of the time the XDG config home would be ~/.config and there would be a subfolder there for the configuration of each application. In Emacs’s case that would be ~/.config/emacs. I hope it’s clear that the idea here is to reduce the clutter at the top-level and to make sure that each application stores its configuration in a predictable place.

I think it’s really great that Emacs is (slowly) adopting the industry standards and I believe that over the course of time the XDG config folder will become the preferred place to store your Emacs configuration. That’s why I encourage everyone to move in this direction and unclutter their home folder a bit.

Just keep in mind a couple of things:

  • You’ll have to make sure that folders like ~/.emacs.d don’t exist anymore, as they’d have precedence over the XDG config folder. The configuration resolution happens like this:

Emacs looks for your init file using the filenames ~/.emacs.el, ~/.emacs, or ~/.emacs.d/init.el in that order; you can choose to use any one of these names. (Note that only the locations directly in your home directory have a leading dot in the location’s basename.)

Emacs can also look in an XDG-compatible location for init.el, the default is the directory ~/.config/emacs. This can be overridden by setting XDG_CONFIG_HOME in your environment, its value replaces ~/.config in the name of the default XDG init file. However ~/.emacs.d, ~/.emacs, and ~/.emacs.el are always preferred if they exist, which means that you must delete or rename them in order to use the XDG location.

Note also that if neither the XDG location nor ~/.emacs.d exist, then Emacs will create ~/.emacs.d (and therefore use it during subsequent invocations).

Emacs will set user-emacs-directory to the directory it decides to use.

Not the best defaults IMO (especially falling back to creating .emacs.d), but you can’t fight tradition! Or rather - fighting tradition is pretty hard…

  • Some packages might have hardcoded the path in which they store their own configuration. In general they should be relying on user-emacs-directory, which will be auto-set to whatever directory Emacs discovered its configuration in, but there will always be some packages that probably didn’t do “the right thing”.

I’m guessing that we’re not really getting rid of ~/.emacs.d any time soon (or ever), but I’m hoping that article like this one might speed up a bit the process. Time will tell.

macOS users should keep in mind that unfortunately macOS doesn’t set the standard XDG environment variables, as it has its own notion of where things like configuration files, application cache, etc should be stored. Still, it’s fairly easy to just set the missing variables yourself (e.g. in your .zshrc):

export XDG_CONFIG_HOME = $HOME/.config
export XDG_DATA_HOME = $HOME/.local/share
export XDG_STATE_HOME = $HOME/.local/state
export XDG_CACHE_HOME = $HOME/.cache

So, has anyone moved their Emacs config to respect the XDG conventions already? How smooth was the process for you?

P.S. If you’re curious to learn more about how Emacs’s configuration discover process I’d suggest reading this chapter of the Emacs Manual. Good stuff!

  1. Check out the XDG base directory specification for more details. 

-1:-- Emacs and XDG sitting on a tree (Post Emacs Redux)--L0--C0--2025-01-12T16:34:00.000Z

Emacs Redux: Ensure all packages are installed by default with use-package

I’m quite fond of use-package and I’ve organized my personal Emacs setup around it for a while now. One thing that I don’t like very much is that by default almost I have to add :ensure t to almost every use-package block, as I want all external packages to be installed if they are not present. That’s quite handy when I’m setting up Emacs on a new computer.1 Think something like:

(use-package zenburn-theme
  :ensure t
  :config
  (load-theme 'zenburn t))

Not a big deal for a few packages, but kind of annoying if you have 50+ packages in your init.el. There’s a pretty simple solution to this problem, though. Just add the following bit of configuration to your Emacs setup:

(setq use-package-always-ensure t)

Now instead of specifying :ensure t you can specify :ensure nil for the packages you don’t want to install automatically. Note that for built-packages (e.g. dired) it doesn’t really matter if a package is using :ensure t or :ensure nil.2

Which approach do you prefer? Are you the type of person who ensures every package is installed when absent or not? Why do you prefer one approach over the other?

  1. I know that a lot of people object to this approach, as you’re not sure what versions of the packages you’d get as package.el is a bit primitive compared to something like Ruby’s Bundler or Node’s npm, but in practice I’ve rarely had issues with my approach and it has saved me a great deal of time. 

  2. package-installed-p will return t for those. 

-1:-- Ensure all packages are installed by default with use-package (Post Emacs Redux)--L0--C0--2025-01-12T16:12:00.000Z

Irreal: Class Notes In Emacs: Teachers’ Edition

I’ve written in the past on my astonishment about people who are able to take mathematics class notes in LaTeX fast enough to keep up with the instructor: 1, 2, and 3. Some people who do this use Emacs, some use Vim. In either case, it’s breathtaking to watch them.

Now Matt Maguire takes a look at it from the other side: producing class notes for students. Speed is not an issue here, of course, but getting a workable system involves more than just writing some LaTeX formulas and publishing them.

Maguire has a specific format in mind and has been producing notes in it for some time directly with LaTeX. But as an Emacs user, we wanted to leverage Org mode to simplify the production of the notes.

The nice thing about his exposition is that it’s widely applicable. You may not be a teacher or you may not want to produce notes involving mathematics but if you want to use anything other than the standard LaTeX article class, you have some work to do. Maguire’s post tells you how to set up another class and other custom settings you need for your unique environment.

All this may seem like a lot of work but you only have to do it once. After that, you just write whatever it is in Org as usual. The post demonstrates one of the (many) strengths of Org mode.

Update [2025-01-12 Sun 11:22]: Added link to Maguire’s post.

-1:-- Class Notes In Emacs: Teachers’ Edition (Post Irreal)--L0--C0--2025-01-12T16:02:27.000Z

Sacha Chua: Treemap visualization of an Org Mode file

[2025-01-12 Sun]: u/dr-timeous posted a treemap_org.py · GitHub that makes a coloured treemap that displays the body on hover. (Reddit) Also, I think librsvg doesn't support wrapped text, so that might mean manually wrapping if I want to figure out the kind of text density that webtreemap has.

One of the challenges with digital notes is that it's hard to get a sense of volume, of mass, of accumulation. Especially with Org Mode, everything gets folded away so neatly and I can jump around so readily with C-c j (org-goto) or C-u C-c C-w (org-refile) that I often don't stumble across the sorts of things I might encounter in a physical notebook.

Treemaps are a quick way to visualize hierarchical data using nested rectangles or squares, giving a sense of relative sizes. I was curious about what my main organizer.org file would look like as a treemap, so I wrote some code to transform it into the kind of data that https://github.com/danvk/webtreemap wants as input. webtreemap creates an HTML file that uses Javascript to let me click on nodes to navigate within them.

For this treemap prototype, I used org-map-entries to go over all the headings and make a report with the outline path and the size of the heading. To keep the tree visualization manageable, I excluded done/cancelled tasks and archived headings. I also wanted to exclude some headings from the visualization, like the way my Parenting subheading has lots of personal information underneath it. I added a :notree: tag to indicate that a tree should not be included.

Screencast of exploring a treemap

Reflections

2025-01-11_19-54-47.png
Figure 1: Screenshot of the treemap for my organizer.org

The video and the screenshot above show the treemap for my main Org Mode file, organizer.org. I feel like the treemap makes it easier to see projects and clusters where I'd accumulated notes, both in terms of length and quantity. (I've omitted some trees like "Parenting" which take up a fairly large chunk of space.)

To no one's surprise, Emacs takes up a large part of my notes and ideas. =)

When I look at this treemap, I notice a bunch of nodes I need to mark as DONE or CANCELLED because I forgot to update my organizer.org. That usually happens when I come up with an idea, don't remember that I'd come up with it before, put it in my inbox.org file, and do it from there or from the organizer.org location I've refiled it to without bumping into the first idea. Once in a blue moon, I go through my whole organizer.org file and clean out the cruft. Maybe a treemap like this will make it easier to quickly scan things.

Interestingly, "Explore AI" takes up a disproportionately large chunk of my "Inactive Projects" visualization, even though I spend more time and attention on other things. Large language models make it easy to generate a lot of text, but I haven't really done the work to process those. I've also collected a lot of links that I haven't done much with.

It might be neat to filter the headings by timestamp so that I can see things I've touched in the last 6 months.

Hmm, looking at this treemap reminds me that I've got "organizer.org/Areas/Ideas for things to do with focused time/Writing/", which probably should get moved to the posts.org file that I tend to use for drafts. Let's take look at the treemap for that file. (Updated: cleared it out!)

2025-01-11_20-10-18.png
Figure 2: Drafts in my posts.org

Unlike my organizer.org file, my posts.org file tends to be fairly flat in terms of hierarchy. It's just a staging ground for ideas before I put them on my blog. I usually try to keep posts short, but a few of my posts have sub-headings. Since the treemap makes it easy to see nodes that are larger or more complex, that could be a good nudge to focus on getting those out the door. Looking at this treemap reminds me that I've got a bunch of EmacsConf posts that I want to finish so that I can document more of our processes and tools.

2025-01-11_14-52-28.png
Figure 3: Treemap of my inbox

My inbox.org is pretty flat too, since it's really just captured top-level notes that I'll either mark as done or move somewhere else (usually organizer.org). Because the treemap visualization tool uses / as a path separator, the treemap groups headings that are plain URLs together, grouped by domain and path.

2025-01-12_08-30-44.png
Figure 4: Treemap of my Emacs configuration

My Emacs configuration is organized as a hierarchy. I usually embed the explanatory blog posts in it, which explains the larger nodes. I like how the treemap makes it easy to see the major components of my configuration and where I might have a lot of notes/custom code. For example, my config has a surprising amount to do with multimedia considering Emacs is a text editor, and that's mostly because I like to tinker with my workflow for sketchnotes and subtitles. This treemap would be interesting to colour based on whether something has been described in a blog post, and it would be great to link the nodes in a published SVG to the blog post URLs. That way, I can more easily spot things that might be fun to write about.

The code

This assumes https://github.com/danvk/webtreemap is installed with npm install -g webtreemap-cli.

(defvar my-org-treemap-temp-file "~/treemap.html") ; Firefox inside Snap can't access /tmp
(defvar my-org-treemap-command "treemap" "Executable to generate a treemap.")

(defun my-org-treemap-include-p (node)
  (not (or (eq (org-element-property :todo-type node) 'done)
           (member "notree" (org-element-property :tags node))
           (org-element-property-inherited :archivedp node 'with-self))))

(defun my-org-treemap-data (node &optional path)
  "Output the size of headings underneath this one."
  (let ((sub
         (apply
          'append
          (org-element-map
              (org-element-contents node)
              '(headline)
            (lambda (child)
              (if (my-org-treemap-include-p child)
                  (my-org-treemap-data
                   child
                   (append path
                           (list
                            (org-no-properties
                             (org-element-property :raw-value node)))))
                (list
                 (list
                  (-
                   (org-element-end child)
                   (org-element-begin child))
                  (string-join
                   (cdr
                    (append path
                            (list
                             (org-no-properties
                              (org-element-property :raw-value node))
                             (org-no-properties
                              (org-element-property :raw-value child)))))
                   "/")
                  nil))))
            nil nil 'headline))))
    (append
     (list
      (list
       (-
        (org-element-end node)
        (org-element-begin node)
        (apply '+ (mapcar 'car sub))
        )
       (string-join
        (cdr
         (append path
                 (list
                  (org-no-properties (org-element-property :raw-value node)))))
        "/")
       (my-org-treemap-include-p node)))
     sub)))

(defun my-org-treemap ()
  "Generate a treemap."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (let ((file (expand-file-name (expand-file-name my-org-treemap-temp-file)))
          (data (cdr (my-org-treemap-data (org-element-parse-buffer)))))
      (with-temp-file file
        (call-process-region
         (mapconcat
          (lambda (entry)
            (if (elt entry 2)
                (format "%d %s\n" (car entry)
                        (replace-regexp-in-string org-link-bracket-re "\\2" (cadr entry)))
              ""))
          data
          "")
         nil
         my-org-treemap-command nil t t))
      (browse-url (concat "file://" (expand-file-name my-org-treemap-temp-file))))))

There's another treemap visualization tool that can produce squarified treemaps as coloured SVGs, so that style might be interesting to explore too.

Next steps

I think there's some value in being able to look at and think about my outline headings with a sense of scale. I can imagine a command that shows the treemap for the current subtree and allows people to click on a node to jump to it (or maybe shift-click to mark something for bulk action), or one that shows subtrees summing up :EFFORT: estimates or maybe clock times from the logbook, or one limited by a timestamp range, or one that highlights matching entries as you type in a query, or one that visualizes s-exps or JSON or project files or test coverage.

It would probably be more helpful if the treemap were in Emacs itself, so I could quickly jump to the Org nodes and read more or mark something as done when I notice it. boxy-headings uses text to show the spatial relationships of nested headings, which is neat but probably not up to handling this kind of information density. Emacs can also display SVG images in a buffer, animate them, and handle mouse-clicks, so it could be interesting to implement a general treemap visualization which could then be used for all sorts of things like disk space usage, files in project modules, etc. SVGs would probably be a better fit for this because that allows increased text density and more layout flexibility.

It would be useful to browse the treemap within Emacs, export it as an SVG so that I can include it in a webpage or blog post, and add some Javascript for web-based navigation.

The Emacs community being what it is (which is awesome!), I wouldn't be surprised if someone's already figured it out. Since a quick search for treemap in the package archives and various places doesn't seem to turn anything up, I thought I'd share these quick experiments in case they resonate with other people. I guess I (or someone) could figure out the squarified treemapping algorithm or the ordered treemap algorithm in Emacs Lisp, and then we can see what we can do with it.

I've also thought about other visualizations that can help me see my Org files a different way. Network graphs are pretty popular among the org-roam crew because org-roam-ui makes them. Aside from a few process checklists that link to headings that go into step-by-step detail and things that are meant to graph connections between concepts, most of my Org Mode notes don't intentionally link to other Org Mode notes. (There are also a bunch of random org-capture context annotations I haven't bothered removing.) I tend to link to my public blog posts, sketches, and source code rather than to other headings, so that's a layer of indirection that I'd have to custom-code. Treemaps might be a good start, though, as they take advantage of the built-in hierarchy. Hmm…

View org source for this post
-1:-- Treemap visualization of an Org Mode file (Post Sacha Chua)--L0--C0--2025-01-12T13:39:55.000Z

Bozhidar Batsov: macOS No Longer Ships with Emacs

While I was setting up my new mac mini yesterday I noticed something interesting - Apple have stopped shipping the ancient Emacs 22.1 with macOS! As I was mostly using Windows + WSL2 in the past 5 years I had missed the exact moment when this happened, but after some digging I discovered that the change was first made in macOS 10.15 (“Catalina”), which was released in October 2019.

For me that’s a very welcome change, as the ancient version of Emacs that Apple was shipping in the past was only causing issues for Emacs users. (occasionally people would accidentally run it, instead of whatever Emacs they installed by themselves and they’d wonder what went wrong) Still, I got curious about would things:

  1. Why didn’t Apple just update Emacs?
  2. What prompted them to finally drop the ancient Emacs?

The answer to the first question turned out to be Apple being opposed to using anything licensed under GPL v3, and so it happens that Emacs 22.1 was the last Emacs version licensed under GPL v2.1 A former Apple employee shared the following in a comment on HackerNews:

When I was at Apple, they discontinued bundling the command line version of emacs, and they sent a long internal email stating why. The stated reason was that any newer emacs would suffer from license incompatibility with macOS, and they didn’t see the point of supporting a 10 year old version that the FSF doesn’t even support when, if someone really wants emacs, it’s just a brew install emacs away.

As for the timing - I guess this was just a long time coming.

One curious tidbit - Apple did provide a sort of out-of-the-box replacement for Emacs. That’s the mg editor, which is something like a “Micro Emacs” in a way. I think it’s a great alternative to the likes of vi and nano when you need to do simple edits from the command-line, so you might consider adding something like this to your shell setup:

export EDITOR=mg

That’s all I have for you today. Keep hacking!

  1. For the same reason macOS switched from Bash to Zsh in the very same macOS 10.15 (“Catalina”). And similarly to the situation with Emacs - for a very long time Apple were shipping an ancient version of Bash with macOS, the last one that was released under GPL v2. Obviously almost noone was willing to use it. 

-1:-- macOS No Longer Ships with Emacs (Post Bozhidar Batsov)--L0--C0--2025-01-12T09:03:00.000Z

Irreal: Converting Markdown to Org

Charles Choi has a handy tip on converting from Mardown to Org. My first thought was that I don’t care about this because I write everything in Org Mode and never use Markdown. But then it occurred to me that the same is doubtless true of Choi as well and that someday I’m going to want to import a Markdown document into Org.

Choi’s basic process is to cut and paste the Markdown document into Emacs and then use Pandoc to convert from Markdown to Org. But there’s a problem. Pandoc will wrap lines while Choi likes to keep paragraphs as a single line. I do that too because it’s how visual line mode works. The excellent Pandoc has us covered, of course. You need only specify --wrap=preserve to get the desired behavior.

With that bit of wisdom, Choi gives us a handy function that will convert the Markdown in the current region to Org. It’s probably worthwhile adding his short function to your init.el just so you’ll have it when the need arrives. If you don’t like cluttering up your init.el with things you’re not actively using, at least bookmark his post so you’ll know what to do when the need arises.

-1:-- Converting Markdown to Org (Post Irreal)--L0--C0--2025-01-11T16:09:50.000Z

Sacha Chua: Automatically correcting phrasing and misrecognized words in speech-to-text captions by using a script

I usually write my scripts with phrases that could be turned into the subtitles. I figured I might as well combine that information with the WhisperX transcripts which I use to cut out my false starts and oopses. To do that, I use the string-distance function, which calculates how similar strings are, based on the Levenshtein [distance] algorithm. If I take each line of the script and compare it with the list of words in the transcription, I can add one transcribed word at a time, until I find the number with the minimum distance from my current script phrase. This lets me approximately match strings despite misrecognized words. I use oopses to signal mistakes. When I detect those, I look for the previous script line that is closest to the words I restart with. I can then skip the previous lines automatically. When the script and the transcript are close, I can automatically correct the words. If not, I can use comments to easily compare them at that point. Even though I haven't optimized anything, it runs well enough for my short videos. With these subtitles as a base, I can get timestamps with subed-align and then there's just the matter of tweaking the times and adding the visuals.

Text from sketch

Matching a script with a transcript 2025-01-09-01

  • script
  • record on my phone
  • WhisperX transcript (with false starts and recognition errors)

My current implementation is totally unoptimized (n²) but it's fine for short videos.

Process:

  • While there are transcript words to process
    • Find the script line that has the minimum distance to the words left in the transcript. restart after oopses
  • Script
  • Transcript: min. distance between script phrase & transcript
  • Restarting after oops: find script phrase with minimum distance
  • Ex. script phrase: The Emacs text editor
  • Transcript: The Emax text editor is a…
  • Bar graph of distance decreasing, and then increasing again
  • Minimum distance
  • Oops?
    • N: Use transcript words, or diff > threshold?
      • Y: Add script words as comment
      • N: Correct minor errors
    • Y: Mark caption for skipping and look for the previous script line with minimum distance.

Result:

  • Untimed captions with comments
  • Aeneas
  • Timed captions for editing

This means I can edit a nicely-split, mostly-corrected file.

I've included the links to various files below so you can get a sense of how it works. Let's focus on an excerpt from the middle of my script file.

it runs well enough for my short videos.
With these subtitles as a base,
I can get timestamps with subed-align

When I call WhisperX with large-v2 as the model and --max_line_width 50 --segment_resolution chunk --max_line_count 1 as the options, it produces these captions corresponding to that part of the script.

01:25.087 --> 01:29.069
runs well enough for my short videos. With these subtitles

01:29.649 --> 01:32.431
as a base, I can get... Oops. With these subtitles as a base, I

01:33.939 --> 01:41.205
can get timestamps with subedeline, and then there's just

Running subed-word-data-use-script-file results in a VTT file containing this excerpt:

00:00:00.000 --> 00:00:00.000
it runs well enough for my short videos.

NOTE #+SKIP

00:00:00.000 --> 00:00:00.000
With these subtitles as a base,

NOTE #+SKIP

00:00:00.000 --> 00:00:00.000
I can get... Oops.

00:00:00.000 --> 00:00:00.000
With these subtitles as a base,

NOTE
#+TRANSCRIPT: I can get timestamps with subedeline,
#+DISTANCE: 0.14

00:00:00.000 --> 00:00:00.000
I can get timestamps with subed-align

There are no timestamps yet, but subed-align can add them. Because subed-align uses the Aeneas forced alignment tool to figure out timestamps by lining up waveforms for speech-synthesized text with the recorded audio, it's important to keep the false starts in the subtitle file. Once subed-align has filled in the timestamps and I've tweaked the timestamps by using the waveforms, I can use subed-record to create an audio file that omits the subtitles that have #+SKIP comments.

The code is available as subed-word-data-use-script-file in subed-word-data.el. I haven't released a new version of subed.el yet, but you can get it from the repository.

In addition to making my editing workflow a little more convenient, I think it might also come in handy for applying the segmentation from tools like sub-seg or lachesis to captions that might already have been edited by volunteers. (I got sub-seg working on my system, but I haven't figured out lachesis.) If I call subed-word-data-use-script-file with the universal prefix arg C-u, it should set keep-transcript-words to true and keep any corrections we've already made to the caption text while still approximately matching and using the other file's segments. Neatly-segmented captions might be more pleasant to read and may require less cognitive load.

There's probably some kind of fancy Python project that already does this kind of false start identification and script reconciliation. I just did it in Emacs Lisp because that was handy and because that way, I can make it part of subed. If you know of a more robust or full-featured approach, please let me know!

View org source for this post
-1:-- Automatically correcting phrasing and misrecognized words in speech-to-text captions by using a script (Post Sacha Chua)--L0--C0--2025-01-11T14:11:40.000Z

Lars Ingebrigtsen: WordPress Statistics & Me

I’m using Jetpack Stats to see what’s happenin’ on mah blogs, and it’s never been whatchamacall “great”, but it’s been OK. It’s OK. It displays all the pertinent info in one pretty compact screen: You don’t have to scroll around.

But they’ve been threatening to remove the “classic”, compact stats for years, and yesterday they apparently did, because I was faced with a page like the above.

As an example, let’s look at Moving Pictures, my auxiliary movie-watching logging blog (not really meant to be read by anybody, really).

I clicked the dreaded “switch to the new stats”, and…

Yeah, OK…

OK, “click” the ad away, and…

That’s the entire screen, and it shows the data for the last seven days!? OK, perhaps you can click a single day (because that’s what I’m interested in)…

*sigh*

So if I want to see today’s info, like in the old interface, I can’t see any previous days. In addition, it’s negging me: Down 87%?!? Yeah, because the day “ticked over” only a few hours earlier at this point, so any normal day will be “down” until it’s over. Why isn’t it showing me the previous 24 hours instead of the day so far, for heaven’s sake?

(I guess if you don’t have a time-series database it’s just easier and faster to just “bin” the data per day instead of having to store all the visits and do a select count(*)... thing. And WordPress very much doesn’t use a time-series database, I think.)

But let’s look at the rest of the page.

Well, that’s nice and spacious… The thing that’s interesting is really 1) the visitor tally, and 2) the referrers (useful if you have sudden explosions in visitor tallies *cough* Hackernews *cough*), and 3) clicks (to see whether something I’ve linked got a lot of traffic).

Obviously, none of that is really of interest here, since that site has no visitors, really, but bear with me.

So scrolling down, the next page has an upsell block, and then the clicks. The “Search” box is never useful, since (virtually) every search engine wisely censors search terms, and there are no videos to track.

So scroll down more, and we get more upsells, and…

… finally some more upsells.

So that’s the new stats page: On this laptop, you have to scroll three screens to glean the “useful” information, and that’s obviously useless.

This page is mostly here to upsell, I guess? Stats is the primary reason people install Jetpack Stats, so it must be seen as the primary channel for upselling people more services from Automattic, so they put all the ads on the stats page? 🤷

But even if you’ve bought everything Automattic has to sell here, the page is still pretty useless, unless you enjoy scrolling (or has a very tall screen).

This sort of stuff pisses me off, so I’m finally doing what I should have done a decade ago: Write my own stats thing, and one that doesn’t involve looking a web page.

Here’s some design points:

  1. I want all the information available at a glance.
  2. I want “today’s” data to be on a 24 hour basis, so that I look at
    how many visits I got over the last 24 hours, and not 2 hour’s worth of data if I’m looking at it at two in the morning.

  3. I want to look at the data across all my blogs at the same time.
  4. I want it to update automatically without having to wait for a reload.

So, obviously, I have to write a plugin to stash the data. And no, one can’t just parse the Apache log, because there’s too much noise there. What Jetpack Stats does (an what almost all stats libraries do) is to trigger the data collection from Javascript, because this weeds out 99.9% of the bots. (Not 99.9% of the bot traffic, though — the major web spiders (like Googlebot) will request (some) JS-triggered requests, but these are mostly well-behaved and have User-Agent strings that are easy to filter out, and there’s only a couple dozen of these.)

So this plugin does the same: An async fetch after the page has been fully rendered, triggered from Javascript. (Yes, this loses hits from people who don’t use Javascript, but those people probably don’t want to be counted anyway.) (And an advantage of using self-hosted stats is that Adblock won’t block the stats. Probably. And no cookies!)

In addition, it’s fun to see what people click, so (like Jetpack Stats), I’m instrumenting all <a> links, so when people click on something, that’s registered, too.

And while I’m at it, I’m also instrumenting <video> elements, and also Lyte/Youtube embeds.

And that’s all the plugin does — it’s a few dozen lines of JS/PHP code. It’s really quite easy and nice working inside the WordPress framework…

Then I just have an API that dumps out this collected data, and the rest happens… in Emacs, of course.

Tada! All the data available in one window, without any scrolling.

But after using the new interface for a couple days, I’m starting to wonder whether the reason they’re using the view-per-date as the most prominent indicator in interfaces like this not because of technical laziness but because of psychology. Let me bloviate:

When the date flips, you’ve confronted with the above: 100% down from the previous day! *gasp* So it’s negging you and making you disappointed. But every time afterwards you reload the page, the number goes up and everybody loves it when the number goes up, right? So doing it this way may be addicting people to the stats: First it gives you a negative shock, and then it rewards you for every reload. Dopamine, man! A 24 hour rolling view doesn’t give you any of that: The number will stay mostly the same, decreasing or increasing a bit each time, and that’s no fun now is it?

IT”S ALL A CONSPIRACY!

Anyway, this took me two days to do, and I ended up with a solution that is (for my use case) far superior to what WordPress has ever had. Your mileage will vary, of course — everybody’s got different preferences. But the reason most people install Jetpack is because of the stats functionality, so you’d think Automattic would be leery at annoying people too much. The layout changes (and attendant slow page load times) were what pushed me over the edge and make me implement my own solution.

When it’s this easy to implement something like this, Automattic is, in my opinion, shooting themselves in the foot by forcing people into changes they don’t want. I mean, absolutely nobody except me is going to use what I hacked together, but when it’s this easy, surely somebody else will write a plugin called Just The Stats And Nothing More or something?

(Yes, there are a lot of other plugins that say they’re good for stats, but from what I can tell by a cursory look-through, most of them seem to be aimed at scum. Sorry, I mean marketers, and many of them seem sleazy and filled with upsells and nobody wants that.)

I’ve got one blog hosted at WordPress.com, but this sort of stuff obviously won’t work there (they don’t allow people installing oddball plugins willy nilly, wisely enough), so I guess I’ll have to move it to self hosting, too. The only reason I haven’t done so before is that I thought it would be nice to continue to pay Automattic some money since they’re doing a lot of good work, but… (The recent meltdowns at Automattic don’t help here.)

Having all this information available in a local SQLite database means that I can add more commands that Jetpack Stats doesn’t have. For instance, there’s three Hacker News referrers? What’s that about, then?

It’s nice to have the data.

And, er, I couldn’t come up with a name when I started typing away, so it ended up being called “bang”, named after Gilbert Hernandez’s Bang & Inez. It’s on Microsoft Github, and I’m already regretting the name. Sorry!

[Edit: I renamed it to “wse”, as in “WordPress Statistics for Emacs”. I ended up with buffer names like “*Bang Date*” which was just too hilarious!]

While I’m bloviating away…

Those Unicode people are pretty smart. I guess everybody knows that those flags are composed by two Unicode characters, so the US flag is 🇺 and then 🇸 and when you put them next to each other, the rendering engine renders it as 🇺🇸 instead? That’s already really clever, but I’ve never looked into what code points like 🇺 are. That is, how they’re arranged, and whether you need some kind of table or something to make the mapping. But no!

You just add 0x1f1a5 to the ASCII code and Bob’s your sister’s brother, possibly.

… well, I thought it was neat. What! Ever!

-1:-- WordPress Statistics & Me (Post Lars Ingebrigtsen)--L0--C0--2025-01-11T09:28:41.000Z

Protesilaos Stavrou: Emacs: denote-sequence.el will also support alphanumeric sequences

In a video from 2025-01-03 (sequence notes with Denote (denote-sequence.el)), I introduced the new optional extension for Denote, which streamlines the creation of file names with an inherent hierarchical relationship. This is expressed as a numeric sequence, where each level of depth is delimited by the equals sign. So 1=1=2 refers to the second child of the first child of the parent note 1.

A popular sequencing scheme is to combine numbers with letters (Luhmann-style folgezettel). This alphanumeric expression is more compact, though it might also be a bit harder to reason about. For example, 1a2 is the same as 1=1=2, which looks clean, but it gets tricky once you reach 1za5zx which is equivalent to 1=27=5=50.

Unlike the numeric scheme where there is an explicit depth delimiter (the =), the alphanumeric scheme establishes depth by alternating between numbers and letters. Thus, 1a2 and 1=1=2 are both three levels of depth.

I am in the process of giving users the option. They will be able to pick their sequencing scheme and then produce notes, re-parent them, and so on, with Denote taking care to use/generate the correct sequence each time. In principle, the two schemes are interoperable and there will be relevant commands to switch between them.

[ UPDATE 2025-01-12 11:25 +0200: Now merged into main and deleted the feature branch. ]

Below is the commit I worked on today. For now it lives in the alphanumeric-sequence-extension branch: https://github.com/protesilaos/denote/tree/alphanumeric-sequence-extension. I will merge it into main as soon as I am done with the user-facing parts, which I will probably do tomorrow or early next week. If you are interested to try out what is now available, please check the source code and let me know your thoughts.

commit 43bd30e6ebd9e948a390d11bc3ec84cf80e74576
Author: Protesilaos Stavrou <info@protesilaos.com>
Date:   Sat Jan 11 19:37:56 2025 +0200

  Make the groundwork for alphanumeric (Luhmann) sequences in denote-sequence.el

  The idea is to support both numeric and alphanumeric sequencing
  schemes, as documented in the new user option 'denote-sequence-scheme'.

  We now have the tools to correctly split, join, increment, and convert
  input accordingly, such that we can, for example, accurately produce a
  child of sequence "1a2" (we could already do that for numeric
  sequences).

  What is covered herein is just the groundwork. I still need to extend
  the helper functions which directly support the creation of new
  parent, child, or sibling sequences. While this still is a lot of
  work, the hard part is now done.

 denote-sequence.el   | 338 +++++++++++++++++++++++++++++++++++++++------------
 tests/denote-test.el |  34 +++++-
 2 files changed, 291 insertions(+), 81 deletions(-)

About Denote

Denote is a simple note-taking tool for Emacs. It is based on the idea that notes should follow a predictable and descriptive file-naming scheme. The file name must offer a clear indication of what the note is about, without reference to any other metadata. Denote basically streamlines the creation of such files while providing facilities to link between them.

Denote’s file-naming scheme is not limited to “notes”. It can be used for all types of file, including those that are not editable in Emacs, such as videos. Naming files in a consistent way makes their filtering and retrieval considerably easier. Denote provides relevant facilities to rename files, regardless of file type.

-1:-- Emacs: denote-sequence.el will also support alphanumeric sequences (Post Protesilaos Stavrou)--L0--C0--2025-01-11T00:00:00.000Z

Charles Choi: Converting a Markdown Region to Org Revisited

On more than one occasion I’ve found myself needing to convert Markdown formatted text to Org using this workflow:

  1. Copy Markdown formatted text (usually from a web browser) into the kill ring.
  2. Paste above text into an Org buffer.
  3. Manually reformat said text to Org.

Folks who have pandoc installed can automate step 3 above with the Elisp function quoted below from the Stack Exchange post “Convert region/subtree from Markdown to org”.

1
2
3
4
(defun my-md-to-org-region (start end)
  "Convert region from markdown to org"
  (interactive "r")
  (shell-command-on-region start end "pandoc -f markdown -t org" t t))

So just copy the above function into your config and call it a day, no?

Not quite. If left unspecified, pandoc will automatically wrap long lines 😞. I prefer keeping a paragraph as a single long line in Org.

To keep a paragraph as a long line, use the pandoc argument --wrap=preserve.

Listed below is an amended version of the above routine.

1
2
3
4
5
6
7
8
9
(defun cc/markdown-to-org-region (start end)
  "Convert Markdown formatted text in region (START, END) to Org.

This command requires that pandoc (man page `pandoc(1)') be
installed."
  (interactive "r")
  (shell-command-on-region
   start end
   "pandoc -f markdown -t org --wrap=preserve" t t))

Regardless of your preference, if this is news to you and seems useful, give it a try.

References

-1:-- Converting a Markdown Region to Org Revisited (Post Charles Choi)--L0--C0--2025-01-10T21:00:00.000Z

Sacha Chua: Org Babel, Mermaid JS, and fixing "Failed to launch the browser process" on Ubuntu 24

Mermaid makes pretty diagrams from text. It's Javascript-based, so the command-line tool (mmdc) uses Puppeteer to get the results of evaluating the diagram in the browser. I was running into some errors trying to get it to work from Org Mode over ob-mermaid on Ubuntu 24, since apparently AppArmor restricts Puppeteer. (Error: Failed to launch the browser process! · Issue #730 · mermaid-js/mermaid-cli).

I put together a pull request to modify ob-mermaid-cli-path so that it doesn't get quoted and can therefore have the aa-exec command needed to work around that. With that modified org-babel-execute:mermaid, I can then configure ob-mermaid like this:

(use-package ob-mermaid
  :load-path "~/vendor/ob-mermaid")
;; I need to override this so that the executable isn't quoted
(setq ob-mermaid-cli-path "aa-exec --profile chrome mmdc -c ~/.config/mermaid/config.json")

I also ran into a problem where the library that Emacs uses to display SVGs could not handle the foreignObject elements used for the labels. mermaid missing text in svg · Issue #112 · mermaid-js/mermaid-cli . Using the following ~/.config/mermaid/config.json fixed it, and I put the option in the ob-mermaid-cli-path above so that it always gets loaded.

{
  "flowchart": {
    "useMaxWidth": false,
    "htmlLabels": false
  }
}

Here's sample Mermaid markup and the file it creates:

mindmap
  root((test))
    Node 1
      Node 1A
      Node 1B
    Node 2
    Node 3
testNode 1Node 2Node 3Node 1ANode 1B

Now I can see the labeled diagrams inside Emacs, too.

This is part of my Emacs configuration.
View org source for this post
-1:-- Org Babel, Mermaid JS, and fixing "Failed to launch the browser process" on Ubuntu 24 (Post Sacha Chua)--L0--C0--2025-01-10T19:16:20.000Z

Irreal: 🥩 Red Meat Friday: Notepad Vs. Emacs

Of all the pretenders to the Emacs crown, notepad strikes me as the most ludicrous. I am not, apparently, the only one. This cartoon captures my feelings exactly.

The Emacs haters were, of course, out in force trying to deflect your attention from the essential truth but we keepers of the faith were not deceived.

-1:-- 🥩 Red Meat Friday: Notepad Vs. Emacs (Post Irreal)--L0--C0--2025-01-10T15:36:20.000Z

Listful Andrew: Sparkly Stats — Stats and sparks from daily data (Emacs package)

Give Sparkly Stats a date range and the values for each date and it gives you statistics and sparks.
-1:-- Sparkly Stats — Stats and sparks from daily data (Emacs package) (Post Listful Andrew)--L0--C0--2025-01-10T10:30:00.000Z

Irreal: Using Ediff On Regions

One of the Emacs powerhouses is Ediff. Every time I think I’ve found a better way of comparing files, it turns out that I just didn’t understand Ediff well enough. It can seem intimidating but as Prot informed me it really pretty simple if you ignore all the complications. I took his advice and can only agree with his assessment.

Now James Dyer has his own take on Ediff. His particular use case is comparing regions rather than whole files. It turns out that there are two functions, ediff-regions-linewise and ediff-regions-wordwise that will use Ediff to compare two regions in the usual Ediff way.

The documentation recommends that you use ediff-regions-wordwise for small regions and ediff-regions-linewise for larger regions. In either case, once you set up the regions to be compared you use Ediff in the usual way. Take a look at Dyer’s post to see how to set up the regions. It’s straightforward and easy to remember once you use it a couple of times.

It’s a nice post and well worth reading even if you don’t need to compare regions right now. You probably will in the future and even if you don’t remember the details, you’ll know what to look for when you do.

-1:-- Using Ediff On Regions (Post Irreal)--L0--C0--2025-01-09T16:01:49.000Z

Sacha Chua: #YayEmacs 9: Trimming/adding silences to get to a target; subed-record-sum-time

New in this video: subed-record-sum-time, #+PAD_LEFT and #+PAD_RIGHT

I like the constraints of a one-minute video, so I added a subed-record-sum-time command. That way, when I edit the video using Emacs, I can check how long the result will be. First, I split the subtitles, align it with the audio to fix the timestamps, and double check the times. Then I can skip my oopses. Sometimes WhisperX doesn't catch them, so I also look at waveforms and characters per second. I already talk quickly, so I'm not going to speed that up but I can trim the pauses in between phrases which is easy to do with waveforms. Sometimes, after reviewing a draft, I realize I need a little more time. If the original audio has some silence, I can just copy and paste it. If not, I can pad left or pad right to add some silence. I can try the flow of some sections and compile the video when I'm ready. Emacs can do almost anything. Yay Emacs!

You can watch this on YouTube, download the video, or download the audio.

Play by play:

  • I like the constraints of a one-minute video, so I added a subed-record-sum-time command. That way, when I edit the video using Emacs, I can check how long the result will be.
    • subed-record uses subtitles and directives in comments in a VTT subtitle file to edit audio and video. subed-record-sum-time calculates the resulting duration and displays it in the minibuffer.
  • First, I split the subtitles, align it with the audio to fix the timestamps, and double check the times.
    • I'm experimenting with an algorithmic way to combine the breaks from my script with the text from the transcript. subed-align calls the aeneas forced alignment tool to match up the text with the timestamps. I use subed-waveform-show-all to show all the waveforms.
  • Then I can skip my oopses.
    • Adding a NOTE #+SKIP comment before a subtitle makes subed-record-compile-video and subed-record-compile-flow skip that part of the audio.
  • Sometimes WhisperX doesn't catch them,
    • WhisperX sometimes doesn't transcribe my false starts if I repeat things quickly.
  • so I also look at waveforms
    • subed-waveform-show-all adds waveforms for all the subtitles. If I notice there's a pause or a repeated shape in the waveform, or if I listen and notice the repetition, I can confirm by middle-clicking on the waveform to sample part of it.
  • and characters per second.
    • Low characters per second is sometimes a sign that the timestamps are incorrect or there's a repetition that wasn't transcribed.
  • I already talk quickly, so I'm not going to speed that up
    • Also, I already sound like a chipmunk; mechanically speeding up my recording to fit in a certain time will make that worse =)
  • but I can trim the pauses in between phrases which is easy to do with waveforms.
    • left-click to set the start, right-click to set the stop. If I want to adjust the previous/next one at the same time, I would use shift-left-click or shift-right-click, but here I want to skip the gaps between phrases, so I adjust the current subtitle without making the previous/next one longer.
  • Sometimes, after reviewing a draft, I realize I need a little more time.
    • I can specify visuals like a video, animated GIF, or an image by adding a [[file:...]] link in the comment for a subtitle. That visual will be used until the next visual is specified in a comment on a different subtitle. subed-record-compile-video can automatically speed up video clips to fit in the time for the current audio segment, which is the set of subtitles before the next visual is defined. After I compile and review the video, sometimes I notice that something goes by too quickly.
  • If the original audio has some silence, I can just copy and paste it.
    • This can sometimes feel more natural than adding in complete silence.
  • If not, I can pad left or pad right to add some silence.
    • I added a new feature so that I could specify something like #+PAD_RIGHT: 1.5 in a comment to add 1.5 seconds of silence after the audio specified by that subtitle.
  • I can try the flow of some sections
    • I can select a region and then use M-x subed-record-compile-try-flow to play the audio or C-u M-x subed-record-compile-try-flow to play the audio+video for that region.
  • and compile the video when I'm ready.
    • subed-record-compile-video compiles the video to the file specified in #+OUTPUT: filename. ffmpeg is very arcane, so I'm glad I can simplify my use of it with Emacs Lisp.
  • Emacs can do almost anything. Yay Emacs!
    • Non-linear audio and video editing is actually pretty fun in a text editor, especially when I can just use M-x vundo to navigate my undo history.

Links:

Related:

View org source for this post
-1:-- #YayEmacs 9: Trimming/adding silences to get to a target; subed-record-sum-time (Post Sacha Chua)--L0--C0--2025-01-09T15:24:29.000Z

Listful Andrew: Sparkly — Create multiline sparks ▁▂▆▄▃▇▅ (Emacs package)

With Sparkly you can transform numbers into multiline sparks, which can help you quickly visualize data. You have control over the number of lines to use.
-1:-- Sparkly — Create multiline sparks ▁▂▆▄▃▇▅ (Emacs package) (Post Listful Andrew)--L0--C0--2025-01-09T08:00:00.000Z

Protesilaos Stavrou: Emacs: custom Oxford University calendar weeks

Over at Oxford University they use their own calendaring scheme to label the terms and weeks of their school year. Why? Because they can! And why would some guy in the mountains of Cyprus care? Because it’s fun to write Emacs Lisp!

The academic year is divided into three periods of teaching and three of vacation. The latter have no special names, while the former are called “Michaelmas”, “Hilary”, and “Trinity”. Each teaching term consists of a few weeks whose numbering starts from 1.

With some custom code, we can configure the Emacs M-x calendar to tell us (i) which term we are in for the current month of the Oxford academic year, if any, (ii) which term and week number we are in, if any, and (iii) which is the standard week number.

The generic ‘M-x calendar’ 😴

Here is how it looks without any customisations. Serviceable, but not conducive to the Oxford culture.

Generic Emacs calendar

The Oxford-friendly ‘M-x calendar’ 🎓

It is busier, for sure, though this is what you get for being at Oxford. Notice that when there is nothing Oxford-related, we just show the regular calendar information.

Oxford Emacs calendar

The code

UPDATE 2025-01-09 18:36 +0200: I revised a few lines of code to (i) work with either Sunday or Monday as the first day of the week, (ii) not show any Oxford week beyond the years specified in prot-oxford-dates.


I wrote this over the course of a few hours. It may be refined here and there, but I think it is already good enough. The only major improvement would be to implement the formula that Oxford uses to derive their dates. As I do not know it, the prot-oxford-dates have to be updated manually each year.

;; NOTE 2025-01-09: Perhaps there is some formula to always get the
;; dates, but I am not aware of it.  As such, these dates need to be
;; updated at the start of each school year.
;;
;; Source: <https://www.ox.ac.uk/about/facts-and-figures/dates-of-term>.
(defvar prot-oxford-dates
  '((michaelmas  (10 13 2024)  (12 7 2024))
    (hilary      (1  19 2025)  (3 15 2025))
    (trinity     (4  27 2025)  (6 21 2025)))
  "Alist of Oxford calendar terms with start and end date.
Each element is of the form (NAME START END), where NAME is the name of
the term, as a symbol, START is the start date and END is the end date.
START and END each are of the form (month day year), where each element
is a number.")

(defun prot-oxford--get-iso-week (date)
  "Get the ISO week of DATE.
DATE is a list of the form (month day year)."
  (unless (calendar-date-is-valid-p date)
    (error "The date `%s' does not conform with `calendar-date-is-valid-p'" date))
  (car
   (calendar-iso-from-absolute
    (calendar-absolute-from-gregorian date))))

(defun prot-oxford--get-term-week (term-start-week term-end-week iso-week prefix)
  "Return the number of the Oxford term week or nil.
Determine the week given TERM-START-WEEK and TERM-END-WEEK as Gregorian
week numbers.  Compare ISO-WEEK to them.

If `calendar-week-start-day' is a Monday, then start counting weeks from
0, because Oxford weeks start from Sunday (otherwise, Week 1 includes 6
days before the first Sunday).

When returning the number, concatenate it with PREFIX.  PREFIX is a
single letter string.  A longer PREFIX is trimmed accordingly."
  (when (and term-start-week term-end-week iso-week)
    (when-let* ((offset (pcase calendar-week-start-day
                          (0 1)
                          (1 0)))
                (number (cond
                         ((> iso-week term-end-week)
                          nil)
                         ((= term-start-week iso-week)
                          offset)
                         ((< term-start-week iso-week)
                          (+ (- iso-week term-start-week) offset))))
                (pre (if (> (length prefix) 1)
                         (substring prefix 0 1)
                       prefix)))
      (concat pre (number-to-string number)))))

(defun prot-oxford--get-term-weeks (term year)
  "Return Oxford TERM start and end week numbers as a list.
Check YEAR to determine if the date is out of bonds of the
`prot-oxford-dates'."
  (pcase-let* ((`(,beg-date ,end-date) (alist-get term prot-oxford-dates))
               (`(,_ ,_ ,term-year) beg-date)
               (beg-week (prot-oxford--get-iso-week beg-date))
               (end-week (prot-oxford--get-iso-week end-date)))
    (when (= term-year year)
      (list beg-week end-week))))

(defface prot-oxford-term-indicator
  '((((class color) (min-colors 88) (background light))
     :foreground "#224499")
    (((class color) (min-colors 88) (background dark))
     :foreground "#afc9f3")
    (t :inherit shadow))
  "Face to style the Oxford term indicator.")

(defface prot-oxford-regular-week
  '((t :inherit shadow))
  "Face to style the regular week.")

(defun prot-oxford-week (month day year)
  "Use MONTH DAY YEAR to determine current week.
Derive the Oxford term week based on the `prot-oxford-dates'."
  (pcase-let* ((`(,m-w-beg ,m-w-end) (prot-oxford--get-term-weeks 'michaelmas year))
               (`(,h-w-beg ,h-w-end) (prot-oxford--get-term-weeks 'hilary year))
               (`(,t-w-beg ,t-w-end) (prot-oxford--get-term-weeks 'trinity year))
               (gregorian-week (prot-oxford--get-iso-week (list month day year)))
               (oxford-week (or (prot-oxford--get-term-week m-w-beg m-w-end gregorian-week "M")
                                (prot-oxford--get-term-week h-w-beg h-w-end gregorian-week "H")
                                (prot-oxford--get-term-week t-w-beg t-w-end gregorian-week "T")
                                "")))
    (format " %2s  %2s  "
            (propertize oxford-week 'face 'prot-oxford-term-indicator)
            (propertize (format "%2s" gregorian-week) 'face 'prot-oxford-regular-week))))

(defun prot-oxford--get-term-month (term-name term-start-month term-end-month month)
  "Return the TERM-NAME of the term month or nil.
Determine the name given TERM-START-MONTH and TERM-END-MONTH as month
numbers.  Compare MONTH to them."
  (when-let* ((number (cond
                       ((> month term-end-month)
                        nil)
                       ((= term-start-month month)
                        1)
                       ((< term-start-month month)
                        (+ (- month term-start-month) 1)))))
    term-name))

(defun prot-oxford--get-months (term)
  "Get start and end months of `prot-oxford-dates' TERM as a list."
  (mapcar #'car (alist-get term prot-oxford-dates)))

(defun prot-oxford-month (year month)
  "Return abbreviated name of MONTH for YEAR.
Append the Oxford term name based on the `prot-oxford-dates'."
  (pcase-let* ((`(,m-beg ,m-end) (prot-oxford--get-months 'michaelmas))
               (`(,h-beg ,h-end) (prot-oxford--get-months 'hilary))
               (`(,t-beg ,t-end) (prot-oxford--get-months 'trinity))
               (oxford-term-name (or (prot-oxford--get-term-month "Michael" m-beg m-end month)
                                     (prot-oxford--get-term-month "Hilary" h-beg h-end month)
                                     (prot-oxford--get-term-month "Trinity" t-beg t-end month)
                                     "")))
    (format "%s %s %s"
            (propertize (calendar-month-name month :abbreviate) 'face 'calendar-month-header)
            (propertize (format "%s" year) 'face 'calendar-month-header)
            (propertize oxford-term-name 'face 'prot-oxford-term-indicator))))

(defun prot-oxford-intermonth-header ()
  "Return string for `calendar-intermonth-header'."
  (format "%s %s"
          (propertize "OX" 'face 'prot-oxford-term-indicator)
          (propertize "Week" 'face 'shadow)))

(setopt calendar-left-margin 10
        ;; Oxford assumes Sunday starts the week, but we want to work
        ;; with the ISO commercial dates, so Monday (1) is the first
        ;; day of the week.  But Sunday (0) will still work.
        calendar-week-start-day 1
        calendar-intermonth-spacing 12
        calendar-intermonth-header (prot-oxford-intermonth-header)
        calendar-intermonth-text '(prot-oxford-week month day year)
        calendar-month-header '(prot-oxford-month year month))
-1:-- Emacs: custom Oxford University calendar weeks (Post Protesilaos Stavrou)--L0--C0--2025-01-09T00:00:00.000Z

James Dyer: Ediff Comparing Regions

ediff is one of my favourite tools in Emacs and I have recently found myself not only using ediff-buffers and vc-ediff often but also now having the need to compare two regions.

For example, when developing new Elisp functions, I generally modify the bottom of my tangled init file and then evaluate/edit as necessary, or sometimes through the scratch buffer.

However, for something more complex, for example, at the moment, I am trying to generate an RSS feed from my Hugo-based blog’s Org file through org-publish. In this case, I prefer creating a separate directory where I can experiment with example files, generated files, and temporarily manage everything under version control.

At the end of development, I could simply paste my function(s) back into my main configuration, however, as a software engineer, I am accustomed to using merging tools, so what better way to handle this than with ediff

Given my development functions and my init file, I can’t compare entire files to copy/merge the changes across, and VC in this case is not helpful. So, how do I compare two regions? In fact, is it even possible in Emacs?

Well, yes, yes it is! (of course)

ediff-regions-linewise

Run Ediff on a pair of regions in specified buffers. BUFFER-A and BUFFER-B are the buffers to be compared. Regions (i.e., point and mark) can be set in advance or marked interactively. Each region is enlarged to contain full lines.

and my keybinding is:

(global-set-key (kbd "M-s +") #'ediff-regions-linewise)

Note: at the moment I only ever run ediff-regions-linewise but the documentation recommends using ediff-regions-wordwise for smaller regions. For this example I shall be using ediff-regions-linewise only.

Documentation for ediff-regions-wordwise

The ediff-regions-linewise function is effective for large regions, over 100-200 lines.

For small regions, use ‘ediff-regions-wordwise’.

Initially, I prefer to have a horizontal split with the two files containing the regions I would like to compare, and then place the point in the left-hand window.

When ediff-regions-linewise is then activated, that is the order the regions are set up, from left to right.

Note: According to the documentation, it seems possible to set up the two regions using marks before the call, after which you will go directly into an ediff comparison rather than the interactive region described below. However, I have not yet been able to get the region comparison to work this way.

Ok, lets do a region comparison!


  1. Run

    • Open both buffers side-by-side, Buffer A : Buffer B
    • Run M-x ediff-regions-linewise
    • Select each side-by-side buffer, by <RET>, <RET>
  2. Mark Buffer A’s Region

    • When prompted for “Region A:”, Buffer A will be automatically switched to
    • Now mark the region for comparison
    • Once the region is marked, hit C-M-c
  3. Mark Buffer B’s Region

    • Similarly, when prompted for “Region B:”, Buffer B will be automatically switched to
    • Now mark the region for comparison
    • Once the region is marked, hit C-M-c

Now the usual familiar ediff comparison begins.


Here is a little visual example set up with a simple paragraph difference:

In this case Buffer A and Buffer B are set up side-by-side and with the process for a region comparison as described above on 2. Mark Buffer A’s Region that I have deliberately made slightly different for Buffer B.

Here is the comparison result:


I have something that seems to work for me at the moment, but I would really like to fully understand the difference between a linewise and a wordwise comparison. I understand that wordwise comparison could be computationally expensive on large blocks, but what benefits do I gain from using a smaller wordwise comparison compared to a linewise comparison?

Also, although the interactive region selection works well, I really want to figure out how to set up the regions initially.

If anyone knows answers to these questions, just drop in a comment!

-1:-- Ediff Comparing Regions (Post James Dyer)--L0--C0--2025-01-08T15:30:00.000Z

Irreal: Extracting Email Addresses

Mike Zamansky has a late Christmas present for us but, as they say, better late than never. This particular present is another video in his Using Emacs series. In this video he considers the problem of extracting all email address from an Emacs buffer.

The original problem was that Zamansky had several documents, including spreadsheets, that contained email address and he wanted to get a list of all those addresses for further processing. Being an Emacser, his first thought was to extract all the text into an Emacs buffer and work from there.

That extraction amounted to cut and pasting, which, while not particularly satisfying, is probably optimal considering the many types of documents involved and the fact that is was a one-off. Once he has all the text into an Emacs buffer, the rest is relatively easy. Zamansky’s solution was to put together a bit of Elisp to do the job. The value of the video is his walking us through the process of doing that.

One of the things I really like about Zamansky’s videos is that he doesn’t clean them up. You see his mistakes and fat fingering and that helps you understand the process much better than just viewing a polished video of the final result.

Accomplished_Will_31, in a reddit comment, notes that occur can do a lot of this more easily. That’s probably true but the real value of the video is seeing how Zamansky puts together his Elisp solution. His process is applicable to a wide range of problems whether or not occur offers a shorter solution.

The video is 17 minutes, 56 seconds so plan accordingly. Like all of his videos, this one is well worth spending the time on.

-1:-- Extracting Email Addresses (Post Irreal)--L0--C0--2025-01-08T15:20:01.000Z

Protesilaos Stavrou: Emacs: the next Fontaine version will use a custom theme

As part of the current development target of my fontaine package, the way I implement changes to fonts is done via a custom theme.

commit 69e80d4a93b28804f3b9d8a0b4328952c2f0d568
Author: Protesilaos Stavrou <info@protesilaos.com>
Date:   Wed Jan 8 10:42:35 2025 +0200

  BREAKING use a custom theme instead of 'set-face-attribute' internals

 fontaine.el | 179 +++++++++++++++++++++---------------------------------------
 1 file changed, 63 insertions(+), 116 deletions(-)

Before, I was relying on the internals of set-face-attribute which worked decently for the most part but required manual intervention to persist the face attributes between theme changes. Whereas the custom theme shall remain in effect no matter what, thus reducing complexity.

Furthermore, the custom theme allows me to declare the display specification in which the given face attributes (i.e. the font styles) apply. I can thus specify that these are for a graphical Emacs frame only.

A potential advantage is the ability to modify any face, even if it is not initialised, whereas set-face-attribute requires the face be defined, else it produces an error. This potential is not realised for the time being because there is no face of the sort that Fontaine affects. All the faces it modifies are loaded eagerly by Emacs. If I need to cover more faces though, it will be straightforward.

For users, the only obvious effect of this transition is the discontinuation of the option to set a Fontaine preset per frame. All face attributes are now always applied to all frames. I am doing this because the old design did not work reliably in all cases and was a niche feature anyway.

I am not aware of any regressions, though I continue to test the package. If you do try it before the new version is out, please let me know of any possible bugs or other improvements we can make.

About Fontaine

Fontaine allows the user to define detailed font configurations and set them on demand. For example, one can have a regular-editing preset and another for presentation-mode (these are arbitrary, user-defined symbols): the former uses small fonts which are optimised for writing, while the latter applies typefaces that are pleasant to read at comfortable point sizes.

-1:-- Emacs: the next Fontaine version will use a custom theme (Post Protesilaos Stavrou)--L0--C0--2025-01-08T00:00:00.000Z

Stefan van der Walt: Pomodoros with org-timer

A New Year! Create the 2025.org journal file, channel all that post-break energy, and get ready to be Super Productive™! 💪 Oh, what’s this… a post on how to track Pomodoros using org mode. 👀 Of course, I didn’t bite. Of course not.

Productivity people sure spend a lot of time writing a lot about little; and the Pomodoro technique is no exception. It can be summarized as:

Set a timer, get to work, take a break. Repeat, take a break.

There’s way too much productivity literature out there, and much of it comes down to: find a way to get your butt in the seat and start [writing/typing/reading/editing/…]. The new wave of productivity literature, or “anti-productivity” literature, tells us to face the facts: you’re never going to finish your TODO list, so give up already, be in the present, and focus on what’s most important (or, whatever, just be present).

Sometimes, I get in the flow easily, and don’t need any tricks. But other times, especially with those slightly-mundane-tasks-that-still-need-to-get-done, I need a little nudge not get started and not get distracted (by something more interesting which, in this case, may well be everything).

For me, the Pomodoro method is helpful for that purpose. And, because distraction abounds, it’s helpful to have the timer as well as the topic you’re supposed to be focused on displayed somewhere visible.

When I saw Charl’s post, I was delighted because:

  1. It relies on org-mode, which I already use;
  2. it does everything I used org-pomodoro for, but more simply;
  3. it provides an easy path towards integration with waybar (he uses xbar for macOS, but same idea); and,
  4. as observed by Charl, it does not interfere with other org clocks.

So, what follows here is a slight modification of Charl’s method, which in turn combines previous approaches by David Wilson (System Crafters) and Xiang Ji.

Emacs configuration: basic #

(require 'org-timer)

;; If you want sound when the pomodoro is over
(setq org-clock-sound (expand-file-name "~/sounds/bell.wav"))

;; And if you'd like to have a keyboard shortcut for starting a Pomodoro
(defun org-timer-start-pomodoro ()
  (interactive)
  (setq current-prefix-arg 25)
  (call-interactively 'org-timer-set-timer))
(global-set-key (kbd "C-c P") 'org-timer-start-pomodoro)

Now, when you press C-c P, a new pomodoro timer is started (you’ll see it in the mode line). You can also start it with org-timer-set-timer. After 25 minutes, when the Pomodoro is done, a system notification is displayed. Tada!

Display pomodoro status on waybar #

To display org-timer’s status on waybar, we’ll query emacs (via emacsclient1) for its current value.

Here’s the function, org-timer-waybar-repr, which generates the status text:

(defun html-escape-string (string)
  "Escape special characters in STRING for HTML."
  (let ((result ""))
    (dolist (char (string-to-list string) result)
      (setq result
            (concat result
                    (pcase char
                      (?\& "&amp;")
                      (?\< "&lt;")
                      (?\> "&gt;")
                      (?\" "&quot;")
                      (?\' "&#39;")
                      (_ (char-to-string char))))))))

(defun org-timer-minutes-to-string ()
  "Remaining org-timer minutes, rounded to nearest minute, as string."
  (let* ((time-string (org-timer-value-string))
         (parts (split-string time-string ":"))
         (hours (string-to-number (nth 0 parts)))
         (minutes (string-to-number (nth 1 parts)))
         (seconds (string-to-number (nth 2 parts)))
         (total-minutes (+ (* hours 60) minutes (/ seconds 60.0))))
    (number-to-string (round total-minutes))))

(defun org-timer-waybar-repr ()
  "Format org-timer status for waybar"
  (if (or (not (boundp 'org-timer-countdown-timer)) (not org-timer-countdown-timer))
    "🤗"
    (html-escape-string
      (concat
        "🍅 "(org-timer-minutes-to-string)
        "  🎯 " (org-link-display-format
                  (substring-no-properties org-timer-countdown-timer-title))))))

Next, we need a script, say org-timer-remaining, to access this text:

#/bin/sh
emacsclient --eval '(org-timer-waybar-repr)' | sed 's/"//g'

(That sed bit is to strip the surrounding quotes from the resulting string.)

To display it in waybar, we need to tell waybar to call the script at a regular interval. In ~/.config/waybar/config:

{
  ...,
  "modules-left": [..., "custom/org_timer"],
  ...,
  "custom/org_timer": {
     "exec": "~/scripts/org-timer-remaining",
     "interval": 30,
     "signal": 8
  }
}

Now, after starting a timer, you should see something like:

Waybar displaying org-timer status

One little trick 🪄: do you notice the "signal": 8 above? You don’t need it, but it’s a mechanism provided by waybar for externally refreshing a widget. In this case, if we send a specific kill signal, the org-timer display will reload:

pkill -RTMIN+8 waybar

You can therefore add the following to your emacs configuration to refresh waybar the moment a pomodoro is created:

(add-hook 'org-timer-set-hook
  (lambda ()
    (start-process "waybar-timer-trigger" nil "pkill" "-RTMIN+8" "waybar")))

Things that didn’t work #

I’d have liked a tomato-themed notification for when my pomodoro expired. I can use notify-send to make that happen:

(defun pomo-notify (MSG &optional TIMEOUT)
  """Display pomodoro notification with notify-send"""
  (apply
    'start-process
    "notify-send" nil "notify-send"
    `(,@(when TIMEOUT (list (format "--expire-time=%d" (* 1000 TIMEOUT))))
       ,MSG)))

(add-hook 'org-timer-done-hook (lambda () (pomo-notify "🍅 org-timer done!")))

However, now I have two notifications popping up: mine, and the org-timer one. I tried to silence the org-timer notification using advice:

(defun suppress-org-notify (orig-fun &rest args)
  (cl-letf (
             ((symbol-function 'org-show-notification) (lambda (&rest _) (ignore)))
           )
    (apply orig-fun args)))
(advice-add 'org-timer--run-countdown-timer :around #'suppress-org-notify)

Alas, my advice fell on deaf ears. Do you perhaps know how to make it work? Let me know!

The end #

En dit is dit. Geniet die tamaties!


Waybar update #

I didn’t like that the monitoring script talked to emacs every 30s, regardless of whether the timer is running. So, here is a modified version that triggers waybar updates only when the timer is started, and pauses them when the timer ends:

org-timer-remaining:

#/bin/sh

function print_status() {
  REPR=`emacsclient --eval '(org-timer-waybar-repr)' | sed 's/"//g'`
  echo $REPR
}

print_status

MONITORING="no"

trap 'MONITORING="yes"' USR1
trap 'MONITORING="no"; print_status' USR2

while true; do
  if [ $MONITORING == "yes" ]; then
    print_status
  fi
  sleep 30 &
  wait $!
done

Because the script now does the polling internally, you invoke it slightly differently from waybar:

    "custom/org_timer": {
        "exec": "~/scripts/org-timer-remaining",
    }

And, because the script handles the signals (instead of waybar), we need to update our hooks too:

(add-hook 'org-timer-set-hook
  (lambda ()
    (start-process
    "waybar-monitor-start" nil "pkill" "-USR1" "-f" "sh .*org-timer-remaining")))

(add-hook 'org-timer-done-hook
  (lambda ()
    (start-process
    "waybar-monitor-pause" nil "pkill" "-USR2" "-f" "sh .*org-timer-remaining")))

  1. Since Emacs 29, the emacsclient server is started automatically! 🙌 ↩︎

-1:-- Pomodoros with org-timer (Post Stefan van der Walt)--L0--C0--2025-01-08T00:00:00.000Z

Irreal: Shopping Lists With Emacs

Kris Carta has a nice post on a problem we all have: handling grocery shopping lists. If you’re single and a geek, this is not a problem. All sorts of solutions suggest themselves. The problem comes when you have a significant other that is not a geek but still likes to organize things on the computer.

If one or the other of you does all the meal planning and shopping it’s not too big a problem but in most households, both partners are making suggestions and adding things to the shopping list. If, in addition, the technical partner is an Emacs geek, the problem becomes how to import the non-technical partner’s suggestion into Emacs and how to export a final list to a smartphone application that either or both can use while shopping.

I wrote about this eight years ago and not a lot has changed. Carta is an Apple user and wanted to produce a shopping list in the iOS Reminders app. That’s what we do at the Irreal bunker too and it works well. All of us here, geek or not, is perfectly capable of using Reminders and marking off items as we buy them.

Carta has a separate wrinkle. He and his wife have always planned their meals in Notion and they wanted to export the items on their meal lists to their shopping list. Even though Carta is new to Emacs, he was able, with help, to figure out how to get the Notion data into Emacs and from there to isolate the food items for the shopping list and export them to Reminders. This leverages Applescript, which is definitely a pain, but provides functionality not available on other platforms.

Here at the bunker, we just enter the list into Reminders directly. That’s easier but not as pleasing to our geek sensibilities.

-1:-- Shopping Lists With Emacs (Post Irreal)--L0--C0--2025-01-07T17:48:48.000Z

Mike Zamansky: Extracting Emails (with Emacs)

Yesterday I found myself in a situation where I had a text document interspersed with a bunch of email addresses and I wanted to extract those email addresses.

Specifically, I had to copy email addresses from a couple of spreadsheets and other sources and I thought it would be faster to copy lines form the sheets instead of email cells as well as just grabbing text from the other sources and then isolating the email addresses in Emacs.

Now, writing a little "extract emails" script to operate on a text file is easy enough. You can search for an email address using a regular expression. Regular expressions are a text pattern matching language that most programming language support. I used something like the following regular expression to identify an email address:

[a-zA-Z0-9\.]+@[a-zA-Z0-9]+\.[a-zA-Z]{3}

The first [a-zA-Z0-9\.]+ matches a sequence of one or more characters that are letters, numbers or a period (the slash before the period is to escape it - prevent using the . for its special regular expression meaning). That's followed by the @ symbol, a second set of characters, this time without the period, then a dot and then three more characters.

This will match most email addresses.

In Clojure, I'd use the function re-seq which would take a string (presumably my text file) and return a list of all the matches. That is, all the email addresses. I could do the same in Python with re.findall.

In an editor, at least in Emacs, it's a little trickier. You can search for a regular expression but that just finds the next email address. I'd then have to manually copy it and then repeat the process.

I guess a keyboard macro could do the trick but easier would be to just write an elisp function and extend Emacs.

The video shows the whole walkthrough but the core of the routine is using the search-forward-regexp function which searches for the next occurrence of an email match. I then add that email address to a list. Putting it all in a while loop, you get:

(while (search-forward-regexp regexp nil t 1)
 (push (match-string-no-properties 0) matches))

The match-string-no-properties grabs the actual text of the match.

There's a little more detail but basically I wrote an equivalent of re-seq for an Emacs buffer - it grabs everything that matches a regular expression and returns all the matches in a list.

Then, it was a simple matter of looping over that list and inserting the results. I ultimately named the routine extract-emails. So now, I can be in any Emacs buffer, copy over all my source email material, run the command, and I'll have all the emails together one per line ready to be pasted into an email client.

I'm thinking that I should probably give the routine an additional optional parameter - have it extract emails by default but allow the user to pass in any regular expression.

Just another reason why Emacs is such a powerful editor.

Here's the video, enjoy:

-1:-- Extracting Emails (with Emacs) (Post Mike Zamansky)--L0--C0--2025-01-07T13:25:00.000Z

Irreal: Recognizing Repeated Tasks In The Org Agenda

Thanks to Alex Popescu for pointing me to this nice post from Wai Hon about marking repeated tasks in the agenda listing. Why would you want to do that? Han explains it in his post: if you mark a repeated task as done but forget to readd the repeater, the task can disappear. Marking agenda tasks as repeated helps to prevent that.

Fixing the problem is pretty easy but involves a few lines of Elisp. Take a look at Han’s code for the details. It’s only a few lines and relatively simple. If you object to having to use Elisp, Han provides a couple of alternative solutions one of which doesn’t involve writing Elisp.

One could dismiss all this as a non-problem or at least a problem too small to worry about but we Emacers are all about reducing friction wherever we find it and this is a nice example of that. Regardless, if you sometimes find yourself having to base your next action on whether or not the current task is repeating, take a look at Han’s post.

-1:-- Recognizing Repeated Tasks In The Org Agenda (Post Irreal)--L0--C0--2025-01-06T16:29:24.000Z

Sacha Chua: 2025-01-06 Emacs news

[2025-01-06 Mon]: Fix typo in exitter's name

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, communick.news, 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
-1:-- 2025-01-06 Emacs news (Post Sacha Chua)--L0--C0--2025-01-06T15:05:28.000Z

Protesilaos Stavrou: Emacs: spacious-padding version 0.6.0

This package provides a global minor mode to increase the spacing/padding of Emacs windows and frames. The idea is to make editing and reading feel more comfortable. Enable the mode with M-x spacious-padding-mode. Adjust the exact spacing values by modifying the user option spacious-padding-widths.

Inspiration for this package comes from Nicolas Rougier’s impressive designs and Daniel Mendler’s org-modern package.

Release notes below.


This is a small release that makes some minor refinements. The package is otherwise stable and works as expected.

We use the spacious-padding theme instead of user

This is an internal detail with how we implement the “spacious” changes to the faces we cover. Before, we would override anything that the user would explicitly configure with custom-set-faces, either via their Elisp code or through the Custom User Interface and the snippet it automatically generates.

By storing our changes in the bespoke spacious-padding theme instead of the special user theme, we do not override the user’s preference. This is the right design in principle, though users may no longer get the same styles as before because faces they had configured before will now override what spacious-padding-mode does.

When in doubt, search your configuration for custom-set-faces, comment out the whole block, try to re-enable spacious-padding-mode, and see if the results are okay.

Thanks to Martin Marshal for making the initial change in pull request 26: https://github.com/protesilaos/spacious-padding/pull/26. The change is within the 15-line limit of permitted changes without the need for copyright assignment to the Free Software Foundation. I then followed it up with the creation of the ~spacious-padding theme.

The tab-line-tab-current face is supported

This means that tab-line-mode will look consistent when spacious-padding-mode is enabled, instead of some tabs looks smaller/different.

spacious-padding-mode works with window-divider-mode

Thanks to Pierre Baille and Tobias Tschinkowitz for bringing up the problem in issue 17: https://github.com/protesilaos/spacious-padding/issues/17.

-1:-- Emacs: spacious-padding version 0.6.0 (Post Protesilaos Stavrou)--L0--C0--2025-01-06T00:00:00.000Z

Matt Maguire: Using Org Mode / Latex for Lesson Notes

Over the past year, I have been using \(\LaTeX\) together with Emacs to produce lesson notes for some of my classes. One reason I went with Emacs instead of TeX Studio and the like was that Emacs provided a lot of shortcuts and completion features. I was finding though that \(\LaTeX\) documents can sometimes become a bit verbose, which made me wonder about ways to make it easier to navigate the documents.

-1:-- Using Org Mode / Latex for Lesson Notes (Post Matt Maguire)--L0--C0--2025-01-06T00:00:00.000Z

Irreal: Freewrite Mode

As most of you know by now, I have a fascination with how non-technical people use Emacs. I’m particularly interested in how writers use it. Certainly there are some world class writers who use (or used) Emacs: Neal Stephenson and Vernor Vinge spring to mind but there are doubtless many others.

A fundamental problem for writers is coming up with ideas. One technique for dealing with this is called “free writing”. The idea is that you simply write whatever comes into your mind as fast as you can without worrying about editing or even correcting typos.

There are some problems. As Chris Maiorana explains, there’s a strong urge to fix things as you write. That’s not too bad when you write in long hand but when you’re typing on a computer, as most writers these days do, there can be an overpowering urge to hit the BackSpace key if only to fix a typo.

Maiorana has a solution. At least for Emacs users. It’s a simple mode that natters at the user whenever the BackSpace key is used. As Maiorana says, there’s plenty of room for improvement. After all, there are ways of deleting characters other than BackSpace but those are all details and, really, mostly you’re going to use BackSpace in those circumstances.

If you’re writing, or trying to write, fiction, you may want to try the free writing technique and if you do, you might want to use Maiorana’s mode to keep you honest.

-1:-- Freewrite Mode (Post Irreal)--L0--C0--2025-01-05T16:23:27.000Z

Marcin Borkowski: Killing all buffers visiting files in a certain directory

When I leave the office, I generally close all work-related things – I sign out of Slack, shut down the virtual machine, and unmount the encrypted filesystem with work data. Obviously, I also want to kill all buffers “related to” that filesystem, for example all buffers visiting files there.
-1:-- Killing all buffers visiting files in a certain directory (Post Marcin Borkowski)--L0--C0--2025-01-05T12:33:39.000Z

Alex Popescu: Change How Repeated Tasks Are Displayed in Org Agenda

Wai Hon provides a complete solution to customizing how repeated tasks are displayed in Org Agenda:

In the Org agenda view, repeated and non-repeated tasks look the same. I always hesitate to mark a repeated task as done because I might forget to set the repeater (e.g., +3d) and the task would then never reappear.

I haven’t thought about this issue. That doesn’t take away anything from this very interesting solution though.

-1:-- Change How Repeated Tasks Are Displayed in Org Agenda (Post Alex Popescu)--L0--C0--2025-01-04T11:19:25.000Z

Wai Hon: Distinguish Repeated Tasks in Org Agenda

Problem: Repeated or Not?

In the Org agenda view, repeated and non-repeated tasks look the same. I always hesitate to mark a repeated task as done because I might forget to set the repeater (e.g., ++1w) and the task would then never reappear.

It would be great if repeated tasks are distinguishable in the Org agenda. For example, showing the repeater (e.g., ++1w) as part of the entry.

Solution: Setting Prefix Format

Thanks to the tip from yantar92 (our new Org maintainer), I am able to add the repeater to Org agenda with org-agenda-prefix-format:

  • Shorten the scheduled leaders to reserve space for repeater.
  • Add a function to get the formatted repeater.
  • Include the formatted repeater to org-agenda-prefix-format.

It needs only a few lines of Elisp:

;; Shorten the leaders to reserve spaces for the repeater.
(setq org-agenda-scheduled-leaders '("Sched" "S.%2dx"))
(setq org-agenda-deadline-leaders '("Deadl" "In%2dd" "D.%2dx"))

(defun my/org-agenda-repeater ()
 "The repeater shown in org-agenda-prefix for agenda."
 (if (org-before-first-heading-p)
 "-------" ; fill the time grid
 (format "%5s: " (or (org-get-repeat) ""))))

;; Add `my/org-agenda-repeater' to the agenda prefix.
(setcdr (assoc 'agenda org-agenda-prefix-format)
 " %i %-12:c%?-12t%s%(my/org-agenda-repeater)")

Alternatives

Using the Agenda Finalize Hook (Hacky)

Before yantar92’s tip, I had a hackier solution (gist):

  • Fetch the repeater using the Org entry as text properties
  • Use org-agenda-finalize-hook which happens after the agenda view is built but before it is displayed, to add the repeater.

Using Column View (Cluttered)

Before that, I had another solution using the column view (M-x org-agenda-columns) by showing the scheduled date as a column by setting the COLUMNS attribute.

(setq org-columns-default-format-for-agenda
 "%TODO %PRIORITY(Pri) %60ITEM(Task) %SCHEDULED")

While it works and is built-in, the strings are too verbose and hard to parse. Hence, I looked for the above solution witch custom Elisp.

-1:-- Distinguish Repeated Tasks in Org Agenda (Post Wai Hon)--L0--C0--2025-01-04T02:00:00.000Z

Kisaragi Hiu: Review for September 2024

2025-01-04: So it's been a while since I wrote a “monthly” review. This is partially because I'm again trying to finish my college studies. Nevertheless, I would like to at least publish what I wrote in September. Open source contributions Make @pkgjs/parseargs work on gjs and other runtimes that don't have global (understandably, since global is a Node-specific name) (@pkgjs/parseargs#158). This is the same parseArgs that went into node:util which strikes an amazing balance between being powerful (named and positional arguments, full types) and being simple (no subcommands, not a whole framework for defining commands).
-1:-- Review for September 2024 (Post Kisaragi Hiu)--L0--C0--2025-01-04T00:00:00.000Z

Kris Carta: Shopping (Lists) with Emacs

At home, we're tying to re-establish a good weekly meal planning & grocery shopping routine.

For many years the steps have been:

  1. Open up Notion
  2. Make a new page like "Meal Plan: Week 2"
  3. Write down what we'll eat on each day
  4. For each meal, list out the items we need to get for it
  5. (Add anything else we need to get for the week, like detergent)

The end result looks something like this: Meal plan in Notion


With the meal plan written down, now we need to make a shopping list.

We've started using a shared "Shopping" list in Apple Reminders. The UX is great, and it all works quite well when we're actively shopping.1 2 We also have widgets on our phones so we can check the list with some quick gestures.

The irritating part is getting the items into the shopping list. I struggled to find a satisfying way to automate this part 😣3 (until now!) .

At an impasse, I had given up, and kept awkwardly copy-pasting the ingredients from Notion over to Apple Reminders - of course this felt wrong every time I did it!

Recently I came across this page showing how to invoke AppleScript from Emacs (because of course that's in Emacs!).

I loathe AppleScript far more than is probably healthy, but sometimes it's the right - i.e. only - tool for the job.

After many iterations and some consultations with my private Emacs tutor, ChatGPT, I scratched together a function to add an item into the Shopping list in Apple Reminders:

Note: if this code can be improved, let me know! I'm still a beginner here and guidance is greatly appreciated.

(defun my-add-item-to-shopping-reminders (item)
  "Prompt for ITEM and add it to the ‘Shopping’ list in Apple Reminders."
  (interactive "sAdd item to Shopping: ")
  (let ((as-command (format
	"tell application \"Reminders\"
		tell list \"Shopping\"
			make new reminder with properties {name:\"%s\"}
		end tell
	end tell"
	item)))
    (message "AppleScript: %s" as-command)  ;; Debug
    (shell-command (format "osascript -e '%s'" as-command))))

I ended up going with shell-command and osascript because I couldn't get past permissions issues between Emacs and Reminders when using do-applescript - I spent a lot of time troubleshooting this before finally giving up 😞

With this milestone reached, there was just one step remaining: process the shopping list items, using the power of Emacs 🙌

With a deal great more iterations and head-scratching, I managed to make a script that:

  1. Parses an Org buffer into an AST
  2. Gets all the list items from the AST
  3. Gets the content of the list items and pushes it into a list
  4. For each item in the list of content, calls AppleScript to add it to the Apple Reminders "Shopping" list.

Sidebar: The hardest part in my Emacs learning has been finding a good reference flow for Emacs / library API's. I know I can look things up in Emacs with C-h, but if I don't know what to look up, then I end up online and searching around aimlessly. This Org Element API is quite helpful, but I wish I knew how to 'discover' it in Emacs, and I still wish it were better organized and more detailed (without being able to put my finger on what exactly I feel like I'm missing).

The script:

(defun my-add-org-items-to-shopping-reminders ()
  "Extract all list items from the current Org buffer and add them to the 'Shopping' list in Apple Reminders."
  (interactive)
  (require 'org-element)
  ;; Extract all list items
  (let ((parsed (org-element-parse-buffer))
        (items '()))
    (org-element-map parsed 'item
      (lambda (item)
        (let ((content (org-element-property :contents-begin item))
              (end (org-element-property :contents-end item)))
          (when (and content end)
            (push (buffer-substring-no-properties content end) items)))))
    ;; Add each item to Apple Reminders
    (dolist (item (reverse items))
      (let ((as-command (format
                         "tell application \"Reminders\"
                            tell list \"Shopping\"
                              make new reminder with properties {name:\"%s\"}
                            end tell
                          end tell"
                         item)))
        (message "Adding to Reminders: %s" item)  ;; Debug
        (shell-command (format "osascript -e '%s'" as-command)))))
  (message "All items added to the 'Shopping' list."))

Now, I have a new routine:

  1. Make the Meal Plan in Notion as usual, with the lists of items we need to get at the store: Meal plan in Notion

  2. Copy the entire Notion page and paste it into an Emacs buffer (even *scratch* works for this): Meal plan in Markdown

  3. Convert Notion's Markdown into Org4 Meal plan in Org

  4. Run the command to send the list items to Apple Reminders: Command to send meal plan items to Apple Reminders

  5. Go shopping! 🛒 Shopping list in Apple Reminders

  1. As much as I otherwise love it, Notion has never been 'lightweight' to use on mobile, and is often a real distraction in cases where I just need something, like a checklist to quickly reference and update.

  2. Also, it's important to keep the meal planning in Notion, because my partner, as smart & wonderful as they otherwise are, is not about to start using some nerdy text editor (😿) or a half-baked app I've written myself (we have enough of those already). Much of our family planning, including our recipes, is also done in Notion.

  3. At least one that doesn't require adding an additional service promising to synchronize the two platforms - no thanks!

  4. Not shown: a simple function I have to convert md->org in the same buffer, using a shell command to pandoc. Also, I did have the thought to just process a md buffer, but I don't know of a good parser for that and I avoid writing regexes whenever possible. I'm fine with have one extra command in my process (for now 🙃).

-1:-- Shopping (Lists) with Emacs (Post Kris Carta)--L0--C0--2025-01-04T00:00:00.000Z

Kisaragi Hiu: Review for October 2024 ~ December 2024

2025-01-04: Again, it's been a while since I updated these. Open source contributions There's like none other than a stalled effort to make a change to the Font Management page in KDE's System Settings. My own projects (Node) kisaragi-hiu/vtt-to-text-paragraphs, a small script utilizing @plussub/srt-vtt-parser to read a subtitle file and output the subtitles into a text file, split into paragraphs based on the length of pauses (Bun) kisaragi-hiu/thai-numbers-deck An Anki deck for Thai numerals and a script for creating it (utilizing npm:anki-apkg-export) Translations Between September 2024 and December 2024, I successfully provided the rest of elementary OS's Traditional Chinese translations, although there's still a fair bit of it that's waiting for the next release of many elementary apps.
-1:-- Review for October 2024 ~ December 2024 (Post Kisaragi Hiu)--L0--C0--2025-01-03T22:02:59.000Z

James Dyer: Emacs Quick Window Pt 4 - Further Tweaks

Previously, my/quick-window-jump was geared mostly toward managing two or more windows. The behaviour was straightforward:

  • If two windows were open, the function would directly jump to the other window.
  • For more than two windows, a key selection interface would allow you to pick a window to switch to.

However, the function didn’t handle a single-window scenario intelligently. A horizontal split from a single window is something I do very frequently, by frequently, I mean at least every minute! This split is currently bound to its own keybinding, but couldn’t my ace-window defun clone do this? For example, I find that the transition from a single window to a split never happens vertically for me, so why not make my/quick-window-jump perform a horizontal split from a single window and then move the point to that window?

In addition, it is very rare for me to vertically split into more than two columns. I could probably drop my current horizontal split keybinding now. If I do require another horizontal split, the default C-x 3 is already bound to muscle memory.

Lets take this function to the next level of convenience, with a very simple change, simply an extra (length window-list) and the relevant logic.

Below is the updated implementation of my/quick-window-jump:

(defun my/quick-window-jump ()
  "Jump to a window by typing its assigned character label.
If there is only a single window, split it horizontally.
If there are only two windows, jump directly to the other window."
  (interactive)
  (let* ((window-list (window-list nil 'no-mini)))
    (cond
     ;; If there is only a single window, split it horizontally.
     ((= (length window-list) 1)
      (split-window-horizontally)
      (other-window 1)) ;; Move focus to the new window immediately after splitting.
     ;; If there are only two windows, switch to the other one directly.
     ((= (length window-list) 2)
      (select-window (other-window-for-scrolling)))
     ;; Otherwise, present the key selection interface.
     (t
      (let* ((my/quick-window-overlays nil)
             (sorted-windows (sort window-list
                                   (lambda (w1 w2)
                                     (let ((edges1 (window-edges w1))
                                           (edges2 (window-edges w2)))
                                       (or (< (car edges1) (car edges2))
                                           (and (= (car edges1) (car edges2))
                                                (< (cadr edges1) (cadr edges2))))))))
             (window-keys (seq-take '("j" "k" "l" ";" "a" "s" "d" "f")
                                    (length sorted-windows)))
             (window-map (cl-pairlis window-keys sorted-windows)))
        (setq my/quick-window-overlays
              (mapcar (lambda (entry)
                        (let* ((key (car entry))
                               (window (cdr entry))
                               (start (window-start window))
                               (overlay (make-overlay start start (window-buffer window))))
                          (overlay-put overlay 'after-string
                                       (propertize (format "[%s]" key)
                                                   'face '(:foreground "white" :background "blue" :weight bold)))
                          (overlay-put overlay 'window window)
                          overlay))
                      window-map))
        (let ((key (read-key (format "Select window [%s]: " (string-join window-keys ", ")))))
          (mapc #'delete-overlay my/quick-window-overlays)
          (setq my/quick-window-overlays nil)
          (when-let ((selected-window (cdr (assoc (char-to-string key) window-map))))
            (select-window selected-window))))))))

The updated my/quick-window-jump function now adapts to any window configuration dynamically. Here is now an overview on how it now works:

  1. Single Window? Split It Automatically

    • If there’s only one window open, the function splits it horizontally and shifts focus to the new window.
  2. Two Windows? Still Jump Directly

    • The behaviour for two windows remains unchanged. It efficiently switches focus to the other window directly without inefficiencies.
  3. More Than Two Windows? Key Selection Interface

    • When multiple windows are open, the function presents the familiar key selection interface, allowing you to jump exactly where you intend.

Will there be more tweaks?, not sure, but this is the advantage of now creating my own defun for this piece of functionality!

-1:-- Emacs Quick Window Pt 4 - Further Tweaks (Post James Dyer)--L0--C0--2025-01-03T20:20:00.000Z

Listful Andrew: Exemplify-Eval — Create arrowified examples from sexps (Emacs package)

Exemplify-Eval creates Exemplify-ERT–like examples. With it you can easily insert arrows followed by the evaluation of the sexp at point or of all sexps in a region.
-1:-- Exemplify-Eval — Create arrowified examples from sexps (Emacs package) (Post Listful Andrew)--L0--C0--2025-01-03T14:00:00.000Z

Listful Andrew: Exemplify-Align — Align examples' arrows (Emacs package)

Exemplify-Align aligns the arrowheads of Exemplify-ERT–like one-lined examples.
-1:-- Exemplify-Align — Align examples' arrows (Emacs package) (Post Listful Andrew)--L0--C0--2025-01-03T13:50:00.000Z

Protesilaos Stavrou: Emacs: sequence notes with Denote (denote-sequence.el)

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

In this ~21-minute video I demonstrate the new optional extension for Denote that I am developing. The denote-sequence.el is part of the denote package. Its purpose is to streamline the creation of “sequence notes”, i.e. files that have an inherent relationship between them. The sequence is represented as a number.

Note that the code is in-development and will be released as part of Denote version 3.2.0. I still plan to make some refinements, though the core functionality is already done.

About Denote

Denote is a simple note-taking tool for Emacs. It is based on the idea that notes should follow a predictable and descriptive file-naming scheme. The file name must offer a clear indication of what the note is about, without reference to any other metadata. Denote basically streamlines the creation of such files while providing facilities to link between them.

Denote’s file-naming scheme is not limited to “notes”. It can be used for all types of file, including those that are not editable in Emacs, such as videos. Naming files in a consistent way makes their filtering and retrieval considerably easier. Denote provides relevant facilities to rename files, regardless of file type.

-1:-- Emacs: sequence notes with Denote (denote-sequence.el) (Post Protesilaos Stavrou)--L0--C0--2025-01-03T00:00:00.000Z

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