2023-01-23 Emacs news

| emacs, emacs-news

[2023-01-23 Mon]: Added emacs.si meetup

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, emacs-devel, and lemmy/c/emacs. 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!

2023-01-16 Emacs news

| emacs, emacs-news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, emacs-devel, and lemmy/c/emacs. 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!

Using bug-hunter to quickly find a problem in my long Emacs configuration

| emacs

I noticed that author names in my mastodon.el timeline were no longer getting highlighted. Since it behaved correctly when I used emacs -Q, I knew the problem was somewhere in my configuration. Problem: my configuration is very long. (Uh, my .el seems to have grown to be about 460KB…) I started writing some code to help me bisect it, but fortunately I remembered using a package to do that a long time ago. After a little digging around, I found bug-hunter again. I patched it to allow me to specify a header and footer:

(defvar bug-hunter-header nil "Emacs Lisp sexp to add at the beginning of the file.")
(defvar bug-hunter-footer nil "Emacs Lisp sexp to add at the end of the file.")

(defun bug-hunter--print-to-temp (sexp)
  "Print SEXP to a temp file and return the file name."
  (let ((print-length nil)
        (print-level nil)
        (file (make-temp-file "bug-hunter")))
    (with-temp-file file
      (when bug-hunter-header (print bug-hunter-header (current-buffer)))
      (print sexp (current-buffer))
      (when bug-hunter-footer (print bug-hunter-footer (current-buffer))))
    file))

That allowed me to set up this header and footer:

(setq bug-hunter-header
      '(progn
         (package-initialize)
         (use-package quelpa)
         (use-package quelpa-use-package)
         (use-package hydra :commands defhydra)
         (use-package use-package-hydra)))
(setq bug-hunter-footer '(load-file "/tmp/mastodon-test.el"))

where mastodon-test.el was this file:

(package-initialize)
(add-to-list 'load-path "~/vendor/mastodon.el/lisp")
(require 'mastodon)
(setq mastodon-active-user "sachac"
      mastodon-instance-url "https://emacs.ch")
(mastodon)

I wasn't sure how to use bug-hunter's support for assertions because of the asynchronous retrieval of the Mastodon timeline, but at least I could use that code to set things up.

Then I used bug-hunter-file to split up the Emacs Lisp file tangled out of my literate configuration. All I had to do was wait for the Mastodon timeline to show up, quit Emacs, and answer y if I saw the bug or n if I didn't.

Here's what the result was:

You have asked to do an interactive hunt, here's how it goes.
1) I will start a new Emacs instance, which opens a new frame.
2) You will try to reproduce your problem on the new frame.
3) When you’re done, close that frame.
4) I will ask you if you managed to reproduce the problem.
5) We will repeat steps up to 13 times, so hang tight!
Doing some initial tests...
Initial tests done. Hunting for the cause...
"~/sync/emacs/Sacha.el", line 6893 pos 0:
  The assertion returned the following value here:
    t
  Caused by the following expression:
    (use-package highlight-defined :ensure t :custom
      (highlight-defined-face-use-itself t)
      :hook
      (help-mode . highlight-defined-mode)
      (emacs-lisp-mode . highlight-defined-mode))

That's weird, since highlight-defined shouldn't have affected mastodon, but okay. I'm going to skip using highlight-defined for now.

If you're tracking down a weird change in your Emacs configuration file, you might find bug-hunter useful too. It's available on ELPA, or you can get it from Github.

View org source for this post

Fixing my old ambiguous sketch references

| blogging, 11ty, emacs

At some point during the conversion of my blog from Wordpress to 11ty, I wanted to change my sketch links to use a custom shortcode instead of referring to the sketch in my old wp-uploads directory. Because Wordpress changed the filenames a little, I used the ID at the start of the filename. I forgot that many of my filenames from 2013 to 2015 just had the date without a uniquely identifying letter or number suffix, so many old references were ambiguous and my static site generator just linked to the first matching file. When I was listening to my old monthly reviews as part of my upcoming 10-year review, I noticed the repeated links. So I wrote these functions to help me find and replace markup of the form sketchLink "2013-10-06" with sketchLink "2013-10-06 Daily drawing - thinking on paper #drawing", replacing references to the same date with the next sketch in the list. I figured that would be enough to get the basic use case sorted out (usually a list of sketches in my monthly/weekly reviews), taking advantage of the my-list-sketches function I defined in my Emacs config.

(defun my-replace-duplicate-sketch-list-references ()
  (interactive)
  (goto-char (point-min))
  (let (seen)
    (while (re-search-forward "sketchLink \\\"\\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\\)\\\""
                              nil t)
      (if (assoc (match-string 1) seen)
          (setcdr (assoc (match-string 1) seen) (1+ (assoc-default (match-string 1) seen)))
        (setq seen (cons (cons (match-string 1) 1) seen))))
    (mapc (lambda (entry)
            (goto-char (point-min))
            (mapc (lambda (sketch)
                    (if (re-search-forward (format "sketchLink \\\"\\(%s\\)\\\""
                                                   (regexp-quote (car entry))) nil t)
                        (replace-match (save-match-data (file-name-sans-extension sketch))
                                       nil t nil 1)
                      (message "Skipping %s possible ref to %s"
                               (buffer-file-name)
                               sketch)))
                  (my-list-sketches (concat "^" (regexp-quote (car entry))) nil '("~/sync/sketches"))))
          seen)))

Sometimes I needed to delete the whole list and start again:

(defun my-insert-sketch-list-between (start-date end-date)
  (insert
   (mapconcat
    (lambda (f)
      (format "<li>%s sketchLink \"%s\" %s</li>\n"
              (concat "{" "%")  ; avoid confusing 11ty when I export this
              (file-name-sans-extension f)
              (concat "%" "}")))
    (sort (seq-filter
           (lambda (f) (and (string< f end-date) (not (string< f start-date))))
           (my-list-sketches nil nil '("~/sync/sketches")))
          'string<)
    "")))

I used find-grep-dired to search for sketchLink \"[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\" and then I just used a keyboard macro to process each file.

Anyway, really old monthly reviews like this one for October 2013 should mostly make sense again. I could probably pull out the correct references from the Wordpress database backup, but what I've got is probably okay. I would probably have gotten much grumpier trying to do this without Emacs Lisp. Yay Emacs!

View org source for this post

Slow days, weeks, months, years

| life

Some days are slow. Some weeks, some months, some years… Parenting gives me a sneak peek at what life is like being slow, and that's handy. I've written about being slow before. Every time I revisit this topic, I learn a little bit more. I can start to figure out the systems and perspectives that might help me as I grow older.

One of the nice things about a slow day is that it's easy to give myself permission to dwell on all the things I gloss over on fast days. I putter around the house, tidying up. I sit with A- as she reads, and I write my thoughts by hand. I update my ledger and doublecheck my budget. I read through my backlog of books and borrow some from A-'s pile so I can keep up with her interests. I learn more about my tools and try things out. I review and update my notes. I write journal entries even for these little moments, because small steps still add up over time.

What do fast days look like? I jump into a programming task and explore an idea, turning my notes into blog posts when I can. I fly around documentation and source code. When I reach out for something, I find it. I feel proud of what I've figured out. With A- , my fast days are when I have the energy and equanimity to help us have fun while taking care of our priorities.

On slow days, I let A- take more of the lead. I might say, "My brain is having a hard time being creative right now," and then we switch to something more physical or more straightforward. When she's grumpy and I don't have the energy to help her manage her feelings, we just let the big feelings wash over us.

It helps that Emacs News and similar things are compatible with slow days, as the hardest thinking I need to do then is just which category to use. Captioning videos and adding chapter markers are also straightforward. Writing about cool stuff is easier than writing and maintaining cool stuff.

Parenting is pretty compatible with slow days, too. When I focus on A- and appreciate the things she's learning and who she is as a person, she glows. There are plenty of resources I can tap, and I don't have to be "on" all the time.

Oh, is that why knitting, gardening, and reading are popular hobbies for older people, because it gets easier to be patient with things that take a while? Oooh. I wonder if that means I might have more patience for things that require compiling or training.

I'll have other slow days in the future, and that's okay. Some people even pay big money or make huge life changes in order to learn how to live more slowly. I'd like to still be happy with myself instead of frustrated when I'm in my 70s or 80s, so I think it will be worth figuring this out (slowly).

View org source for this post

2023-01-09 Emacs news

| emacs, emacs-news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, emacs-devel, and lemmy/c/emacs. 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!

Moving my Org post subtree to the 11ty directory

| 11ty, org, emacs, blogging

I sometimes want to move the Org source for my blog posts to the same directory as the 11ty-exported HTML. This should make it easier to update and reexport blog posts in the future. The following code copies or moves the subtree to the 11ty export directory.

(defun my-org-11ty-copy-subtree (&optional do-cut)
  "Copy the subtree for the current post to the 11ty export directory.
With prefix arg, move the subtree."
  (interactive (list current-prefix-arg))
  (let* ((file-properties
          (org-element-map
              (org-element-parse-buffer)
              'keyword
            (lambda (el)
              (list
               (org-element-property :key el)
               (org-element-property :value el)
               (buffer-substring-no-properties
                (org-element-property :begin el)
                (org-element-property :end el))))))
         (entry-properties (org-entry-properties))
         (filename (expand-file-name
                    "index.org"
                    (expand-file-name
                     (assoc-default "EXPORT_ELEVENTY_FILE_NAME" entry-properties) 
                     (car (assoc-default "ELEVENTY_BASE_DIR" file-properties))))))
    (unless (file-directory-p (file-name-directory filename))
      (make-directory (file-name-directory filename) t))
    ;; find the heading that sets the current EXPORT_ELEVENTY_FILE_NAME
    (goto-char
     (org-find-property "EXPORT_ELEVENTY_FILE_NAME" (org-entry-get-with-inheritance "EXPORT_ELEVENTY_FILE_NAME")))
    (org-copy-subtree 1 (if do-cut 'cut))
    (with-temp-file filename
      (org-mode)
      (insert (or
               (mapconcat (lambda (file-prop) (elt file-prop 2))
                          file-properties
                          "")
               "")
              "\n")
      (org-yank))
    (find-file filename)
    (goto-char (point-min))))

Then this adds a link to it:

(defun my-org-export-filter-body-add-index-link (string backend info)
  (if (and
       (member backend '(11ty html))
       (plist-get info :file-name)
       (plist-get info :base-dir)
       (file-exists-p (expand-file-name
                       "index.org"
                       (expand-file-name
                        (plist-get info :file-name)
                        (plist-get info :base-dir)))))
      (concat string
              (format "<div><a href=\"%sindex.org\">View org source for this post</a></div>"
                      (plist-get info :permalink)))
    string))

(with-eval-after-load 'ox
  (add-to-list 'org-export-filter-body-functions #'my-org-export-filter-body-add-index-link))

Then I want to wrap the whole thing up in an export function:

(defun my-org-11ty-export (&optional async subtreep visible-only body-only ext-plist)
  (let* ((info (org-11ty--get-info subtreep visible-only))
         (file (org-11ty--base-file-name subtreep visible-only)))
    (unless (string= (plist-get info :input-file)
                     (expand-file-name
                      "index.org"
                      (expand-file-name
                       (plist-get info :file-name)
                       (plist-get info :base-dir))))
      (save-window-excursion
        (my-org-11ty-copy-subtree)))
    (org-11ty-export-to-11tydata-and-html async subtreep visible-only body-only ext-plist)
    (my-org-11ty-find-file)))

Now to figure out how to override the export menu. Totally messy hack!

(with-eval-after-load 'ox-11ty
  (map-put (caddr (org-export-backend-menu (org-export-get-backend '11ty)))
           ?o (list "To Org, JSON, HTML" 'my-org-11ty-export)))
View org source for this post