Categories: geek

RSS - Atom - Subscribe via email

Org Mode: Merge top-level items in an item list

| org

I usually summarize Mastodon links, move them to my Emacs News Org file, and then categorize them. Today I accidentically categorized the links while they were still in my Mastodon buffer, so I had two lists with categories. I wanted to write some Emacs Lisp to merge sublists based on the top-level items. I could sort the list alphabetically with C-c ^ (org-sort) and then delete the redundant top-level item lines, but it's fun to tinker with Emacs Lisp.

Example input:

  • Topic A:
    • Item 1
    • Item 2
      • Item 2.1
  • Topic B:
    • Item 3
  • Topic A:
    • Item 4
      • Item 4.1

Example output:

  • Topic B:
    • Item 3
  • Topic A:
    • Item 1
    • Item 2
      • Item 2.1
    • Item 4
      • Item 4.1

The sorting doesn't particularly matter to me, but I want the things under Topic A to be combined. Someday it might be nice to recursively merge other entries (ex: if there's another "Topic A: - Item 2" subitem like "Item 2.2"), but I don't need that yet.

Anyway, we can parse the list with org-list-to-lisp (which can even delete the original list) and recreate it with org-list-to-org, so then it's a matter of transforming the data structure.

(defun my-org-merge-list-entries-at-point ()
  "Merge entries in a nested Org Mode list at point that have the same top-level item text."
  (interactive)
  (save-excursion
    (let* ((list-indentation (save-excursion
                               (goto-char (caar (org-list-struct)))
                               (current-indentation)))
           (list-struct (org-list-to-lisp t))
           (merged-list (my-org-merge-list-entries list-struct)))
      (insert (org-ascii--indent-string (org-list-to-org merged-list) list-indentation)
              "\n"))))

(defun my-org-merge-list-entries (list-struct)
  "Merge an Org list based on its top-level headings"
  (cons (car list-struct)
        (mapcar
         (lambda (g)
           (list
            (car g)
            (let ((list-type (car (car (cdr (car (cdr g))))))
                  (entries (seq-mapcat #'cdar (mapcar #'cdr (cdr g)))))
              (apply #'append (list list-type) entries nil))))
         (seq-group-by #'car (cdr list-struct)))))

A couple of test cases:

(ert-deftest my-org-merge-list-entries ()
  (should
   (equal
    (my-org-merge-list-entries
     '(unordered ("Topic B:" (unordered ("Item 3")))))
    '(unordered ("Topic B:" (unordered ("Item 3"))))))
  (should
   (equal
    (my-org-merge-list-entries
     '(unordered ("Topic B:" (unordered ("Item 3")))
                 ("Topic A:"
                  (unordered ("Item 1")
                             ("Item 2"
                              (unordered ("Item 2.1")))))
                 ("Topic A:"
                  (unordered
                   ("Item 4" (unordered ("Item 4.1")))))))
    '(unordered
      ("Topic B:" (unordered ("Item 3")))
      ("Topic A:"
       (unordered ("Item 1")
                  ("Item 2" (unordered ("Item 2.1")))
                  ("Item 4" (unordered ("Item 4.1")))))))))
View org source for this post

2025-03-17 Emacs news

| emacs, emacs-news

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

View org source for this post

Remove open Org Mode clock entries

| emacs, org

Pedro pointed out that I had some incomplete clock entries in my Emacs configuration. org-resolve-clocks prompts you for what to do with each open clock entry in your Org agenda files and whatever Org Mode files you have open.

If you don't feel like cancelling each clock with C, I also wrote this function to delete all open clocks in the current file.

(defun my-org-delete-open-clocks ()
  (interactive)
  (flush-lines
   (rx
    line-start
    (zero-or-more space)
    "CLOCK:"
    (one-or-more space)
    (regexp org-ts-regexp-inactive)
    (zero-or-more space)
    line-end)))
This is part of my Emacs configuration.
View org source for this post

2025-03-10 Emacs news

| emacs, emacs-news

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

View org source for this post

Getting an Org link URL from a string; debugging regex groups

| elisp, org

Sometimes I want to get the URL from a string whether the string contains a bare URL (https://example.com) or an Org bracketed link ([[https://example.com]] or [[https://example.com][Example]], ignoring any extra non-link text (blah https://example.com blah blah). org-link-any-re seemed like the right regular expression to use, but I started to get a little dizzy looking at all the parenthesis and I couldn't figure out which matching group to use. I tried using re-builder. That highlighted the groups in different colours, but I didn't know what the colours meant. All the matching information is in (match-data), but integer pairs can be a little hard to translate back to substrings. So I wrote an Emacs Lisp function to gave me the matching groups:

(defun my-match-groups (&optional object)
  "Return the matching groups, good for debugging regexps."
  (seq-map-indexed (lambda (entry i)
                     (list i entry
                           (and (car entry)
                                (if object
                                    (substring object (car entry) (cadr entry))
                                  (buffer-substring (car entry) (cadr entry))))))
                   (seq-partition
                    (match-data t)
                    2)))

There's probably a standard way to do this, but I couldn't figure out how to find it.

Anyway, if I give it a string with a bracketed link, I can tell that the URL ends up in group 2:

(let ((text "blah [[https://example.com][example]] blah blah"))
  (when (string-match org-link-any-re text)
    (pp-to-string (my-match-groups text))))
((0 (5 37) "[[https://example.com][example]]")
 (1 (5 37) "[[https://example.com][example]]")
 (2 (7 26) "https://example.com")
 (3 (28 35) "example"))

When I use a string with a bare link, I can see that the URL ends up in group 7:

(let ((text "blah https://example.com blah blah"))
  (when (string-match org-link-any-re text)
    (pp-to-string (my-match-groups text))))
((0 (5 24) "https://example.com")
 (1 (nil nil) nil) (2 (nil nil) nil)
 (3 (nil nil) nil) (4 (nil nil) nil)
 (5 (nil nil) nil) (6 (nil nil) nil)
 (7 (5 24) "https://example.com")
 (8 (5 10) "https") (9 (11 24) "//example.com"))

This makes it so much easier to refer to the right capture group. So now I can use those groups to extract the URL from a string:

(defun my-org-link-url-from-string (s)
  "Return the link URL from S."
  (when (string-match org-link-any-re s)
    (or
     (match-string 7 s)
       (match-string 2 s))))

This is handy when I summarize Emacs News links from Mastodon or from my inbox. Sometimes I add extra text after a link that I've captured from my phone, and I don't want that included in the URL. Sometimes I have a bracketed link that I've copied from org-capture note. Now I don't have to worry about the format. I can just grab the link I want.

View org source for this post

2025-03-03 Emacs news

| emacs, 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

Two weeks with the iPad Air (+ SuperNote A5X and Lenovo P52)

Posted: - Modified: | geek, tech, ipad

[2025-03-01 Sat]: Genaro suggested beorg, so now I've got that on my iPad and it seems to be doing fine for browsing my Org Mode files. I think I'll set up WebDAV on our network-attached storage (NAS) and see what that's like, too.

Also, apparently, it's been about three weeks, whoops!

I've had this iPad Air 13" for about two three weeks now, and I'm slowly settling into how the different pieces of tech can work together and how things can flow.

Text from sketch

Getting used to SuperNote A5X + iPad Air 13"

SuperNote A5X:

  • I prefer it for writing, sketchnotes
    • pen feel
    • strokes: exactly as I expect, no hooks or smoothing
    • lighter weight
    • no backlight
  • Also nice for reading EPUBs, PDFs

iPad Air:

  • Libby for library e-books
  • Procreate, Simply Draw for art
  • NetNewswire for RSS
  • Copying & pasting is nice

Procreate replaces spaces with underscores and doesn't overwrite files. Instead, it adds 2 to the filename.

I want to start building up thought maps so I can see quick summaries and open questions. The laptop is probably the best place to do it.

Org Mode files with sketches, text, and links

  • Export to HTML
  • Also, add export to HTML gallery view?

Private sketches: Available through DS

Private web server on the NAS? I can just run my sketch viewer.

DS: Synology NAS drive

  • Program the server to consolidate files?
  • Consider moving metadata to subdir for easier flipping?

Ideas for next steps:

  • Figure out easier doodling from iPad: resample, autocrop, insert with HTML markup
  • Automate recolour/rename from Dropbox (phone? server?)

On the iPad, I've been mostly using:

Moving data around is a bit of a hodgepodge: internal webserver or Dropbox for the Supernote, Dropbox and Synology's DS Drive for the iPad, and Syncthing for the server. I'm sure that'll settle down eventually as I figure out a better flow.

Untitled_Artwork.jpg

I like how drawing, singing, and playing the piano give me a way to distract myself from the urge to nag A+. Still definitely just starting out, but it's fun anyway. It's nice to be able to breeze through library e-books again, too.

I notice I've been missing the occasional calendar reminder from my phone, so that's probably a sign that I need to (a) set up the iPad for calendar access as well, (b) have more distinct notifications on my phone, and (c) take the phone with me as I go from room to room. On the plus side, that probably means I've been getting in the zone, yay! It seems a little easier to just take the whole stack of phone+Supernote+iPad when I move rooms instead of leaving them in different places and then having to go find them. The laptop is a lot heavier, though, so I tend to move that on an as-needed basis.

I set up a copy of sketches.sachachua.com server on the NAS (so it's just on the local network) pointing to my private sketches directory, so now I can flip through my private sketches fairly easily. They're mostly just various thoughts on parenting and emotions and life. Now I can review them from either my iPad or my laptop, yay!

Ideas for next steps:

  • Keep distracting myself from fretting about A+ by completing various art/music tutorials.
  • Have fun doodling.
  • Contemplate whether I want to read my Org files on the iPad, and what the best way of doing it might be. (Organice? PlainOrg? Just do a PDF/EPUB export of stuff I'm focusing on? - update 2025-03-01: added Beorg)
  • See if A+ is up for field trips to the art gallery or museum.

The iPad Air seems like a good addition. Let's see what I can do with it.

View org source for this post