Irreal: Decreasing Emacs Load Time

A week and a half ago, Bozhidar Batsov stirred up a storm by claiming that Emacs startup time doesn’t matter. There was much foaming at the mouth but I agreed with Batsov: Emacs startup time hardly every matters. When it does, it is in very special circumstances that, happily, most of us never experience.

At the end of my post on the matter, I said that if your Emacs startup time is lengthy, it probably indicates a problem with your configuration. Batsov apparently believes the same and in a new post offers some advice for addressing that problem.

He frames his discussion in terms of the use-package macro that most people use. The secret, he says, is to delay loading packages until they are needed. That makes sense because loading a single package won’t take long but loading, say, a hundred may add significant time to your init time. By deferring the loading, you speed up Emacs’ startup time at the cost of adding a tiny delay later when you first actually need a package.

Batsov’s suggestions are a bit complex but the TL;DR is to avoid the :preface, :init. and :config keywords. You should definitely read his post for the details. Ironically, he doesn’t follow his own advice because, as he says, Emacs startup time doesn’t matter.

-1:-- Decreasing Emacs Load Time (Post Irreal)--L0--C0--2025-04-18T15:10:32.000Z

Protesilaos Stavrou: Emacs: tmr version 1.1.0

TMR provides facilities for setting timers using a convenient notation. Lots of commands are available to operate on timers, while there also exists a tabulated view to display all timers in a nice grid.

Below are the release notes.


Version 1.1.0 on 2025-04-18

This version makes small refinements to an already stable package.

All of tmr-tabulated.el is part of tmr.el

The command tmr-tabulated-view, which produces a grid with timers+descriptions, used to be in a separate file. It now is part of the singular tmr.el to keep things simple. Users who were using (require 'tmr-tabulated) or similar will now get a warning. Simply load tmr instead.

Refined the behaviour of the tmr-tabulated-view command

When the command tmr-tabulated-view (alias tmr-list-timers) is called interactively, it uses the *tmr-tabulated-view* buffer just as it did before. Though it also evaluates the new user option tmr-list-timers-action-alist: it is a variable that controls where the buffer is displayed. The default value displays the buffer at the bottom of the Emacs frame and makes some other tweaks for usability.

Watch my video on the display-buffer-alist for further details on how to control the display of buffers: https://protesilaos.com/codelog/2024-02-08-emacs-window-rules-display-buffer-alist/.

The tmr-tabulated-view command is further revised to make it callable from a program. One scenario where we do this is to interrupt the termination of Emacs if there are running timers (more below).

TMR interrupts the termination of Emacs if it must

In the past, we did not have anything to prevent the termination of Emacs if timers were running: Emacs would simply shut down. Now we define the tmr-kill-emacs-query-function, which is added to the standard kill-emacs-query-functions: if there are running timers, it asks for confirmation before closing Emacs. To make it easier for users to decide how to proceed, it also pops up the list with all the timers (i.e. it uses tmr-tabulated-view from Lisp, as noted above).

The list view is easier to follow

The buffer produced by tmr-tabulated-view now uses more colours to make it easier to track the data it presents. These are all the faces it applies:

  • tmr-tabulated-start-time: The time when the timer was started.

  • tmr-tabulated-end-time: The time when the timer is set to end.

  • tmr-tabulated-remaining-time: The remaining time.

  • tmr-tabulated-acknowledgement: Whether the timer needs to be “acknowledged” after it ends (if it is marked as “acknowledged”, then it will not go away until the user confirms they have seen it).

  • tmr-tabulated-description: The text describing what the timer is about.

-1:-- Emacs: tmr version 1.1.0 (Post Protesilaos Stavrou)--L0--C0--2025-04-18T00:00:00.000Z

Jeremy Friesen: Automating Adding Books to My Org-Mode Document

The “Problem”

I have a lot of books and I want to keep track of what I “own.” I also have an Org-Mode 📖 document that has a set of books that I:

  • have highlighted one or more quotes
  • are on my shopping list
  • are books I’ve read or am reading

However, I didn’t really have a concept of “and I own this book.” To address that issue, I set about the process of using a mobile application to track books I owned.

However, that meant I had two conceptual sources of data. Ownership and “a book of interest” if you will.

The Spark

I saw A Book Tracking Package for Emacs by Lars Ingebrigtsen. In that post Lars shares his book tracking solution. It involves a bar code scanner and Emacs 📖 .

Curious, I set about getting that code working. And with a few adjustments, namely ignoring playing audio files during workflow steps, I was able to get it working.

The Pivot

I spent a bit of time hacking on it, but paused as I considered that what I wanted was a different.

Namely, given an International Standard Book Number (ISBN 📖) , I wanted to get the associated book information and either add the book to my Org-Mode document or update the entry in that document.

Also, I myself had a bar-code scanner, and wanted to use that as the thing that typed the ISBN into the buffer. I also considered that I wanted to intervene during the workflow; namely once I had retrieved the book information from the ISBN , I needed to decide if I added the book or updated an existing entry.

Establishing a Workflow

Given that I needed to make a decision, I reconsidered my workflow. Namely, I would grab a pile of ten or so books, and scan their ISBNs into a buffer. Then, with that pile, I would reconcile each book.

The idea being two-fold:

  • It is a lot of fun to rapidly point and shoot a bar-code scanner.
  • Looking at a list of ISBNs with no other context doesn’t tell me much.

Therefore, build a manageable list of ISBNs then flip to the other task. Namely reconciling the remote information with what I have.

Implementation

I drew some inspiration from Lars’s isbn.el; but ultimately set about my own implementation. Let’s walk through that.

Defining the Structure

I defined the jf/book structure. I would use this to create a common mapping for the data retrieved via ISBN process and my bibliography.

(require 'request)
(require 's)

(cl-defstruct jf/book
  "A basic representation of a book as it relates to my personal
bibliography.

Slots:
- label:     Used for comparing to labels from my bibliography; expected
             to conform to `jf/book-make-label' function.
- title:     The ubiquitous human readable identifier of a work.
- author:    The author(s) of the book; separated by \" and \".
- subtitle:  Usually the words after the colon of a title.
- tags:      A list of tags for the book; these are internal values.
- isbn:      The ISBN for the given book.  One of the unique
             identifiers.
- custom_id: The `org-mode' headline CUSTOM_ID property, used for
             helping find the headline."
  label
  label
  title
  author
  subtitle
  tags
  isbn
  custom_id)

Caching That Which I Already Have

Given that I had an existing set of books already stored locally, I wanted to cache that information for processing multiple ISBNs . I use my-cache-of-books to store that information.

(defvar my-cache-of-books
  (make-hash-table :test 'equal)
  "We use this as a cache of my bibliography entries that are books,
reprsented as an alist, and for each pair the `car' is a
`jf/book-label' (as formated by `jf/book-make-label') and the `cdr'
being an instance of `jf/book'.

See `my-cache-of-books/populate' for details on populating
this structure.

Normally I'd prefix this kind thing with \"jf/\", however I like how
this variable reads.")

Avoiding Magic Strings

As a matter of practice, I tend to avoid using “magic strings”, instead favoring defined values.

(defconst jf/bibliography/tag-own
  "own"
  "The tag used to indicate ownership of a work.")

(defconst jf/bibliography/tag-books
  "books"
  "The tag used to indicate that a work is book.")

Populating the Cache

As I was working through adding a book via ISBN , I didn’t want to rebuild a list of all of my books. The my-cache-of-books/populate function handles this logic.

  (defun my-cache-of-books/populate (&optional clear-cache)
  "Populates `my-cache-of-books' with my current books.

When CLEAR-CACHE is non-nil, clobber the cache and rebuild."
  (when clear-cache (clrhash my-cache-of-books))
  (when (hash-table-empty-p my-cache-of-books)
    (save-excursion
      (with-current-buffer
          (find-file-noselect jf/filename/bibliography)
        (org-map-entries
         (lambda ()
           ;; For some reason the org-map-entries is not filtering
           ;; on only items tagged as books.  Hence the
           ;; conditional.
           (when-let* ((tags
                        (org-element-property
                         :tags (org-element-at-point)))
                       (_
                        (member jf/bibliography/tag-books tags)))
             (let* ((title
                     (org-element-property
                      :title (org-element-at-point)))
                    (author
                     (org-entry-get
                      (org-element-at-point) "AUTHOR"))
                    (subtitle
                     (org-entry-get
                      (org-element-at-point) "SUBTITLE"))
                    (label (jf/book-make-label
                            title subtitle author)))
               (puthash label
                        (make-jf/book
                         :label label
                         :tags tags
                         :title title
                         :subtitle subtitle
                         :author author
                         :custom_id (org-entry-get
                                     (org-element-at-point)
                                     "CUSTOM_ID")
                         :isbn (org-entry-get
                                (org-element-at-point)
                                "ISBN"))
                        my-cache-of-books))))
         (concat "+level=2+" jf/bibliography/tag-books) 'file))))
  my-cache-of-books)

Fetch a Book from an ISBN

In jf/book-from-isbn I convert the ISBN into a jf/book data structure.

(defun jf/book-from-isbn (isbn)
  "Fetch the associated ISBN from Google API and return a `jf/book'.

  TODO: Instead of returning the book, consider taking a function that
  operates on the book.  There would need to be an inversion of behavior."
  (let ((book nil))
    (request "https://www.googleapis.com/books/v1/volumes"
      :params (list (cons "q" (concat "isbn:" isbn)))
      :parser (lambda ()
                (let ((json-object-type 'plist))
                  (json-read)))
      :sync t
      :success (cl-function
                (lambda (&key data &allow-other-keys)
                  (let* ((item
                          (aref (plist-get data :items) 0))
                         (volumeInfo
                          (plist-get item :volumeInfo)))
                    (setq book
                          (let ((title
                                 (plist-get volumeInfo :title))
                                (subtitle
                                 (plist-get volumeInfo :subtitle))
                                (author
                                 (s-join " and "
                                         (plist-get
                                          volumeInfo :authors))))
                            (make-jf/book
                             :isbn isbn
                             :label (jf/book-make-label
                                     title subtitle author)
                             :tags (list
                                    jf/bibliography/tag-own
                                    jf/bibliography/tag-books)
                             :subtitle subtitle
                             :author author
                             :title title)))))))
    book))

Here I Enter the ISBN

In jf/add-to-bibliography function is the entry point into this workflow. As the docstring states, I use the function to: “Append or amend to my bibliography the book associated with the ISBN.”

It is also the function where I intervene, namely determining if the given book is already in my bibliography or not.

(defun jf/add-to-bibliography (isbn &optional clear-cache force)
  "Append or amend to my bibliography the book associated with the ISBN.

When CLEAR-CACHE is non-nil, clobber and rebuild `my-cache-of-books'.

When FORCE ignore already existing ISBN; a bit of a refresh."
  (interactive (list (read-string "ISBN: ")))
  (let ((books
         (my-cache-of-books/populate clear-cache)))
    (when (or force (not (my-cache-of-books/contains-isbn-p isbn)))
      (let* ((book-from-isbn
              (jf/book-from-isbn isbn))
             (completed-value
              (completing-read
               (format "Match %s: " (jf/book-label book-from-isbn))
               books nil nil
               ;; Maybe we'll get a direct hit?
               (jf/book-label book-from-isbn))))
        (if-let ((book-from-bibliography
                  (gethash completed-value books)))
            (jf/bibliography/update-book-via-merge
             book-from-bibliography
             book-from-isbn)
          (jf/bibliography/insert-book
           book-from-isbn))))))

Insert a Book

When the book I fetched via an ISBN is not in my Org-Mode file, the jf/bibliography/insert-book function inserts a record. The inline comments describe some false starts and the why of the solution.

Restating the comments, I use the Org-Mode capture ecosystem to perform the heavy lifting of adding a book entry to the document. Namely by using variable binding and then calling org-capture; an approach that felt as though I leveled-up in my understanding of what was possible.

(defun jf/bibliography/insert-book (book)
  "Insert BOOK into bibliography.

Where BOOK is a `jf/book' struct."
  ;; NOTE: My first incarnation was to use `org-capture-string' which
  ;; meant adding to `org-capture-templates' a conceptually \"private\"
  ;; capture template.  That incarnation used \"?\" as the template
  ;; body.  However, when I'd test the behavior, I was getting an empty
  ;; entry.
  ;;
  ;; Instead I deconstructed the concise `org-capture-string' and
  ;; shifted towards binding `kill-ring' and using the \"%c\" capture
  ;; variable.  I like this approach as it eschews adding a useless
  ;; capture template while leveraging the power of the `org-capture'
  ;; ecosystem.
  (let* ((kill-ring
          (list
           (concat (jf/book-title book)
                   " :" (s-join ":" (jf/book-tags book)) ":\n"
                   ":PROPERTIES:\n"
                   ":CUSTOM_ID: "
                   (jf/denote-sluggify-title (jf/book-label book)) "\n"
                   (when (s-present? (jf/book-subtitle book))
                     (concat ":SUBTITLE: "
                             (jf/book-subtitle book) "\n"))
                   (when (s-present? (jf/book-author book))
                     (concat ":AUTHOR: " (jf/book-author book) "\n"))
                   ":ISBN: " (jf/book-isbn book) "\n"
                   ":END:\n")))
         (org-capture-entry
          '("B" "Book from ISBN Lookup"
            entry (file+headline jf/filename/bibliography "Works")
            "%c"
            :immediate-finish t)))
    (org-capture)
    (puthash (jf/book-label book) book my-cache-of-books)
    (message "Appended %s to bibliography" (jf/book-label book))))

Update a Book

When the book I fetched via an ISBN is in my Org-Mode file, the jf/bibliography/update-via-merge function merges and updates the document.

In essence, this is about finding the CUSTOM_ID and adding the own tag and ISBN to the book.

(defun jf/bibliography/update-via-merge (from-bibliography from-isbn)
  "Update a book FROM-BIBLIOGRAPHY with FROM-ISBN information.

Where both FROM-BIBLIOGRAPHY and FROM-ISBN are `jf/book' structs."
  (setf (jf/book-isbn from-bibliography) (jf/book-isbn from-isbn))
  (setf (jf/book-tags from-bibliography)
        (sort (seq-union
               (jf/book-tags from-bibliography)
               (jf/book-tags from-isbn)
               #'string=)))
  (save-restriction
    (widen)
    (save-excursion
      (with-current-buffer
          (find-file-noselect jf/filename/bibliography)
        (org-map-entries
         (lambda ()
           (let ((hl (org-element-at-point)))
             (when (string=
                    (org-entry-get hl "CUSTOM_ID")
                    (jf/book-custom_id from-bibliography))
               (progn
                 (org-set-property "ISBN"
                                   (jf/book-isbn from-bibliography))
                 (org-set-tags
                  (jf/book-tags from-bibliography))
                 (save-buffer)))))
         (concat "+" jf/bibliography/tag-books 'file))
        (message "Updated %s with ISBN %s"
                 (jf/book-label from-bibliography)
                 (jf/book-isbn from-bibliography))))))

Next Steps

In writing this, I’m thinking of a few next steps. There’s a TODO item in my docstring. Namely to refactor jf/book-from-isbn. Instead of returning a jf/book, I would pass a function which would receive a jf/book. I’d call that function on success.

Why consider that refactor? It touches back on that mobile application. I have already scanned 80 or so books. And I can export that application’s data and feed that into my process. I wouldn’t need to call the Google function but instead develop the method for creating a jf/book structure from that exported data.

And you may ask, where’s the code? It is a bit in flux. In part because I realized how trivial these next steps were. And I have this bar code scanner that demands I start “shooting books.”

Conclusion

With this new functionality, I’ve been able to add more functionality into my “bibliography.” It now serves as:

  • A repository of quotes associated with their source
  • A shopping list
  • A reading tracker
  • An inventory of my currently owned books

All in a rich, yet structured, format.

Further, the resulting workflow and functions helps me “chunk my work”, making it easier to review without simply mashing “accept.”

Postscript

I used the Emacs Read-eval-print loop (REPL 📖) to iterate on the solution. And all of the code was in my Bibliography file; usable via Org-Mode ’s tangle process.

Why do I mention this?

I found myself first writing prose, and thinking through Lars’ solution and how I might modify or extend that implementation in relation to the problem as I understood it.

-1:-- Automating Adding Books to My Org-Mode Document (Post Jeremy Friesen)--L0--C0--2025-04-17T21:41:43.000Z

Irreal: Emacs For Everything

Joshua Blais has an interesting post that looks at the idea of using Emacs for everything. That’s a familiar meme for us Emacsers, of course, but you don’t often see a careful, considered examination of the idea. Blais is an Emacs user so his post is, naturally, supportive of the idea but he does try to give both sides of the issue.

Blais’ main argument is built around the flow concept. Interrupting a knowledge worker’s flow can have devastating effects on their productivity. These interruptions include context switches and one study suggests that context switching and multitasking takes up around 60% of our working time. That seems high to me but whatever the value, it’s certainly significant.

One of the virtues of Emacs is that virtually all your text based tasks can be done in Emacs. That’s significant because the editor provides a uniform interface to all those tasks. Indeed, you can even slip it into other apps with packages like Emacs Everywhere that allow you to bring up an Emacs window to edit some app’s text area. For example, I use it with iMessage so that I can compose my texts in Emacs.

The advantage to doing most things in Emacs is that you avoid context switches and can better stay in the flow. A secondary benefit, as least for me, is that it provides a single set of muscle memories to deal with. As I’ve said before, I do better with a single set of keybindings so folding everything possible into Emacs is a win for me.

Blais also considers and dismisses the argument that Emacs takes so long to learn that you never regain the time “lost” to learning it. Even a few seconds thought is enough to recognize that idea for the silliness it is but Blais makes the point that if you aren’t using Emacs you’re still spending time learning other utilities that Emacs would otherwise handle. I know this first hand. Before I came to Emacs I was a long time command line user and spent years learning to use it effectively. Now I rarely use the command line; everything I need to do I can do from within Emacs.

Blais’ post is well worth a few minutes of your time whether or not you’re an Emacs user. Take a look.

-1:-- Emacs For Everything (Post Irreal)--L0--C0--2025-04-17T15:26:29.000Z

Lars Ingebrigtsen: Perplexingly Book-Learned Emacs

As I was whining yesterday, it’s perplexingly difficult to find a (semi-)programmatic way of determining whether an author has written a new book. Or even manually in some cases. The best advice is, like, “Follow them on Goodreads” or something?

But I wondered whether I could finally find something useful for an LLM to do? Sure, LLMs are plagiarism machines that use all of the world’s energy, but what can it do for me? So I signed up for the Perplexity AI, and an hour later:

Yes, I’m a prompt engineer now.

I’ve gotta give it to Perplexity — signing up for the API, adding some money and getting it to spew some data at me was totally painless. I created a little library called perplexity.el (and put it on Microsoft Github), but it’s totally trivial: It just does a url-retrieve request with some headers set, and you get JSON back.

So in the screenshot above, it’s listing all the books by David Sedaris it knows about, and also helpfully what kind of book it is.

That’s OK, and it’s of course trivial to just use that to list books that are newer than the last book I have from him. So I guess I could use this to automatically get all new books from the authors I’m interested in.

I also did a variation where I asked “list all the books by this author, but exclude books on this list:”, and then list all the books I already have by this author.

And as you can see, that, er, kinda changed what it’s outputting? Now it’s including a lots of plays that it didn’t before?

Typically enough, LLMs give different answers every time I ask, so this isn’t, you know, data to be relied on in any way. Sure, it’d be better if it was actual data instead of hallucinations and LLM navel lint, but it’s better than nothing.

The response times from Perplexity are all over the place — sometimes it responds within a couple seconds, and sometimes it takes a minute, but whatchagonnado.

Let’s test another author:

Amy Hempel. She’s awesome. I hit the m command (to list “missing” books), and:

All those three are books that exist! And that I don’t have.

I tried asking it again, and then it only listed two of these books — the non-repeatability of the results is annoying, but again, LLMs are toys, and if you get anything useful out of them, that’s nice, but your expectations can’t be low enough.

For giggles, let’s try this a “list missing books” on Megan Whalen a few times:

1st.

2nd.

3rd.

Oh well. I tried adjusting the temperature, but no go. I guess I could run the query several times and aggregate the results, but it’s already pretty slow…

After using this for a handful of authors, I’m now deep in debt. OK, perhaps not from the API usage, but because I’ve bought, like, fifteen books while just testing things out here…

I’ve added convenient commands to the search buffer to go to bookshop.org so that I can shop, as well as Goodreads to check whether the books actually exist. (They mostly do, but there are of course some hallucinations.)

And now I can also do that thing I wanted where there are certain authors that I track, and then query Perplexity for books they’ve released the last few years. Let’s see, I’ve marked a few authors, and:

Let’s repeat that:

And again:

*sigh*

Heh, if I remove Anthony Horowitz, I get:

Sure, sure… If I have just Stross and MacLeod, I get:

OK OK OK.

Is there a way to make Perplexity try, like, harder? (This is with Sonar Pro.) The prompts are in the package.

[Edit an hour later: OK, I guess I get it… Perplexity is based on web searches, and it just doesn’t like to do a lot of searches? So I’m going to have to loop through each tracked author and then aggregate the results.]

But… it does actually kinda look like even I managed to find something actually useful to use an LLM for, even if it’s literally janky? Actually?

Whodathunk.

-1:-- Perplexingly Book-Learned Emacs (Post Lars Ingebrigtsen)--L0--C0--2025-04-17T14:44:06.000Z

Bozhidar Batsov: Using use-package the right way

I recently wrote that Emacs startup time doesn’t matter and I got quite a lot of heat for it. I totally stand by everything I said there, but I acknowledge that different people have different use-cases and perspectives when it comes to this.

That’s why I’ve decided to share with you the #1 tip to speed up your Emacs - defer the load time of your packages (in other words - load them as late as possible, ideally when you actually need them for the first time). There are many ways to achieve this, but probably the easiest and most popular these days is to use use-package to organize your package configuration.

Unfortunately, using use-package the right way is not very obvious and there’s plenty of incorrect information about it all over the Internet. Here’s classic example of a problematic use-package usage:1

(use-package projectile
  :init
  (setq projectile-project-search-path '("~/projects/" "~/work/" "~/playground"))
  :config
  ;; I typically use this keymap prefix on macOS
  (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map)
  ;; On Linux, however, I usually go with another one
  (define-key projectile-mode-map (kbd "C-c C-p") 'projectile-command-map)
  (global-set-key (kbd "C-c p") 'projectile-command-map)
  (projectile-mode +1))

While this is technically speaking correct, the use of :init and :config means that the package will be loaded immediately.

You might be wondering at this point when to use things like :preface, :config and :init and you would be right to. As usual, the best answer is in the Emacs manual, and I’ll try to expand on it below.

Where possible, it is better to avoid :preface, :config and :init. Instead, prefer autoloading keywords such as :bind, :hook, and :mode, as they will take care of setting up autoloads for you without any need for boilerplate code. While the usage of preface in the wild is fairly rare, you’ll see a ton of usage of :init and :config for whatever reasons.2

For example, consider the following declaration:

(use-package foo
  :init
  (add-hook 'some-hook 'foo-mode))

This has two problems. First, it will unconditionally load the package foo on startup, which will make things slower. You can fix this by adding :defer t:

(use-package foo
  :defer t
  :init
  (add-hook 'some-hook 'foo-mode))

This is better, as foo is now only loaded when it is actually needed (that is, when the hook some-hook is run).

The second problem is that there is a lot of boilerplate that you have to write. In this case, it might not be so bad, but avoiding that was what use-package was made to allow. The better option in this case is therefore to use :hook, which also implies :defer t. The above is thereby reduced down to:

(use-package foo
  :hook some-hook)

Now use-package will set up autoloading for you, and your Emacs startup time will not suffer one bit. Nice, ah?

So, let’s return now to our original example and think how we can improve it. Our first instinct is probably to do something like:

(use-package projectile
  :defer t
  :init
  (setq projectile-project-search-path '("~/projects/" "~/work/" "~/playground"))
  :config
  ;; I typically use this keymap prefix on macOS
  (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map)
  ;; On Linux, however, I usually go with another one
  (define-key projectile-mode-map (kbd "C-c C-p") 'projectile-command-map)
  (global-set-key (kbd "C-c p") 'projectile-command-map)
  (projectile-mode +1))

This is going to be useless, though, as projectile-mode will run at the end of the :config block forcing the package to be loaded. We can make things a bit better if we instruct the mode to be loaded only after Emacs’s initialization has finished:

(use-package projectile
  :defer t
  :init
  (setq projectile-project-search-path '("~/projects/" "~/work/" "~/playground"))
  :config
  ;; I typically use this keymap prefix on macOS
  (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map)
  ;; On Linux, however, I usually go with another one
  (define-key projectile-mode-map (kbd "C-c C-p") 'projectile-command-map)
  (global-set-key (kbd "C-c p") 'projectile-command-map)
  :hook (after-init . projectile-mode)

Note that we’re using the name after-init instead of after-init-hook, as the hook is actually named. That’s done to spare you some typing, but I understand it might also be a bit confusing. You can enforce the usage of the full hook names like this:

(setopt use-package-hook-name-suffix nil)

So, what can we improve next? Ideally we should get rid of :defer, :init and :config:

(use-package projectile
  :custom (projectile-project-search-path '("~/projects/" "~/work/" "~/playground"))
  :bind-keymap (("C-c C-p" . projectile-command-map)
                ("C-c p" . projectile-command-map)
                ("s-p" . projectile-command-map))
  :hook (after-init . projectile-mode)

Much better!

Of course, you can’t always achieve this clean setup, but if you try you’ll get there 90% of the time!

So, to recap:

  • Avoid the use of :init, :config and :preface whenever possible
  • Most of the time you don’t need to use :defer
  • Usually you should aim to activate minor modes only after Emacs’s main initialization has finished (otherwse :defer is pointless)

I’ll add here that less is more, even in Emacs. It’s usually a good idea to review the list of packages in your .init.el every few months and trim it from time to time. I used to be the type of guy who loads 100+ packages in their config, but these days I limit myself only to packages really improve my workflows.

If use-package still feels like black magic to you I can suggest the following:

  • Macroexpand various use-package blocks in your config to see what’s the generated Emacs Lisp code. Here’s an example you can try:
(macroexpand-1
'(use-package projectile
  :custom (projectile-project-search-path '("~/projects/" "~/work/" "~/playground"))
  :bind-keymap (("C-c C-p" . projectile-command-map)
                ("C-c p" . projectile-command-map)
                ("s-p" . projectile-command-map))
  :hook (after-init . projectile-mode)))

;; macroexpansion
(progn
  (use-package-ensure-elpa 'projectile '(t) 'nil)
  (defvar use-package--warning78
    #'(lambda (keyword err)
        (let
            ((msg
              (format "%s/%s: %s" 'projectile keyword (error-message-string err))))
          (display-warning 'use-package msg :error))))
  (condition-case-unless-debug err
      (progn
        (let ((custom--inhibit-theme-enable nil))
          (unless (memq 'use-package custom-known-themes)
            (deftheme use-package) (enable-theme 'use-package)
            (setq custom-enabled-themes
                  (remq 'use-package custom-enabled-themes)))
          (custom-theme-set-variables 'use-package
                                      '(projectile-project-search-path
                                        '("~/projects/" "~/work/" "~/playground")
                                        nil nil
                                        "Customized with use-package projectile")))
        (unless (fboundp 'projectile-mode)
          (autoload #'projectile-mode "projectile" nil t))
        (add-hook 'after-init-hook #'projectile-mode)
        (bind-key "C-c C-p"
                  #'(lambda nil (interactive)
                      (use-package-autoload-keymap 'projectile-command-map
                                                   'projectile nil)))
        (bind-key "C-c p"
                  #'(lambda nil (interactive)
                      (use-package-autoload-keymap 'projectile-command-map
                                                   'projectile nil)))
        (bind-key "s-p"
                  #'(lambda nil (interactive)
                      (use-package-autoload-keymap 'projectile-command-map
                                                   'projectile nil))))
    (error (funcall use-package--warning78 :catch err))))

I know this looks a bit intimidating at first, but if you spent a bit of time reading the code you’ll see there’s nothing scary about it.3

  • use-package also comes with profiler, you can set use-package-compute-statistics to t, restart Emacs and call use-package-report to see which packages are taking too much time to set up and what stage they’re at.4

That’s all I have for you today. Feel free to share other use-package tips in the comments!

  1. From my own init.el - after I all I told you I don’t really care about the startup time. :D 

  2. I’ll have to admit I don’t even remember what :preface does. 

  3. You might also want to get wild with something like https://github.com/emacsorphanage/macrostep 

  4. Thanks to Andrey Listopadov for reminding me about this! 

-1:-- Using use-package the right way (Post Bozhidar Batsov)--L0--C0--2025-04-17T12:50:00.000Z

Christian Tietze: Test, Save, and Execute HTTP Requests with Hurl (or restclient.el)

So I’ve used restclient.el in the past to embed HTTP requests in readmes and other kinds of documentation to test login flows and APIs as I’ve been writing the corresponding servers.

But chances are you can’t use that package since you don’t use Emacs.

I found a command-line replacement – no thanks to my web searches, but thanks to alternativeto.net listings of httpie of all things.

My problem with cURL, Httpie, netcat, etc. for this is that you need to specify the host name as a command line argument. That makes sense to pipe HTTP responses into the next request from the terminal, but it doesn’t work well to tell a program to “please execute this self-contained HTTP request”.

Hurl to the rescue. Ignoring the unfortunate name, Hurl is, of course, written in Rust. It wraps libcurl and offers standard input and file-based processing of HTTP requests like so:

$ echo "GET https://example.com" | hurl 

Their Hurl file format does far more than I need for complex workflows. But it also does the simple things, i.e. single requests with payloads.

# Get home:
GET https://example.org
HTTP 200
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"


# Do login!
POST https://example.org/login?user=toto&password=1234
X-CSRF-TOKEN: {{csrf_token}}
HTTP 302

This example demonstrates how you can parse the response and capture parts of it – be it JSON, plain text, or in this case HTML to grep the CSRF token. Check out the samples page for inspiration.


Soooo …. if you happen to use md-babel (which I haven’t formally announced, yet!), and execute this httpbin.org request, say from the Markdown document of this very blog post:

POST https://httpbin.org/post
Content-Type: application/json
{ "test":"foo","bar":"baz" }

… then you get this result inserted for you right below:

{
  "args": {}, 
  "data": "{ \"test\":\"foo\",\"bar\":\"baz\" }", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "28", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "hurl/6.1.1", 
    "X-Amzn-Trace-Id": "Root=1-680093bf-142169f93e6208ec096d1024"
  }, 
  "json": {
    "bar": "baz", 
    "test": "foo"
  }, 
  "origin": "79.221.187.120", 
  "url": "https://httpbin.org/post"
}

That’s pretty cool and I’m going to be using that in the future to test my APIs in a way that non-Emacs-users can also enjoy, using hurl, Markdown, and md-babel.


Hire me for freelance macOS/iOS work and consulting.

Buy my apps.

Receive new posts via email.

-1:-- Test, Save, and Execute HTTP Requests with Hurl (or restclient.el) (Post Christian Tietze)--L0--C0--2025-04-17T08:09:20.000Z

James Dyer: Flex Matching with isearch

I’m having issues with isearch!

I’ve been using fido-mode for a while now (I’m one of those weirdos who transitioned from the Vertico/Marginalia/Orderless stack back to an Emacs built-in) and have become accustomed to the flexible matching that fido offers. For example, I often shorten commands to find them, such as:

It is very common for me to search in a buffer for strings that contain a dash (especially now I am doing a lot more elisp programming), and I keep instinctively fido-shortening the search (so searching without the dash). Each time, isearch says no (or fails), so I have to awkwardly reach out for the “-” and go back and perform the search again.

What if there was a more flexible way to perform an in-buffer search? Well, of course, there is, and that is the isearch-regexp variants. I have seen some people just binding this to their main isearch because, why not? Most of the time, the search will be identical, and there is always the opportunity for a more flexible search using “.*”.

Let’s run an example. I would like to search for the first occurrence of use-package in my config. Let’s see how many keys we can get down to for an efficient search and how easy ergonomically they are to get to (excluding the possibility of some annoyingly similar matching lines in the config)

Firstly, let’s try isearch - you would have to type up to at least “use-pa” (probably).

Now for isearch-regex - you would type “us.*p” (probably) or of course just the same as isearch, so “use-pa”. This is better but a bit awkward to access the “*” and you would have to be quite aware of what you were searching for and the rough split of words.

If this were translated into a form of fido search, you would type at least “usep” (probably), a similar number of characters, but no awkward punctuation (so faster to type) and the find is more flexible and for me now, more familiar.

As far as I’m aware, by default fido uses some form of flex matching (but not quite flex), which has been good enough for me for a while now. So, how do I get this form of matching using isearch and it being in-buffer?

After a little investigation, there are packages out there in Emacs-land for some fuzzy searching, and the most relevant seemed to be flex-isearch. I’m not sure I quite got it working for me, but looking through the code, I thought I could distil some concepts into simple defuns. I learned that you can actually slot any search function at the backend of isearch, so let’s transform a normal search into something on steroids that would give us a flexible search, fido style!

(defvar flex-isearch-group-size 3
  "Number of initial characters to group together for more accurate flex searching.")

(defun flex-isearch-regexp-compile (string)
  "Convert a search string to a more intelligent flex-matching regexp.
The first `flex-isearch-group-size` characters are grouped together for more accurate matching."
  (let* ((parts (split-string string " " t))
         (compile-part
          (lambda (part)
            (let ((grouped (substring part 0 (min flex-isearch-group-size (length part))))
                  (rest (substring part (min flex-isearch-group-size (length part)))))
              (concat
               (regexp-quote grouped)
               (mapconcat
                (lambda (char)
                  (let ((c (char-to-string char)))
                    (cond
                     ((and (>= char ?A) (<= char ?Z))
                      (concat "[^" c "\n]*" c))
                     ((and (>= char ?a) (<= char ?z))
                      (concat "[^" c (upcase c) "\n]*[" c (upcase c) "]"))
                     (t
                      (concat "[^" (regexp-quote c) "\n]*" (regexp-quote c))))))
                rest
                "")
               "[^-_[:alnum:]\n]*")))))
    (concat
     "\\b"
     (mapconcat compile-part parts "[^-_[:alnum:]\n]+"))))

(defun flex-isearch-search-fun ()
  "Return the appropriate search function for flex searching."
  (if isearch-forward 'flex-isearch-forward 'flex-isearch-backward))

(defun flex-isearch-forward (string &optional bound noerror count)
  "Flex search forward for STRING."
  (let ((regexp (flex-isearch-regexp-compile string)))
    (re-search-forward regexp bound t)))

(defun flex-isearch-backward (string &optional bound noerror count)
  "Flex search backward for STRING."
  (let ((regexp (flex-isearch-regexp-compile string)))
    (re-search-backward regexp bound t)))

(setq isearch-search-fun-function 'flex-isearch-search-fun)

With my first attempt at this, the search at times seemed to settle on more candidates than I would have liked, but I thought, “I generally always know at least the first 2 or 3 characters that I’m searching for, at which point things get a little fuzzy” (pun intended). So, can I group the first few letters as part of the search, thus narrowing down the candidate list?

The answer is yes, yes I can.

The code above is a first attempt and with all these things, I shall play around with it and see if it works for me. At the moment, this seems to give me a more fido-iish feel when searching in-buffer with isearch, and no awkward search punctuation in sight!. So, using the examples above I can now find “use-package” in my config with a simple “usep”.

I shall report back in a later post to see if it has settled into my workflow, or whether there is an annoyance that I just cannot put up with so it will have to be discarded, along with my other myriad of Emacs experiments!

-1:-- Flex Matching with isearch (Post James Dyer)--L0--C0--2025-04-17T08:00:00.000Z

Protesilaos Stavrou: Emacs: modus-themes version 4.7.0

I just published the latest stable release of the Modus themes. The change log entry is reproduced further below. For any questions, you are welcome to contact me. I will now work to apply these same changes to emacs.git, so please wait a little longer for the updates to trickle down to you.


4.7.0 on 2025-04-17

This release introduces many subtle stylistic tweaks to the “tinted”, “deuteranopia”, and “tritanopia” theme variants.

The modus-themes-list-colors command uses a tabulated list

This command and its modus-themes-list-colors-current variant help users see the colour values and semantic palette mappings defined by the given theme. In the past, their buffer was designed in the same spirit as that of the command list-faces-display, whereas now it is like the buffer of the command list-packages. Concretely, users may now sort by column. Do M-x describe-mode while in that buffer to learn about the available commands and their respective key bindings.

The “tinted” themes have slightly different colours

The overall feel of the modus-operandi-tinted and modus-vivendi-tinted themes is the same as before. Though in a side-by-side comparison between the old and new versions reveals lots of subtle differences. The general idea is to make the themes a bit more consistent by tweaking the foreground values to be more harmonious in combination with their background.

The “deuteranopia” themes are more consistently blue and yellow

These are the modus-operandi-deuteranopia and modus-vivendi-deuteranopia, which are optimised for users with red-green colour deficiency. In the past, these themes used blue and yellow hues wherever a concept of “success” versus “failure” had to be established. This approach is more generalised now, to include programming syntax highlighting and many other contexts. In short, the themes are more blue+yellow, while retaining their original feel.

The “tritanopia” themes are more consistently red and cyan

As above, the modus-operandi-tritanopia and modus-vivendi-tritanopia themes, which are optimised for users with blue-yellow colour deficiency, use a red+cyan palette in more places. Overall, they feel like they did before, only they are more consistent.

Miscellaneous

  • Extended support for the icomplete faces that are coming in Emacs version 31.

  • Added support for treemacs faces, courtesy of Rahul Juliato in pull request 121: https://github.com/protesilaos/modus-themes/pull/121. Rahul has assigned copyright to the Free Software Foundation.

  • Added support for the tldr package.

  • Extended support for adoc-mode. Thanks to Leilei332 for the contribution in pull request 137: https://github.com/protesilaos/modus-themes/pull/137. The change is within the ~15-line limit, meaning that the author does not need to assign copyright to the Free Software Foundation.

  • Added support for my spacious-padding package, specifically the faces it can use when the spacious-padding-subtle-mode-line user option is enabled.

  • Added support for the howm package.

  • Extended support to the new faces of the transient package. More specifically, all those faces use the same colour for key bindings because the idea of colour coding keys (e.g. light yellow means something different than dark blue) does not work in practice when considering accessibility. Such semantics should not be limited to differences in colour: they should also have distinct indicators, such as ASCII or Unicode characters.

  • Revised the avy package’s faces to only use one coloured background. The multiple coloured backgrounds have been a perennial problem for our accessibility requirements and have made the themes needlessly more complex just to support an edge case. With this simplified style, avy continues to work fine: it simply is less flamboyant. Other interfaces with avy-like model of interaction, such as optional extensions to the vertico and corfu packages, have these same changes, in the interest of consistency.

  • Update the meow sample configuration in the manual. This package is not directly supported at the theme level because (i) I do not use it and (ii) it is very hard for an outsider to it to trigger the display of all of its faces in the right context. Without seeing how all of them look together, I cannot come up with a reliable design. The manual offers a “good enough” approximation.

  • Broadened the support of the vterm faces to include the “bright” colours, while updating those that were already covered. Thanks to Edgar Vincent for informing me that some of the vterm faces were changed a while ago. This was done in issue 317 on the GitLab mirror: https://gitlab.com/protesilaos/modus-themes/-/issues/317.

  • Revised the org-column-title face to inherit the fixed-pitch face if the user option modus-themes-mixed-fonts is non-nil. This user option makes it possible to have a buffer with proportionately spaced fonts (such as by enabling variable-pitch-mode), while keeping spacing-sensitive elements, like tables and code blocks, in a monospaced font.

    Thanks to pedro-nonfree for bringing this matter to my attention in issue 129: https://github.com/protesilaos/modus-themes/issues/129.

  • Simplified the helper function modus-themes--retrieve-palette-value to make it more efficiently. Thanks to Basil L. Contovounesios for the contribution in merge request 60 on the GitLab mirror: https://gitlab.com/protesilaos/modus-themes/-/merge_requests/60.

  • Reworded the minibuffer prompt of the modus-themes-list-colors command.

  • Made Ivy and IDO subdirectories and “virtual” buffers easier to tell apart from matching text highlights.

  • Included coverage for the auto-dim-other-buffers-hide-face of the package auto-dim-other-buffers

  • Covered the built-in abbrev-table-name face.

-1:-- Emacs: modus-themes version 4.7.0 (Post Protesilaos Stavrou)--L0--C0--2025-04-17T00:00:00.000Z

Yi Tang: Filter Ledger Transactions using Tags

I have been testing using Ledger-Cli to track my expenses, so far I have found the tagging system useful. In my ledger journal, each transaction is associated with a project, for example, the below transaction is assigned to project “2024 Monitor Stand”

2024-12-08 Screwfix
    ; project: 2024 Monitor Stand
    Expenses:HomeImprovement:Tools            £ 4.99 
    Expenses:HomeImprovement:PPE             £ 19.98 
    Expenses:HomeImprovement:PPE             £ 14.99 
    ; :refund:
    Assets:Amex

This constraint I came up with helps avoid meaningless spending on new shiny tools. Operationally, imposing this limitation on my book provides flexible ways of querying the data.

For example, bring up the transactions that do not have projects assigned to:

 
ledger reg exp and "expr" "not has_meta('project')" \
       --format "| %(date) | %P | %(amount) | %(note) |\n"
Table 1: posts without project
Date Payee Amount Note
2024/12/22 Selco £ 30.570 ; CaberFloor p5 T&G 2400x600x18mm x 2

There is only one post that I forgot to add the project tag, so pretty good.

A bit of explanation of the ledger-cli query syntax

  • exp: check only accounts contain ‘exp’, in the ledger’s convention, it is all expending accounts, i.e. Expense::*
  • expr: invoke filters using expressions
  • has_meta(‘project’): check if the transactions have the metadata key ‘project’
  • and, not: logical operators
  • –format: specify the output formatting

Another use case is counting the number of transactions per project. I use the number of purchased items as a proxy to gauge the project size.

 
ledger reg exp and "expr" "has_meta('project')" \
       --format "%(meta('project'))\n"  \
       | sort |  uniq -c | sort -bgr
Table 2: Number of items purchased for each project
No. Items Project
39 2024 Loft Lights
34 2024 Loft Insulation
32 2025 Garage Conversion
8 2024 Monitor Stand
2 General

The data shows the “2024 Loft Lights” project is by far the largest . That was a simple project by itself, however, since that was my first electrical project, I had to purchase a lot of stuff, 1.5mm cables, clamps, grommets, connectors, switches, sockets etc.

Finally, I have the “refund” tag so I can flag up the items to remind of myself to check if I received the refund fully.

 
ledger reg "expr" "has_tag('refund')" \
        --format "| %(date) | %P | %(amount) | %(note) |\n"
Date Payee Amount Note
2024/12/08 Screwfix £ 14.990 Site Optimus Gel Knee Pads

So far I enjoyed the plain text accounting using ledger-cli. The format and syntax are simple, and yet I can do complicated queries.

-1:-- Filter Ledger Transactions using Tags (Post Yi Tang)--L0--C0--2025-04-16T16:00:00.000Z

Jeremy Friesen: Fixing a mu4e and Proton Bridge Foible

I use Proton Mail as my email provider. A while ago, I set out to read and send email from Emacs 📖 . I explored using both notmuch and mu; I’ve settled on mu in part because it was the one that I could easily setup deleting a local email and seeing it deleted upstream.

Things appeared to be working, but I kept noticing and ignoring the following message: [mu4e] Update process returned with non-zero exit code.

, during my lunch, I decided to tackle fixing the problem.

First the Hammer

I nuked my ~/Maildir directory and cache to hopefully “fix” the problem. In this aggressive delete, I accidentally deleted the associated cert.

I didn’t have notes on how to build that cert, so these are those notes:

Then the Scalpel

With a “clean slate”, I started anew, but continued to see [mu4e] Update process returned with non-zero exit code in my *Messages* buffer. There wasn’t any more useful information.

I shelled out and ran the underlying mbsync <account> command and saw the following:

Error: far side refuses to store message 4775 from near side.
IMAP command 'CLOSE' returned an error: operation not allowed
IMAP error: malformed FETCH response from 127.0.0.1 (127.0.0.1:1143): unable to parse INTERNALDATE
Channels: 1    Boxes: 5    Far: +1 *0 #0 -0    Near: +0 *0 #0 -0

I searched the web for "IMAP command 'CLOSE' returned an error: operation not allowed" and stumbled upon IMAP CLOSE command not allowed · Issue #426 · ProtonMail/gluon. This looked to be my experienced problem.

However, the solution was not immediately obvious. So I searched for mbsync skip expunge for a mail folder and found email - Is it possible to ignore “Inbox” folder when using IMAP (mbsync)? - Unix & Linux Stack Exchange.

I changed my ~/.mbsync file replacing Patterns * with Patterns * !"All Mail" and ran mbsync <account>. No more errors.

In summary, Proton’s “All Mail” folder is a virtual folder, and thus disallows directly deleting from it. By changing the Patterns configuration value, I told mbsync to ignore that virtual folder.

Conclusion

My ~./mbsyncrc file is available on Github. And I used Setting up Protonmail in Emacs and V.H. Belvadi — Setting up e-mail with Emacs and mu4e on macOS (Venkatram Harish Belvadi) to help get me started.

I updated the Github issue that helped me work through the resolution, to provide a breadcrumb for other folks encountering the problem.

-1:-- Fixing a mu4e and Proton Bridge Foible (Post Jeremy Friesen)--L0--C0--2025-04-15T21:07:32.000Z

Irreal: Scrim Beta

Charles Choi, someone whom Irreal often writes about, has a new app and is looking for beta testers. His app, scrim, is a way of making the org-protocol available to macOS users. The problem is that org-protocol depends on the ability to call emacs-client from within the invoking application but security rules on recent macOS versions make this difficult or impossible. Take a look at the scrim link for a fuller explanation.

Becoming a beta tester is really easy. All you have to is install TestFlight and choose the Scrim app. My experience with TestFlight has been outstanding. It automates virtually everything including keeping you up to date with the latest beta version.

The only app I ever want to export data from is Safari and I have some custom AppleScript code to handle that. Still, if I were starting today I would probably try to use org-protocol to solve the problem for any application supporting the share menu.

If you want to import data from other applications into Emacs or think that being able to do so would make your workflow smoother, take a look at Choi’s post and consider becoming a beta tester. If you are already a Captee (Choi’s previous application for this) user you should especially consider this.

I can tell you from my own experience with being a beta tester for Journelly, that it’s a rewarding and beneficial activity. You not only get an early look at an application but can also influence its final form. And, of course, you help the developer deliver the best possible app.

-1:-- Scrim Beta (Post Irreal)--L0--C0--2025-04-15T15:38:08.000Z

Lars Ingebrigtsen: A Book Tracking Package for Emacs

In 2013, I got tired of rooting through the book cases every time I bought a new book (to see whether I already had it, because who can remember those things).

So I bought an ISBN bar scanner, and whipped up some basic code in Emacs to keep track of what I’d bought. I’ve used it for over a decade now, and it works fine, but it’s super duper basic:

It’s basically just a buffer that lists all the authors, and then if you click them, you get:

A list of all the books from that author, as well as the registered publication date.

So that’s fine, but:

The data quality from the various ISBN lookup providers is pretty bad. I mean, it’s not only the inconsistency, but it’s also sometimes altogether wrong, because ISBN reuse is a thing, unfortunately.

In addition, I’ve been buying ebooks too, and these are totally outside this system. Whenever I buy a papery physical book, I blitz it with the bar code scanner, and it’s registered — it takes me literally two seconds or less. With ebooks, I’ve got no system at all, and I’m buying via three different online stores, so my book shopping headache has reestablished itself.

So I hear what you’re saying: Just bite the bullet and use Librarything for everything. And I say: No! So there! stomps foot

Voila!

I spent one day tinkering with this thing, and now I can edit the data to satisfy my CDO (it’s like OCD, only the letters are in the proper alphabetical order), and I can add ebooks (manually-ish, sigh). When the data is easily available like this, the obvious errors (like with the author name above) can be fixed very quickly.

I added more support to the isbn.el library to do Goodreads queries, too. Goodreads doesn’t have an API any more, so I have to do some web scraping, which sucks, but this increased the number of book covers by 180%, since the services that do have an API aren’t as comprehensive. So whatchagonnado.

Covers are nice.

I also added a new command to just list all the books in one big buffer:

Because I realised that having a buffer with authors, and then books by specific authors is all well and fine, but it’s really just much faster to C-s for a book name than to first find the author, and then find the book. I mean, I’ve not got a million books; it’s just er:

That many.

Hey, now I can get data out of this thing by just clicking on the columns… Let’s see… I’ve got 432 unread books! Sure! I’ll get to them all one day.

(Actually, looking over that list, it looks like I’ve forgotten to mark a large number of books as read… uhm… I’m guesstimating about 100? I should revise the data one of these days.)

Anyway, the code is on Microsoft Github. I’m not sure anybody would find this usable as is, but perhaps there’s some useful bits. At least the isbn.el library in there is somewhat helpful.

-1:-- A Book Tracking Package for Emacs (Post Lars Ingebrigtsen)--L0--C0--2025-04-15T14:52:57.000Z

Protesilaos Stavrou: Emacs: Denote version 4.0.0

Denote aims to be a simple-to-use, focused-in-scope, and effective note-taking and file-naming tool for Emacs.

Denote is based on the idea that files should follow a predictable and descriptive file-naming scheme. The file name must offer a clear indication of what the contents are about, without reference to any other metadata. Denote basically streamlines the creation of such files or file names while providing facilities to link between them (where those files are editable).

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 constistent way makes their filtering and retrieval considerably easier. Denote provides relevant facilities to rename files, regardless of file type.

Below are the release notes.


Version 4.0.0 on 2025-04-15

This is a massive release. There is one breaking change, which should be easy to adapt to: this pertains to the reorganisation of the project to separate the “core” of Denote from its “extensions”. The core is the denote package. Each extension now has its own package (details below).

Other than that, this version includes lots of new features for searching and linking as well as quality-of-life refinements. We have generalised the infrastructure for performing queries in the denote-directory and made the buffers with the search results more useful.

Take your time to read through this publication. I am writing it for you. Also remember that the most up-to-date resource for anything related to Denote is its manual. You are always welcome to contact me: https://protesilaos.com/contact. Or join the development on the Git repository.

As usual, special thanks to Jean-Philippe Gagné Guay for making high quality contributions to Denote since the beginning of the project ~3 years ago. Those will not always be headline features, but are important improvements to the underlying code base.

I mention contributions from Jean-Philippe and others in its context. Though I do not cover implementation details, otherwise this document will be the size of a book. This does not mean that they are no important though. Please consult the Git commit log for all the technicalities.

All the “extras” are in separate packages, including the Org dynamic blocks

In previous versions of Denote, we included some optional extensions as part of the denote package. These included the files denote-org-extras.el (Org dynamic blocks, among others), denote-journal-extras.el (streamlined for journaling), denote-silo-extras.el (working with multiple Denote silos).

The files denote-md-extras.el (Markdown extras) and denote-sequence.el (sequence notes, including Luhmann-style alphanumeric sequences) were also part of the project during the last development cycle, though they never made it into a tagged release.

All these are now available as standalone packages on the official GNU ELPA archive:

  • denote-org: In the Emacs configuration file, replace all instances of denote-org-extras with denote-org.

  • denote-journal: Replace denote-journal-extras with denote-journal.

  • denote-silo: Replace denote-silo-extras with denote-silo.

  • denote-markdown : Replace denote-md-extras with denote-markdown.

  • denote-sequence: No changes to any of the defined symbols. Simply get the new package.

I will document each of these packages further below. The plan, going forward, is to maintain all the packages and coordinate their new versions.

More things in “core”

While the extras are moved out to their own code repositories, all other features are merged into denote.el. Those include everything that was in denote-sort.el and denote-rename-buffer.el.

  • The “sort” mechanism is mostly for package developers. We use it extensively in our Org dynamic blocks, which are now part of the denote-org package.

  • The denote-dired command (alias denote-sort-dired) is the only user-facing “sort” command we have always provided. It produces a fully fledged Dired buffer showing the results of the given search for file names. The matching files are sorted according to the user’s expressed preference. The details are described in the manual.

  • The denote-rename-buffer-mode and all of its user options are unchanged. This mode automatically renames the buffer of a given Denote file so that it is easier to read it. Again, the manual covers the technicalities.

Users do not need to make changes, unless they are explicitly loading denote-sort-dired and denote-rename-buffer. In that case, they may just remove those calls: only denote needs to be loaded.

The denote-query-mode

Many of the features I will describe below produce search results via the built-in Xref mechanism. Xref performs a search with a Grep or Grep-like program, subject to the user option xref-search-program. The buffer those search results are displayed in runs the denote-query-mode. It supersedes denote-backlinks-mode.

The denote-query-mode supports the following:

  • Results are shown in the context, with the exact match in highlight.
  • Matches are grouped by file. Each file is a “heading”.
  • Headings can be folded with TAB, just how it is done in Org buffers.
  • The results can be used for further queries. Type C-h m (describe-mode) to learn about all the relevant commands.

We have had support for Xref since the original version of Denote. It now is more generalised to cover backlinks, query links, and denote-grep (more below).

Use query links for file contents or file names

Denote has always provided the option to link directly to a file with a given name by referencing its identifier. This can be done with the command denote-link, among a few others like it (always consult the manual of Denote).

In addition to these “direct links”, we also support “query links”. Those do not point to a file but instead trigger a search. The results are placed in a buffer that uses the appropriate major mode.

There are two types of query links:

  • Query file contents: Use the command denote-query-contents-link to insert a query link at point for “file contents”. It perform a search inside files in the denote-directory and put the results in a denote-query-mode buffer.

  • Query file names: Use the denote-query-filenames-link to insert a query link for “file names”. It performs the query against file names (not contents!) and puts the results in a dired buffer.

The display of the buffer with the query link results is controlled by the user option denote-query-links-display-buffer-action.

Query links are styled a little bit differently than direct links. Compare the denote-faces-link with denote-faces-query-link. Both should look okay with most themes.

Denote query links are supported as part of the denote: hyperlink type. They are available in all file types we define (per the user option denote-file-type) and should, in principle, work in any custom file type (advanced users can check the variable denote-file-types).

Backlinks now always show their context

In the past, the command denote-backlinks would produce a bespoke buffer showing a list of file names that included links to the current file (any file with the Denote file-naming scheme can have backlinks, by the way, including PDFs, videos, etc.). This buffer did not provide any additional functionality. We used to support the option to show results in their context via denote-backlinks-show-context. Those would be rendered in a standard Xref buffer.

The contextual results are now the default and sole option. This is because we have expanded the functionality of those buffers to use the denote-query-mode, as explained above. Plus, it makes our code base simpler.

Users will notice how backlikns look just like a query link for file contents. This is because backlinks are the original query links since day one of Denote.

Direct links to a file with matching contents

The command denote-link-to-file-with-contents allows users to produce a direct link to a file whose contents (not file name!) includes the given query.

Similarly, the command denote-link-to-all-files-with-contents generates a typographic list (bullet list) to all files whose contents match the given query.

The manual covers all linking commands in depth.

The essence of denote-search is part of denote

The denote-search package by Lucas Quintana uses the infrastructure of Denote to perform searches in file contents. We now provide its feature set as part of core denote.

We decided to do this since query links already introduced all of the requisite generalisations to denote-query-mode.

Users can rely on the commands denote-grep, denote-grep-marked-dired-files, and denote-grep-files-referenced-in-region.

The placement of these buffers is subject to the user option denote-grep-display-buffer-action.

This functionality was introduced in two pull requests by Lucas Quintana, 571 and 573, with further changes by me:

Lucas has assigned copyright to the Free Software Foundation.

I think this was a much-needed addition to the core of Denote. It complements denote-dired and query links.

Formatting of links with denote-link-description-format

The old user option denote-link-description-function is deprecated and superseded by the new denote-link-description-format. The new user option still accepts a custom function as its value, so the old behaviour should be retained.

What the new denote-link-description-format supports is an easier way to customise the description of a link by using format specifiers for common options. For example, users who only want to see the title of the linked file can do this:

(setq denote-link-description-format "%t")

The documentation of this user option covers all the format specifiers and further details.

Miscellaneous changes for all users

  • The command denote-add-front-matter is superseded by denote-rename-file and related. Those renaming commands will add missing front matter or rewrite the modified lines of existing front matter. This is due to refinements made by Jean-Philippe Gagné Guay to the file renaming mechanism. We discussed this deprecation in issue 498: https://github.com/protesilaos/denote/issues/498. Also thanks to Samuel Flint for reporting an earlier problem with file name signatures: https://github.com/protesilaos/denote/issues/492.

  • The user option denote-open-link-function specifies the function used by Denote to open the file of a direct link.

  • The user option denote-org-store-link-to-heading can now be set to form generic context links without a PROPERTIES drawer and corresponding CUSTOM_ID. Set the value of this variable to 'context. Read its documentation for further details.

  • Also about denote-org-store-link-to-heading, we have changed its default value to nil, which is what we were doing for most of Denote’s history. This means that, by default, org-store-link and anything building on top of it will create a link only to the current Denote file, like denote:IDENTIFIER, but not to the current heading within that file. To create links to the file+heading, set the value of this variable to 'id.

  • The command denote-dired-link-marked-notes is an alias for denote-link-dired-marked-notes.

  • The user option denote-sort-dired-extra-prompts control what denote-dired (alias denote-sort-dired) prompts for. It accepts either a nil value or a list of symbols among sort-by-component, reverse-sort, and exclude-regexp. The order those symbols appear in the list is significant, with the leftmost coming first.

  • There is a new denote-sort-identifier-comparison-function variable which determines how identifier-based sorting should be done by default. It complements the existing denote-sort-title-comparison-function, denote-sort-keywords-comparison-function, denote-sort-signature-comparison-function. Thanks to Maikol Solís for the contribution in pull request 517: https://github.com/protesilaos/denote/pull/517. The change is small, meaning that Maikol does not need to assign copyright to the Free Software Foundation (though I believe the paperwork is done, anyway).

  • Lots of refinements to the doc strings of individual variables and/or functions as well as the manual.

  • Lots of other contributions to discussions and questions on the Git repository. Granted, these are not “changes” per se but are part of the development effort nonetheless.

  • Made denote-get-path-by-id use denote-get-file-extension-sans-encryption instead of denote-get-file-extension. This fixes a bug where the extension is duplicated if it has an encryption component. Thanks to eum3l for the patch in pull request 562: https://github.com/protesilaos/denote/pull/562. The change is small, meaning that the author does not need to assign copyright to the Free Software Foundation.

  • Same as above for denote--rename-file, which was done in pull request 557: https://github.com/protesilaos/denote/pull/557.

For developers or advanced users

The following have been added or modified.

  • NEW Function denote-file-has-denoted-filename-p: Return non-nil if FILE respects the file-naming scheme of Denote. This tests the rules of Denote’s file-naming scheme. Sluggification is ignored. It is done by removing all file name components and validating what remains. Thanks to Jean-Philippe Gagné Guay for the pull request 515: https://github.com/protesilaos/denote/pull/515.

  • NEW Functions denote-infer-keywords-from-files: Return list of keywords in denote-directory-files. With optional FILES-MATCHING-REGEXP, only extract keywords from the matching files. Otherwise, do it for all files. Keep any duplicates. Users who do not want duplicates should refer to the functions denote-keywords.

  • MODIFIED Function denote-keywords: Returns an appropriate list of keyword candidates, while accounting for the value of the user option denote-infer-keywords. It now also accepts the optional FILES-MATCHING-REGEXP parameter.

  • MODIFIED Function denote-directory-files: Returns a list of absolute file paths in variable denote-directory. It now accepts the optional EXCLUDE-REGEXP parameter.

  • MODIFIED Function denote-format-file-name: Formats a file name. The way it treats its ID parameter has changed. Please read its doc string. Thanks to Jean-Philippe Gagné Guay for the pull request 496: https://github.com/protesilaos/denote/pull/496.

  • ALIAS Function denote-retrieve-filename-keywords-as-list: This is a name that is easier to discover than denote-extract-keywords-from-path, because of the many other functions with the denote-retrieve-* prefix.

  • MODIFIED Function denote-retrieve-filename-identifier: Extracts the identifier from FILE name, if present, else returns nil. To create a new one from a date, refer to the denote-get-identifier function. Thanks to Jean-Philippe Gagné Guay for the pull request 476: https://github.com/protesilaos/denote/pull/476.

  • MODIFIED Function denote-get-identifier: Converts DATE into a Denote identifier using denote-id-format. If DATE is nil, it returns an empty string as the identifier. Also by Jean-Philippe in pull request 476 mentioned right above.

  • MODIFIED Function denote-date-prompt: Prompts for a date, expecting YYYY-MM-DD or that plus HH:MM (or even HH:MM:SS). Can also use Org’s more advanced date selection utility if the user option denote-date-prompt-use-org-read-date is non-nil. It now has the optional parameters INITIAL-DATE and PROMPT-TEXT. Thanks to Jean-Philippe Gagné Guay for the pull request 576: https://github.com/protesilaos/denote/pull/576.

  • NEW Function denote-retrieve-groups-xref-query: Accesses the location of xrefs for QUERY and group them per file. Limit the search to text files.

  • NEW Function denote-retrieve-files-xref-query: Returns sorted, deduplicated file names with matches for QUERY in their contents. Limits the search to text files.

  • NEW Function denote-retrieve-xref-alist: Returns xref alist of files with the location of matches for QUERY. With optional FILES-MATCHING-REGEXP, it limits the list of files accordingly (per denote-directory-files). At all times, it limits the search to text files.

  • NEW Function denote-prepend-front-matter: Prepend front matter to FILE. The TITLE, KEYWORDS, DATE, ID, SIGNATURE, and FILE-TYPE are passed from the renaming command and are used to construct a new front matter block if appropriate.

  • MODIFIED Function denote-rewrite-front-matter: Rewrites front matter of note after denote-rename-file (or related). The FILE, TITLE, KEYWORDS, SIGNATURE, DATE, IDENTIFIER, and FILE-TYPE arguments are given by the renaming command and are used to construct new front matter values if appropriate. If denote-rename-confirmations contains rewrite-front-matter, prompt to confirm the rewriting of the front matter. Otherwise produce a y-or-n-p prompt to that effect. Thanks to Jean-Philippe Gagné Guay for the pull request 558: https://github.com/protesilaos/denote/pull/558.

Denote “extensions” that are not in the denote package anymore

denote-journal integrates nicely with M-x calendar

The calendar can now highlight days that have journal entry. It may also be used as a date picker to view or write a journal entry for that day.

Other than that, the package is providing the same functionality as the discontinued denote-journal-extras.el.

denote-org is almost the same as the discontinued denote-org-extras.el

The only addition to dynamic blocks the optional :not-regexp parameter. This is a regular expression that can further filter the results of a search, such that the matching items are removed from the output.

The official manual of denote-org covers the technicalities.

Also thanks to Elias Storms for fixing a small issue with the “missing links” Org dynamic block, in pull request 486: https://github.com/protesilaos/denote/pull/486

denote-silo is the same as the discontinued denote-silo-extras.el

I have only made small tweaks to it, but nothing that changes the user experience.

denote-markdown for some Markdown-specific extras

This package provides some convenience functions to better integrate Markdown with Denote. This is mostly about converting links from one type to another so that they can work in different applications (because Markdown does not have a standardised way to define custom link types). It also defines an “Obsidian” file type which does not have any front matter but only uses a level 1 heading for the title of the note.

The code of denote-markdown used to be bundled up with the denote package before version 4.0.0 of the latter and was available in the file denote-md-extras.el. Users of the old code will need to adapt their setup to use the denote-markdown package. This can be done by replacing all instances of denote-md-extras with denote-markdown across their configuration.

Write sequence notes (or “folgezettel”) with denote-sequence

Users who want their notes to have an inherent structure can use denote-sequence. The idea is to have thoughts that naturally form sequences and are named accordingly. The sequence scheme is either numeric or alphanumeric. The manual of the package explains all the details.

I had a lot of fun developing this comprehensive package during the winter holidays.

Thanks to Claudio Migliorelli, Kierin Bell, Mirko Hernandez for helping me fix some issues during development:

The consult-denote also gets a small update

This has always been a standalone package. I made the function consult-denote-file-prompt read the special-purpose variable denote-file-prompt-use-files-matching-regexp. This is related to commit e0f1d47 in denote.git, about issue 536 as reported by Alan Schmitt: https://github.com/protesilaos/denote/issues/536. The variable denote-file-prompt-use-files-matching-regexp is meant to be let bound and is for advanced users or developers.

Feature freeze at least until the end of April 2025

I will not develop new features or accept pull request for a couple of weeks. The idea is to focus on fixing any bug reports. We can then publish point releases quickly.

New features can be included after we are confident that the packages we have are okay.

Git commits

This is just an overview of the Git commits, though remember that there is more that goes into a project, such as the reporting of inconsistencies, discussion of new ideas, et cetera. Thanks to everybody involved! Plus, some commits are large while others are tiny.

~/Git/Projects/denote $ git shortlog 3.1.0..4.0.0  --summary --numbered
   470	Protesilaos Stavrou
    90	Jean-Philippe Gagné Guay
     6	Kierin Bell
     4	Alan Schmitt
     3	eum3l
     2	Claudio Migliorelli
     2	Lucas Quintana
     2	grtcdr
     1	Elias Storms
     1	Laurent Gatto
     1	Maikol Solís
     1	Octavian
     1	TomoeMami

The following are not accurate because they only reflect the changes after the reorganisation I made. But we have to start from somewhere.

~/Git/Projects/denote-journal $ git shortlog  --summary --numbered
    54	Protesilaos Stavrou
     2	Honza Pokorny
     1	Vineet C. Kulkarni

~/Git/Projects/denote-sequence $ git shortlog  --summary --numbered
    22	Protesilaos Stavrou

~/Git/Projects/denote-silo $ git shortlog  --summary --numbered
    17	Protesilaos Stavrou

~/Git/Projects/denote-org $ git shortlog  --summary --numbered
    15	Protesilaos Stavrou

~/Git/Projects/denote-markdown $ git shortlog  --summary --numbered
    11	Protesilaos Stavrou
-1:-- Emacs: Denote version 4.0.0 (Post Protesilaos Stavrou)--L0--C0--2025-04-15T00:00:00.000Z

Charles Choi: Making an App, Looking for Beta Testers

Nearly two years back I wrote a utility called Captee that uses the native macOS Share Menu ( ) to share a link or text clipping in either Markdown or Org format. The idea was that any macOS app that supported the Share Menu could take advantage of this. Among the features that Captee supported was the ability to share content via Org Protocol, a custom URL scheme devised to import content into Emacs.

However, for Org Protocol to work, it required that there be a way to transport the org-protocol:// URL request to Emacs on macOS. Org Protocol’s original implementation presumed that emacsclient would do the lifting here, where the sharing app would be able to call emacsclient with the org-protocol// request as its argument. This was however a regrettable design decision made over a decade ago as recent versions of macOS now have a default sandbox policy that prevents an app from calling an executable like emacsclient outside of its sandbox.

The Mitsuharu Yamamoto fork of Emacs got around this restriction by supporting the Org Protocol custom URL scheme natively, where it would directly accept the URL request, bypassing emacsclient. Unfortunately for users of GNU FSF-published Emacs, Org Protocol was viable only if emacsclient was enabled, typically by relaxing macOS security permissions.

To address the above for GNU FSF-published Emacs, I set out to build a proxy utility that would accept the org-protocol:// URL request and transport it to Emacs in a way that conforms to macOS security policies. In effect, I made a macOS client app for Emacs server.

This app is named Scrim and is now in BETA test. I’m looking for testers, especially for those of you who already have Captee. If you’re interested and willing to provide feedback, please add yourself to the Beta test group on TestFlight, an Apple tool for distributing code-signed apps. At current I’m most interested in the following feedback:

  • Sanity check - Does the app work as advertised?
  • Were the instructions to setup Emacs server easy to follow? If not, then what was missing?
  • Are you a user of Captee?

If you do sign yourself up for this, please provide answers to the above via the mail feedback in the Scrim app.

Thanks!


YES, I WANT TO BETA TEST SCRIM

-1:-- Making an App, Looking for Beta Testers (Post Charles Choi)--L0--C0--2025-04-14T21:50:00.000Z

Grant Rettke: Why Lisp Is Fun

Via jrm: [Lisp] is a language that was designed to be used as a tool for thinking about problems, and that is the fun part of programming.
-1:-- Why Lisp Is Fun (Post Grant Rettke)--L0--C0--2025-04-14T18:39:00.000Z

Irreal: Emacs And Lisp

Joe Marsahll has been blogging up a storm lately. One of his latest posts is a sort of personal history of Emacs and its relationship to Lisp. When Marshall started as an undergraduate, he was using the line oriented editor TECO. If you think your experience with an editor is difficult, read his description of what it was like to use TECO.

Soon he was directed to Vi and a whole new world was opened to him. He no longer had to work in the blind but could actually see what he was doing. Later, when he moved to MIT he was introduced to Lisp and various early incarnations of what we now think of as “Emacs”.

Emacs, of course, began life as a set of macros for TECO so, in a sense, Marshall revisited TECO when he started working at the MIT AI lab. but the Lisp Machines that were developed in the AI lab had their own Emacs clone integrated into the system. In his career he has used a wide variety of Emacs-like editors but like all of us he’s using GNU Emacs these days.

One of the things Marshall talks about is using Sly with Emacs to recreate his experiences on the Lisp Machine. Sly, he says, pretty much recreates the interface that you had using ZWEI—an early version of Emacs—on a Lisp Machine. I’m still using Slime for my Lisp coding but, as I have written many times, I have a long standing urge to work on a Lisp Machine so I may give Sly a try if only to get closer to the Lisp Machine experience.

-1:-- Emacs And Lisp (Post Irreal)--L0--C0--2025-04-14T16:18:40.000Z

Sacha Chua: 2025-04-14 Emacs news

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

View org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- 2025-04-14 Emacs news (Post Sacha Chua)--L0--C0--2025-04-14T14:49:29.000Z

Marcin Borkowski: Copying the current location on steroids

A few years ago I wrote about a simple utility I wrote for myself to copy the “current location” in the project (that is, the filename and optionally a line number) to the system clipboard so that I can yank (paste) it in e.g. my company chat system (which I have to use outside Emacs). I’m using it all the time, and it’s very useful, but it’s still a bit limited.
-1:-- Copying the current location on steroids (Post Marcin Borkowski)--L0--C0--2025-04-14T04:34:44.000Z

Irreal: Prot On Elisp

If you follow the various Emacs forums, you’ll see a lot a whining about how hard Elisp is to learn and how things would be so much better if only the extension language were something rational like Python, Ruby, or Lua. I rail frequently against this nonsense but Prot (Protesilaos Stavrou) stands as an unassailable refutation of the silliness.

He’s a guy with a liberal arts education who has no training in programming and yet has nevertheless produced some really great Emacs software. If you look at his code, you can see that he’s obviously mastered the intricacies of Elisp.

Now, Prot has shared some of his knowledge in the form of a short Elisp tutorial. Although Emacs is exhaustively documented, there’s a dearth of Elisp documentation for beginners. There is, of course, the Elisp manual, which is comprehensive, but not really beginner friendly. It’s a reference rather than a tutorial.

There’s the builtin Elisp tutorial and Mbork’s Elisp book but not much more. I am, therefore, happy to see Prot’s contribution. I haven’t had a chance to do more than scan it but it seems like a very worthwhile addition to the Emacs corpus.

One thing for sure, it should put all the whiners to shame. If you’re claiming to be a software engineer but find Elisp too difficult to learn, you should be chagrined that a guy with no formal training in software is nevertheless able to produce first rate Elisp code that the Emacs community embraces as useful and excellent.

The irony is that Lisps in general are actually easier to learn and use than other languages. It’s just that they’re unfamiliar to n00bs so they seem unapproachable. If you just suspend your expectations from other languages, you’ll find that Elisp is actually an easy language to learn.

Update [2025-04-13 Sun 16:24]: Added link to Prot’s book.

-1:-- Prot On Elisp (Post Irreal)--L0--C0--2025-04-13T14:55:42.000Z

Kris Carta: How I Emacs (#2)

A few months ago I wrote a post detailing my Emacs journey so far.

Having read quite a lot of community posts, I think my path is one oft-taken:

  1. Start out with someone else's opinionated config.
  2. Slowly adapt that config to fit my own needs whims.
    • In my case how do I get this to be more like Vim?
  3. Play around with all the things Emacs can do, stuffing the config with packages and snippets copied uncritically from various sources.
  4. Read actual documentation (in my case Mastering Emacs) and learn to do things "the Emacs way".
  5. Rip out almost everything and start from scratch, focusing narrowly on my 'actual' needs.

At the time of my previous post, I was firmly at step 3.

My config was growing and growing, and I had layers of custom bindings and commands that were starting to feel productive, but were also keeping me from learning how to do things the Emacs way.

Emacs is radically extensible and can easily be made to emulate other tools that I'm familiar with like VS Code or Vim (via styling, plugins, or even just direct 'emulation'), but I felt an increasing dissonance the farther I pushed it in those directions.

Furthermore, as I lamented in a 'sidebar' in a previous post, I hadn't yet learned how to consult the built-in documentation in Emacs - a real limitation given that Emacs does more than any tool I've ever used in providing a veritable wealth of comprehensive documentation and guidance.

Working in Emacs

In January, I returned to work after a nearly 4-month long parental leave.

Returning to work meant:

  • Far more time spent in Org mode trying to keep my work life structured,
  • Far less tolerance for half-baked or half-working configuration,
  • Far less time (i.e. none) to play around with new packages and hobby-coding.

Org mode has been nothing short of a godsend for me at work, and as I wrote in my previous posts on "Why/How I Emacs", Org mode was the first "killer app" for me with adopting Emacs. I was skeptical at first, and the learning curve was steep, but I'm so happy I stuck with it. After a few 7am coffee sessions at work tweaking different parts of my Org mode config, I now have a simple little setup that I love and still has lots of room to mature.1

When Emacs 30.1 released in mid-February, I decided to dive in and finally purchase Mastering Emacs. I'm so happy I did! The book eases you smoothly into "the Emacs way", with a great balance of technical and mindset explanations, to bring you along without ever feeling overwhelming.

Rip & tear

From January to today (April), my Emacs config - which I take as representative of how I use the tool - has transformed.

Over the past few months, a number of rip-and-tear sessions have dramatically reduced my init.el file, from:

  • ~1000 LoC2
    • Split over multiple files (not a bad thing per se, but in my case indicative of too much complexity, too soon),
    • Mostly unannotated,
    • With:
      • ~150 LoC for custom bindings (using general.el and SPC as leader),
      • Most of the remainder a hodgepodge of useful & not-so-useful config lifted uncritically from other people's examples.

To:

  • ~400 LoC
    • In one file,
    • Mostly annotated,
    • Either written by myself or copied from package docs (while consulting the built-in references),
    • With:
      • ~150 LoC for general Emacs tweaks,
      • ~150 for packages, all of which I use regularly,
      • ~100 for custom functions (mostly for writing this blog on Jekyll or for making shopping lists).

Put in terms of a mindset shift, I've gone from seeing Emacs mostly as an amazing playground where anything that can be done in plaintext is possible,3 to seeing Emacs as an unparalleled organization and writing (and sometimes coding) tool that I can customize exactly to my needs.

Next steps

There's still lots to do and learn!

Every week I learn a new handful of Emacs bindings, but I'm still very much a noob - next week I hope to finally start grokking copying & pasting and figuring out how to use the "kill ring" and registers. Not a work session goes by where I don't catch myself struggling to copy-and-paste, especially if it's from another app into Emacs.

I now think I know how to make a decent coding setup for Ruby & JS/TS with Eglot and Treesit, but as I'm still mired in new-baby-parenting (with baby in my lap at this very moment), I simply haven't had the free time to start coding again.

I'm still making my way through Elisp guides, albeit at a glacial pace. I'll get to writing my own commands and scripts, eventually!

I have some obvious areas for improvement in my config. Eventually, I reckon I'll bring back Elpaca or something like it for smarter package management than I get out of the box. I'm using Org mode all the time at work, but haven't been using Agenda so much - as a consultant, I'm very dependent on Outlook to juggle my multiple work accounts & calendars, and don't see this changing in the near future.

Lastly, I'm still leaning entirely on Evil mode for in-buffer navigation & movements. This is one thing I can't see changing, as the Vim motions are baked into my fingers after over a decade of daily reinforcement. I'd love it if I could remove or disable everything in Evil except the Vim motions, but after looking into this a little, it doesn't seem easy to achieve.

That's all for now! Seeing the development from the first post to this one, I'm curious how things will be when I get around to making the next post in this little series ☺️

  1. I am drafting a post on my use of Org mode, but have some final things to try out before I feel it's fully matured and ready to show off.

  2. Not much by many users' standards, but it's a lot for me, being allergic to complexity and wanting to understand (as much as possible) what each little piece is doing.

  3. Not to downplay this mindset at all though - it's for years been my dream to find such a thing, and it's still what fascinates me most about Emacs.

-1:-- How I Emacs (#2) (Post Kris Carta)--L0--C0--2025-04-13T00:00:00.000Z

Jeremy Friesen: Re: Moving from Spacemacs Package to Hand-Crafted

What follows is my response to an email I received in . The quoted regions are from the sender, and the non-quoted regions are my response.

My hope in publishing this post is to provide a bit of insight into my process of adopting Emacs 📖 .

Email and Response

I’d like to use Emacs for more things. I tend to lose focus if I have to switch context too much.

Switching context is the pathway to a thousand paper cuts of mental fatigue.

Currently I use Spacemacs 📖 for Org-Mode 📖 , but I’d love to get into Emacs for all project planning and coding workflows (maybe even doing research and emailing as well).

It took me awhile to use Org-Mode in Emacs ; I was using Markdown. But with my latest job, I’m all in on Org-Mode . It is how I track and eventually report my time. I haven’t yet made the switch to email via Emacs , but it is on my radar.

I’m still not using “all of it” but I’m building my way towards using more of it.

I’ve seen some articles out there about Org-Roam 📖 and using capture buffers to efficiently capture thoughts, but once I start going down the rabbit hole of custom Org-Roam capture templates and customizing any package outside of the Spacemacs layer guardrails I get lost!

What are some of the capture templates you were creating? I’m curious to understand how you’re looking to use Org-Roam . Sidenote For myself I have about 5 capture templates; and I think one of them I rarely use.

Note: I had originally used Org-Roam . It is fantastic. However, I started extending it and found it somewhat more difficult and cumbersome. I grew concerned that I wouldn’t be smart enough in the future to further fix or enhance that. So I switched to Denote 📖 , which has amazing documentation and the functionality that I wanted for Org-Roam (namely backlinks).

I wrote about this in Migration Plan for Org-Roam Notes to Denote.

I also tend to get lost when I need to set up a development environment for any of the programming languages that I use. I’ve tried Spacemacs for Java at work, but all the packages installed by the Java layer cause my Spacemacs instance to slow to a crawl, and half of the Language Server Protocol (LSP 📖) commands don’t even work.

When I first started out, I picked Spacemacs , but I have little experience beyond a week or so with Spacemacs . I switched towards a basic install (e.g. fresh install no packages) and then went hunting.

I don’t program in Java; I’m a Rubyist, so I don’t know the language server ecosystem for Java. I know for Ruby 📖 there are two primary ones: Solargraph and Ruby Language Server. Both of those servers deliver different functionality. I’ve settled on Solargraph as it has more of what I’m after (namely jump to definition).

I write this wondering what functions you’re looking for from your language server.

Also, I have explored using LSP Mode, and it added a more visual experience; but settled on Eglot. In part because Eglot has a smaller implementation foot print, leveraging more of Emacs internals (and thus likely to stay “patched” and “maintained”).

This follows on something I identified when switching from Ivy to Consult as well as from Company to Corfu 📖 . See Switching from Company to Corfu for Emacs Completion.

I’d love to know how to surgically pick packages that will help me only, and cut out anything unnecessary.

This was the approach I took when moving from Spacemacs and Doom. I started from a blank slate, went through the Emacs tutorial, and then just started writing and coding. I would stumble and say “previous setups had done this. I’m going to go research how to do this.”

I’d find a few packages and look at their README’s, open issues, lines of code, etc and then adopt one. It was what first lead me to Ivy/Counsel/Swiper over Helm and would later lead me to Consult/Vertico/Marginalia.

I did the above with the mindset of “I am learning and building my tools; so other things will move slower.” In other words, if you have an urgent task to complete, maybe don’t do this. But if you have some slack in time, do this.

These days I recommend picking a good package manager (elpaca or straight.el) and installing one. I personally use straight.el.

I would also immediately adopt:

helpful
to improve discoverability
consult
a nice extensible wrapper around a lot of Emacs functions.
marginalia
to get inline guidance on your M-x commands; and many other things
vertico
I like the veritcal completion experience.

This is a principle of building out a solid foundation of interacting with your editor. From there move to the specific interactions.

I’d also love to know how to really fine-tune LSP and Debug Adapter Protocol (DAP 📖) to completely replace any need for an Integrated Development Environment (IDE 📖) in my day-to-day life.

I haven’t fine-tuned LSP and haven’t used DAP , so I’m curious what this means to you. Sidenote Since my original response, I have used DAP and found it to be very useful.

Regarding the cruft of Spacemacs , the startup time bothers me, and being dependent on all the packages that layer maintainers stuff into their layers. I want to explicitly craft my experience and not rely on what others think are valuable packages to install. I want to know everything going into my instance and how to use it.

I appreciate that there are viable Emacs starter kits. Sidenote Spacemacs , Doom, and Prelude are three that I can list off the top of my head.

Agreed. My pathway lead me to best understand what was happening. It has meant a lot more Yak-shaving, but I have an editor that moves with me.

My problem with this though, is I don’t know how to discover what I need. How do I know what packages to look for? How do I know when to write my own elisp config? No clue…

I used a multi-pronged approach. Starting from a basic setup, I would just start writing in Emacs and wonder “Hey, is there a function to do this?”

To know if there was something internally, meant I needed to figure out introspection of interactive functions. Then how to have meaningful descriptions of those functions. I would spend a bit of time scanning those functions. I might find something and read the docstring. Often this would lead to a neighborhood of functions. I could use that neighborhood to lookup the package. Then search Melpa for related or replacing packages.

Another strategy I adopted was to go to Melpa and perform some sorting. Finding the recently changed packages; I’d scan those quickly to see what is getting effort. I’d also sort on most downloaded, to see what other folks are using. And I’d look to last changed. All of which to orient to the breadth of options.

And if I stumbled upon a bunch of “thispackage-*” I’d dig in; after all folks were extending the baseline package.

I did all of this starting from a small baseline and building outward; from what was generally applicable to the specific. In a way, what is common to prog-mode and text-mode? Those are more useful than what is only text-mode and only prog-mode.

Also, should I break my reliance on full Vim 📖 keybindings and go for Emacs only? I don’t know. If I have to constantly re-bind keys for evil-mode with every package that I install I’m thinking that I’ll need to switch to Emacs

keybindings.

I don’t use Vim keybindings; even before using Emacs I was using its bindings. I’m wondering if something like the General package might be useful. I don’t use it but it helps manage these things.

-1:-- Re: Moving from Spacemacs Package to Hand-Crafted (Post Jeremy Friesen)--L0--C0--2025-04-12T12:42:11.000Z

ablatedsprocket: Migrating Appointments and Holidays to Diary

I use Org for many things, two of which are managing appointments and tracking my paid time off. I just learned about Emacs' Diary package from one of Protesilaos Stavrou's videos, and decided to see if moving my appointments and holidays to Diary made sense.

-1:-- Migrating Appointments and Holidays to Diary (Post ablatedsprocket)--L0--C0--2025-04-12T05:00:00.000Z

Emacs APAC: Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, April 26, 2025

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

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

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

-1:-- Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, April 26, 2025 (Post Emacs APAC)--L0--C0--2025-04-12T00:01:09.000Z

Protesilaos Stavrou: Emacs: My new ‘Emacs Lisp Elements’ book

I just published the book “Emacs Lisp Elements”. It is available for free and in freedom under the same terms as all documentation that ships with GNU Emacs:

I provide a big picture view of the Emacs Lisp programming language by combining prose with code. The goal is to give readers an idea of how Elisp works by showing some of the main concepts or patterns discernible in everyday code.

Some chapters are beginner-friendly, while others dive into deeper waters. Though I think everything is still approachable, as I try to explain basic concepts and take things one step at a time.

The book is not meant to be a replacement for the built-in Emacs Lisp Reference Manual. It simply gives you enough information to reason about Elisp. Once you start extending Emacs, the rest will follow naturally.

I hope you enjoy it and continue to have fun with Emacs.

-1:-- Emacs: My new ‘Emacs Lisp Elements’ book (Post Protesilaos Stavrou)--L0--C0--2025-04-12T00:00:00.000Z

Jeremy Friesen: Extending consult-notes Package to Add Draft Blog Post Candidates

Introduction

I’m provisionally using the consult-notes package to provide find and search functionality for the notes I write using Denote 📖 . I write provisionally as I want to explore its utility relative to other things (such as the consult-denote package.

But that is not why I’m writing this post, instead I’m writing to draw attention to an approach I took at re-using implementation details in Emacs 📖 in a way that is different from other languages. Namely scoped variable binding.

I find this important as it is a conceptually different approach from every other programming languages I’ve used (e.g., Ruby, Python, Javascript, PHP, Basic, etc.)

Let’s dig in.

Consult Notes Denote Implementation

The consult-notes-denote–source is defined as follows. Importantly, the :items property is a chunky lambda. Sidenote By default, I’m hiding the following code, as it is a lot to read through.

Implementation of the consult-notes-denote--source constant.
(defconst consult-notes-denote--source
  (list :name     (propertize "Denote notes" 'face 'consult-notes-sep)
        :narrow   ?d
        :category consult-notes-category
        :annotate consult-notes-denote-annotate-function
        :items    (lambda ()
                    (let* ((max-width 0)
                           (cands (mapcar (lambda (f)
                                            (let* ((id (denote-retrieve-filename-identifier f))
                                                   (title-1 (or (denote-retrieve-title-value f (denote-filetype-heuristics f)) (denote-retrieve-filename-title f)))
                                                   (title (if consult-notes-denote-display-id
                                                              (concat id " " title-1)
                                                            title-1))
                                                   (dir (file-relative-name (file-name-directory f) denote-directory))
                                                   (keywords (denote-extract-keywords-from-path f)))
                                              (let ((current-width (string-width title)))
                                                (when (> current-width max-width)
                                                  (setq max-width (+ consult-notes-denote-title-margin current-width))))
                                              (propertize title 'denote-path f 'denote-keywords keywords)))
                                          (funcall consult-notes-denote-files-function))))
                      (mapcar (lambda (c)
                                (let* ((keywords (get-text-property 0 'denote-keywords c))
                                       (path (get-text-property 0 'denote-path c))
                                       (dirs (directory-file-name (file-relative-name (file-name-directory path) denote-directory))))
                                  (concat c
                                          ;; align keywords
                                          (propertize " " 'display `(space :align-to (+ left ,(+ 2 max-width))))
                                          (propertize (funcall consult-notes-denote-display-keywords-function keywords) 'face 'consult-notes-name)
                                          (when consult-notes-denote-dir
                                            (propertize (funcall consult-notes-denote-display-dir-function dirs) 'face 'consult-notes-name)))))
                              cands)))
        ;; Custom preview
        :state  #'consult-notes-denote--state
        ;; Create new note on match fail
        :new     #'consult-notes-denote--new-note))

I wanted to re-use the implementation details of the :items property, but specify my own consult-notes-denote-files-function. Namely one that would create a candidate list of all denote notes tagged with blogPosts but did not have an associated published URL; which in my case is a note lacking a ROAM_REFS property.

Custom Candidate Function for Draft Blog Posts

In a shell, I can query for draft blog posts as follows:

fd "_blogPosts.*\.org" /path/to/denote/dir | xargs rg "^#\+ROAM_REFS:" \
--ignore-case --files-without-match --sortr modified

Let’s break that down:

  • Use fd 📖 to find denote name conformant Org-Mode 📖 files with the tag blogPosts.
  • Pipe those files to Ripgrep 📖 selecting from that file list files that do not have a line starting with #+ROAM_REFS:.

Re-Using the Existing

My first implementation copied the logic of consult-notes-denote--source. However, that was a lot of duplicate logic. I set about what it would take to re-use much of that logic.

The following, with code available on Github, is what I settled:

(use-package consult-notes
  :config
  (require 'consult-notes-denote)
  ;; Add a draft blog post section to my consult notes.
  (add-to-list 'consult-notes-all-sources
    `(:name ,(propertize "Draft Blog Posts" 'face 'consult-notes-sep)
       :narrow ?D
       :cateogry consult-notes-category
       :items jf/consult-notes/draft-blog-posts/items
       :state  consult-notes-denote--state))
  (defun jf/consult-notes/draft-blog-posts/items ()
    "Return a propertized list of draft blog posts.

The `consult-notes-denote--source' :items value has most all of the
logic I want (encoded as a `lambda').  However, I want to provide my own
files used in generating the candidates; this is done by way of a
contextual override of `consult-notes-denote-files-function'.

By convention, a draft blog post is one that is tagged with blogPosts
but does not yet have a ROAM_REFS property (meaning I have not
associated the org-mode document with the corresponding published URL of
the document)."
    (let ((consult-notes-denote-files-function
            (lambda ()
              (split-string-and-unquote
                (shell-command-to-string
                  ;; First narrow to files with tags
                  (concat
                    "fd \"_" jf/denote/keywords/blogPosts ".*\\."
                    (symbol-name denote-file-type) "\" "
                    (denote-directory) " | "
                    "xargs rg \"^#\\+ROAM_REFS:\" -i --files-without-match --sortr modified"))
                "\n"))))
      (funcall (plist-get consult-notes-denote--source :items)))))

First, instead of my :items having that chunky lambda, I specified a function (e.g. jf/consult-notes/draft-blog-posts/items). For the context of that function’s evaluation it overwrote the definition of consult-notes-denote-files-function. Then within that context called the :items property function stored in consult-notes-denote--source.

The docstring of jf/consult-notes/draft-blog-posts/items contains much of the relevant information.

’s implementation might look “chunky” but much of it is defining the overriding function. Below is a theoretical refactoring that might further clarify what is happening:

(defun jf/consult-notes/draft-blog-posts/items ()
  (let ((consult-notes-denote-files-function
         #'jf/consult-notes/draft-blog-posts/files-function))
    (funcall (plist-get consult-notes-denote--source :items))))

Alternatives?

I consider this somewhat magical coming from most other languages. I might be able to accomplish this in Ruby 📖 , but this would likely not be feasible in any other languages in which I’ve worked.

Other languages might require that I pass the consult-notes-denote-files-function as a parameter (either directly or by way of a property on a scoping context).

However the variable scoping meant that I:

  • could re-use code
  • didn’t need to submit a pull request

Conclusion

I wanted to have a function to find my draft blog posts. I also wanted to minimize duplicate effort. I could’ve used the underlying completing-read to do this lookup, but I also wanted to explore tying into a package I have been using.

And in tying into that usage, I am encouraging myself to use the package and make sure it is something I want to keep; or jettison and favor something else.

-1:-- Extending consult-notes Package to Add Draft Blog Post Candidates (Post Jeremy Friesen)--L0--C0--2025-04-11T20:56:13.000Z

Irreal: Configuring Journelly

After my post Initial Thoughts On Journelly, in which I recounted how I’m using Álvaro Ramírez’s Journelly app, Bren Smith asked me to share my configuration for sharing files between my iOS devices and my Mac. If you’re an Emacs user and want to enjoy the full power of Journelly, it’s important to be able to share the files between your iOS devices and your Macs.

The thing is, it’s so easy that it didn’t occur to me an explanation was needed. By default, Journelly keeps all its files locally on your phone or other iOS device but it’s possible to store them in the iCloud or, really, anywhere that’s reachable from your Mac and your iPhone/iPad. If you click on the three dots at the upper right of the Journelly screen and then choose Storage → iCloud Drive, Journelly will allow you to store your files in the iCloud. From there you can easily access them from your Mac.

Apple’s default location for storing the files is a long and complex file spec so you’ll probably want to set a symbolic link to it so that it’s easy to access from the Mac. Once you’ve done that, it’s easy to bring up the Journelly file in the usual way from Emacs or any other app.

It’s also possible to put the files somewhere else in the iCloud by using the Storage → Other… option. I did this so I wouldn’t have to navigate the complex file spec but in the end, it turned out to be easier to just set a symbolic link so I recommend you just use the Storage → iCloud Drive option. On the other hand, I think, but haven’t verified, that you can use Storage → Other… to store the files anywhere you like but I don’t know the details.

The other thing to keep in mind is that Journelly is still in beta so any or all of this could change. Still, I’ve been using this setup every day without any problems.

-1:-- Configuring Journelly (Post Irreal)--L0--C0--2025-04-11T15:15:18.000Z

Anand Tamariya: CEDET: Across the Language Barrier

Emacs at its core is a Lisp interpreter written in C. If you are an Emacs developer, this means you often need to jump between C and Lisp programming languages. CEDET is capable of maintaining project-wide tag database and hence allows the user to seamlessly jump across the definitions in either C or Lisp. 

 


Demo: Jump to definition across Lisp and C (including pre-processor) using CEDET.

Note: Emacs provides necessary tools to make development easier.  find-function will also do the job. However, this article is about CEDET.


Another useful application is when you are editing an HTML which usually entails editing HTML, CSS and JS.


Setup instructions are here: https://lifeofpenguin.blogspot.com/2021/04/gnu-emacs-as-lightweight-ide.html


-1:-- CEDET: Across the Language Barrier (Post Anand Tamariya)--L0--C0--2025-04-11T04:38:00.000Z

Jeremy Friesen: Extending Org-Mode Export to Include Acronyms Section

Introduction

In Denote Emacs Configuration I wrote about adding abbr and abbr-plural link types. In short, when I exported these link types the process would render an abbreviation and its title, based on my glossary entries. At least that’s what they did for HTML 📖 and plain text.

But until , I had deferred on handling the LaTeX export. There was a rudimentary handling of it, but I figured I could do more. So I did.

Abbreviations on My Blog

First, I want to share how I handle this for my blog, as its different from other export backends.

I write my blog posts in Org-Mode 📖 and use Ox-Hugo 📖 to export into a Markdown format.

When exporting for my blog, I pass all of the acronyms through a custom my glossary Hugo 📖 short-code. Sidenote See Github for my glossary short-code implementation; warning this is a complicated short-code that I don’t want to touch. During the processing, the first time I encounter a glossary entry, I render the title/name and its abbreviation. After that I use the abbr HTML tag.

Extending LaTeX Export

For LaTeX 📖 exports and thus generating PDFs 📖 I would like similar behavior as my blog.

I had found How to generate list of abbreviations in LaTeX? (from the LaTeX Stack Exchange); which demonstrates using one of two LaTeX packages:

acro
declare the acronyms in the header and then reference throughout.
acronym
explicitly render the acronymes in their own section.

Due to my initial approach and understanding, I went with the acronym package.

Let’s look at the code changes I made. Sidenote Here’s the Github commit that shows the totality of changes. There are four steps:

In summary, these described changes append a section named “List of Acronyms” that assist in untangling the acronyms I’ve used within the document.

Let’s dive in.

Register an Encountered Abbreviation

I was already handling abbreviations in LaTeX ; though they were somewhat broken in that I was rendering the associated name/title of the item.

Based on my read through of the Stack exchange suggestions, I wanted to add to a list of abbreviations I’d encountered. Sidenote I suppose I could’ve generated a full list of abbreviations for each LaTeX
export, but I have 400+ abbreviations and that seemed rather gross and excessive.

I added the following comments and code to my jf/denote/link-ol-abbr-with-property function.

;; When we encounter an abbreviation, add that to the list.  We'll
;; later use that list to build a localized abbreviation element.
(let ((abbr-links
        (plist-get info :abbr-links)))
  (unless (alist-get keyword-value abbr-links nil nil #'string=)
    (progn
      (add-to-list 'abbr-links (cons keyword-value title))
      (plist-put info :abbr-links abbr-links))))

With this, each time I encounter an abbreviation, I make not of it for later processing. The note includes the abbreviation key and its title (e.g. “PDF” and “Portable Document Format”).

Note: Based on my debugging, there is a caching layer that minimizes calls to this function. That is, we will only call the link generating function once for a given link (where the link identity is the key and description).

Conditionally Render an Acronym Section

The jf/ox/filter-body/latex function checks if we’ve encountered abbreviations. If so we append to the body of the document each encountered abbreviation (sorted alphabetically) along with its descriptive “title.”

(defun jf/ox/filter-body/latex (body backend info)
  "Conditionally add a list of acronyms to the exported LaTeX document.

To have a meaningful render, this requires using the acronym LaTeX
package.  The `jf/ox/filter-final-output/latex' handles injecting that
LaTeX package."
  (if-let ((abbr-links (plist-get info :abbr-links)))
    ;; We encountered some links, let's add a section.
    (progn
      (concat
        body
        "\n\\section{List of Acronyms}\n"
        "\\begin{acronym}\n"
        (mapconcat
          (lambda (cell)
            "Create an acro for link."
            (format "\\acro{%s}{%s}" (car cell) (cdr cell)))
          ;; Sort the keys alphabetically.  Otherwise they are rendered
          ;; in the reverse order in which they are encountered.
          (sort abbr-links :key #'car)
          "\n")
        "\n\\end{acronym}\n"))
    body))

In this case the body parameter is the inner-content of the eventual LaTeX document. It does not contain the header information nor the closing \end{document}.

Conditionally Use the Acronym Package

The purpose of this step is to ensure that we have are using the acronym LaTeX package. I could’ve updated all of my class declarations, but opted for this route.

The jf/ox/filter-final-output/latex function injects the acronym package into the final LaTeX document.

(defun jf/ox/filter-final-output/latex (body backend info)
  "Conditionally add an acronym package to exported LaTeX document."
  (if-let ((abbr-links (plist-get info :abbr-links)))
    (replace-regexp-in-string
      "^\\\\documentclass\\(.*\\)"
      (lambda (md)
        "Acronym package to matching line."
        (concat "\\\\documentclass" (match-string 1 md)
          "\n\\\\usepackage[printonlyused,withpage]{acronym}"))
      body)
    body))

In this context, the body parameter is the totality of content we will write to the interstitial tex file. That is it contains the LaTeX header information as well as the begin and end blocks.

I’m not enamored with using a regular expression, and might tinker with this a bit more.

Add Export Handling Functions

None of the above matters, until I append these filters to my LaTeX export.

The jf/org-export-change-options function and adding to the org-export-filter-options-functions ensures that we call the functions that conditionally render an acronym section and conditionally use the acronym package.

(defun jf/org-export-change-options (plist backend)
  "Conditinally add filter functions to our org-export."
  (cond
   ((equal backend 'latex)
    (if-let ((filter-body
              (plist-get plist :filter-body)))
        (progn
          (add-to-list 'filter-body jf/ox/filter-body/latex)
          (plist-put plist :filter-body filter-body))
      (plist-put plist :filter-body '(jf/ox/filter-body/latex)))
    (if-let ((filter-final-output
              (plist-get plist :filter-final-output)))
        (progn
          (add-to-list 'filter-final-output jf/ox/filter-final-output/latex)
          (plist-put plist :filter-final-output filter-final-output))
      (plist-put plist :filter-final-output '(jf/ox/filter-final-output/latex)))))
  plist)

(add-to-list 'org-export-filter-options-functions
             'jf/org-export-change-options)

The code is a bit chatty, but I wanted to ensure that I did not clobber other filter functions; instead appending to the list of possible filters.

A Bit of How I Got There

While working towards this solutions, I took a few different pathways. Ultimately, I leveraged the Emacs 📖 debugger and inline documentation of the ox package: the Org-Mode export “framework” package.

I allude to the revelatory moment in the change of the jf/denote/link-ol-abbr-with-property parameter name and doc string; changing the parameter name from protocol to info. I had carried protocol over from some other implementation; but a bit of debugging showed me that this was very much the information channel shared throughout the export.

With that knowledge, I added to the info parameter and set about finding a meaningful place to check the content.

It became a matter of reading through some source code to see how I might apply filters; something I knew existed based on previous reads of the ox package.

Why This “Vanity Project”?

Using a glossary of abbreviations is an endeavor that started with a two-fold goal:

And a third goal that has emerged over time: Untangling the Acronyms Used At Work.

Registering as Back-Links

When I was writing my posts in Markdown and directly into my Hugo project, I established back-link capabilities for glossary items. As I moved to Org-Mode for writing and first Org-Roam 📖 and then Denote 📖 , I wanted to continue to have access to back-links.

And since my abbr and abbr-plural conform to Denote ’s linking schema; using an abbreviation link registers as a back-link. Which means later on I can easily find written references to those glossary terms.

Initial Descriptions of Abbreviations

We live in a world of acronyms. Consider the three letters `RPG`:

  • Rocket Propelled Grenade
  • Role-Playing Game
  • Report Program Generator

All of those are terms I’ve used when I’ve typed or spoken `RPG`.

When blogging about role-playing games, I don’t want to keep writing “Role-Playing Game” over and over. I want to use the abbreviation. But the first time I introduce an abbreviation, I want to provide the full name.

In using that strategy, I hope I’m improving the accessibility of my writing. Helping the reader acclimate to the terms I’m using, while also not maintaining a verbosity that might be off-putting for readers familiar with the domain.

Untangling the Acronyms Used At Work

Since I started blogging, I have changed jobs four times. I have joined:

  • An academic library
  • An early stage start-up
  • A software consultancy
  • A new-to-me tech domain of product development/engineering

At each of these, management and other folks would use new-to-me terms. Each time, I’d clarify then write down the acronym and its “title”.

This helped me remember what we were chattering about. And in some cases, depending on the documentation maturity of the organization, I’d contribute my glossary terms back to the organization’s glossary; or my team’s glossary.

Conclusion

Given that I write via Org-Mode documents for different audiences and that I have a glossary of abbreviated terms, I think it is useful to share at least a bit about that term.

For the future, I did note that the acronym package allows for me to include a description as a third parameter to rendering the list. My glossary also includes descriptions, so at some point, I’m certain I’ll be adding that to my LaTeX output.

-1:-- Extending Org-Mode Export to Include Acronyms Section (Post Jeremy Friesen)--L0--C0--2025-04-11T02:41:49.000Z

Irreal: Another Happy Journelly User

As you all know by now, I’ve become a huge Journelly fan. Its author, Álvaro Ramírez, thinks of it as Twitter for your private use. That’s a reasonable description given its user interface but it doesn’t begin to capture its real power.

As more and more users have started using the beta, I’ve come to realize that Journelly is a bit of a shape shifter that adapts itself to your needs. Ramírez, apparently, just wanted a simple app that he could use like a private Twitter. My use case is implementing my memo book. I want to capture entries on my phone and keep the results as a permanent record of my daily activities.

JTR over at The Art Of Not Asking Why has a different conception. He is using it as a sort of staging area for capturing notes that he then refiles to the appropriate Org files. That’s possible because one of Journelly’s strengths is that it keeps its data in Org format and can arrange to sync its files with your Mac and other iOS devices.

JTR explains all this in his latest post on Journelly in which he reveals himself as another happy Journelly user. It’s a nice post that recounts his experience with using Journelly. As I say, his use is slightly different from mine but that just speaks to the power and flexibility of Journelly. If you’re the slightest bit interested in Journelly, you should read his post.

About the only thing I don’t agree with is that JTR says that if you’re not an Emacs user, you should probably look elsewhere. Journelly is such a good fit for my memo book that I’d probably use it even if I weren’t an Emacser. But, of course, I am and the fact that its records are in Org format just expands its power for me. As Jack Baty says in a comment to JTR’s post,

I think he things of the .org thing as an implementation detail, so as to not limit his audience, but you’re right, for me it’s The Thing™.

The other thing that JTR mentions that I forgot to write about is that after tapping the big plus sign, you can enter your note orally in the usual iOS way. That’s really powerful, especially when you’re on the run.

-1:-- Another Happy Journelly User (Post Irreal)--L0--C0--2025-04-10T15:22:26.000Z

TAONAW - Emacs and Org Mode: First week with Journelly

I’ve been testing out Journelly for the last week, and I have a lot of good things to say about it. Before we get there though, I want to start by explaining what it is - which is also the single piece of criticism I have about it at the moment.

Ramírez explains his app as “kinda like tweeting but for my eyes only,” which is true, but in my opinion, not the main point. After all, tweeting (X-ing?) or tooting or whatever is not new. At the same time, there are many excellent journaling apps for iOS, including Apple’s own Journal and the celebrated Day One. Putting Journelly against these apps is also misleading. This is partly why I didn’t understand the excitement around Journelly when I first heard about it, especially from an enthusiastic Emacs user1.

I would take a step further and say that the average iPhone user, who trusts Apple for all syncing and backup needs and doesn’t know what Emacs is, should skip Journelly and go with the other apps, at least for now.

But, if you’re reading this post in your org-mode category on your RSS reader, you are not a regular citizen of the internet. We are Emacs org-mode users. So let me tell you why this application is amazing.

Journelly is a front-end iOS app for org-mode. The notes you take with Journelly are written entirely in org-mode, in a single org-mode file, which you can sync to your Mac with iCloud or another method that works with both iOS and macOS.

Every note you take, which you create by tapping a large “+” sign in the app, starts with an org-mode header made of a time and location (which you can disable), like so:

* [2025-04-08 Tue 14:28] @ 123 Sesame St

On the app, these entries look very polished:

A Journelly app screenshot shows entries for Wednesday, April 9, 2025, with a link to a buy it for life article and a YouTube video about sleep deprivation.

Behind the scenes, Journelly also adds the following in the header’s properties drawer (I removed the values below, but Journelly populates them automatically):

    :PROPERTIES:
    :LATITUDE: 
    :LONGITUDE: 
    :WEATHER_TEMPERATURE: 
    :WEATHER_CONDITION: 
    :WEATHER_SYMBOL: 
    :END:

You won’t see these properties (including the weather, which is included in the app when you take a note) on the app in the default view, but you can switch views to the “Markup” to see raw org-mode syntax. Speaking of org-mode syntax, let’s talk about some Journelly magic.

I use iCloud to sync a Desktop folder for org files to my Mac. This is also where I told Journelly to store its org file (which is called Journelly.org - at this point, it doesn’t seem like you can change this file name). This means that after making an update in Journelly, I can visit the org file with Eamcs on my Mac within seconds.

Then, through Emacs, I can make whatever changes I want to this org file: fixing typos, adding emphases (say, make certain words show in italics or bold), adding links, whatever. The next time I launch Journelly on my iPhone, it will notify me of the file change and prompt me to reload it. One tap and everything is rendered with the same polished look you see above as if I wrote it from within Journelly itself. The whole thing is so freaking seamless and visually appealing it’s addicting, as I found out quickly.

Since Journelly’s org file is included in my org-mode agenda (the folder it’s in is defined in my agenda files in Emacs on the Mac), I can easily refile entries from Journelly.org into my big journal.org file which I’ve been updating since 2018. This means that notes I take on the fly, with timestamps and location (and weather!) data, are added to my journal within seconds. After doing this for a couple of days, a thought occurred to me: why stop there?

A stylish bar with a vintage facade and neon BAR sign sits along a city street.

As you can see, Journelly knows how to deal with photos well. They are stored in an images folder inside Journelly.org.assets, which is easily accessible if you sync the app’s org file. So, if it’s so easy to use for notes and pictures, why not use Journelly for my project notes and meetings?

My favorite way to work with Journelly so far is by dictation: I talk to write. This last part is what caused me to change how I think of project notes in general. Instead of writing notes for each project in Emacs, why not just write or dictate them in Journelly? I can easily refile headings into related projects later if I wish, whether it’s a work task, a personal journal entry, or a link from the internet.

I don’t have to think of what goes where as I take notes. it all goes to one place where it’s easy to see and a pleasure to work with. This concept is not new to me (and it’s well documented in productivity systems like GTD as “inbox” or what have you) - I used to have an OhSnap.org to capture everything. But on my phone, through Orgzly on Android or Beorg on the iPhone, it’s not easy to see nor a pleasure to work with. These apps are geared toward tasks and to-do lists, and that’s part of the problem: when I capture information, I don’t usually know if it’s a task yet, just a note, a workflow, and where it goes. If I do know, then I’d put this task directly into the org file it belongs, so what’s the point? And it all looks so bland and boring and uninviting. On other org-mode apps, writing notes is a chore.

Journelly blows this mental challenge away. It invites me to take notes and just notes. It’s the opposite of a chore. “Go ahead,” it winks at me, “spill the beans. Snap some pictures. We’ll work on the details later.” Suddenly, I’m free to let my mind go.

Later on Emacs, I refile personal headers with their notes into my journal, minutes from meetings into my project files, and use org-attach to move the images from Journelly2 to their correct place where they are also stored for backup.

This makes me wish Journelly would have the ability to attach voice memos, which would work great in a journalling app. I hear from Ramírez this is on his list and will come soon (judging from how responsive he is, we won’t wait long). Who knows, perhaps other files could be added, like video clips and even PDFs for flight or train ticket scans. For now though, the app is still in beta, and Ramírez is working overtime to get version 1.0 out. I will be one of his first customers, that’s for sure.

In the few days I’ve used Journelly, it has transformed from a nice way to keep a journal to an essential mind dump tool for saving anything I need. I now reach for my phone whenever I want to capture thoughts because it’s just so pretty and easy. If you’re an org-mode user on an iPhone, I highly recommend you give it a try as soon as it’s out.

Footnotes

1 - I do tend to miss details, and while the Mac Observer review I read does mention Journelly is based on org-mode, it’s somewhere toward the bottom where my quick scan of the article missed.

2 - at this point, Journelly stores full-resolution images, which can quickly add up in space on the iPhone. This is easy to resolve with a Shortcut on iOS or a script with ImageMagick. I prefer to do some slight editing to my images anyway (a bit of cropping, some levels adjustments, etc), but these are also things you can do on the phone and automate the compression.

-1:-- First week with Journelly (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-04-09T18:50:15.000Z

James Dyer: Ollama-Buddy 0.9.20: Curated AI Prompting with Awesome ChatGPT Prompts

  • Added ollama-buddy-awesome.el to integrate Awesome ChatGPT Prompts.

ollama-buddy-awesome is an ollama-buddy extension that integrates the popular Awesome ChatGPT Prompts repository, allowing you to leverage hundreds of curated prompts for various tasks and roles right within your Emacs environment, I thought that since I have integrated the fabric set of curated prompts, so then why not these!

There is a video demonstration here : https://www.youtube.com/watch?v=5A4bTvjmPeo

Key Features

  1. Seamless Sync: Automatically fetch the latest prompts from the GitHub repository, ensuring you always have access to the most up-to-date collection.

  2. Smart Categorization: Prompts are intelligently categorized based on their content, making it easy to find the perfect prompt for your task.

  3. Interactive Selection: Choose prompts through Emacs’ familiar completion interface, with category and title information for quick identification.

  4. Effortless Application: Apply selected prompts as system prompts in ollama-buddy with a single command, streamlining your AI-assisted workflow.

  5. Prompt Management: List available prompts, preview their content, and display full prompt details on demand.

Getting Started

To access the Awesome ChatGPT prompts, just select the transient menu as normal and select “[a] Awesome ChatGPT Prompts”, this will fetch the prompts and prepare everything for your first use and give you a transient menu as follows:

Actions
[s] Send with Prompt
[p] Set as System Prompt
[l] List All Prompts
[c] Category Browser
[S] Sync Latest Prompts
[q] Back to Main Menu

Now available are a vast array of role-based and task-specific prompts, enhancing your ollama-buddy interactions in Emacs!

-1:-- Ollama-Buddy 0.9.20: Curated AI Prompting with Awesome ChatGPT Prompts (Post James Dyer)--L0--C0--2025-04-09T12:43:00.000Z

Ruslan Bekenev: Newsticker - Emacs built-in RSS reader

I’ve been browsing Emacs’s The Info Directory (C-h i) the other day and came across a menu item called Newsticker.

Turned out is a built-in RSS reader I never heard of. I know Emacs has Gnus which I tried to use as RSS reader before but didn’t succeed. Then, as probably most of Emacs users, I moved to Elfeed which is great.

Here is how Newsticker looks like:

hermit logo

isn’t it nice? You can open it with M-x newsticker-show-news.

It’s been a few weeks since I decided to use it.

Navigation

Navigation is very simple:

  • f and F for switching feeds forward/backward
  • n and p for switching posts
  • <RET> to read full article

Configuration

Setting feeds is only slightly different from Elfeed:

(setq newsticker-url-list '(
 ("Planet Emacslife" "https://planet.emacslife.com/atom.xml")
 ("<name>" "<url>")))

Also when q is pressed the whole Newsticker is closed BUT its buffers are still there which I don’t really like.

So I came up with this elisp:

(defun my/close-newsticker ()
 "Kill all tree-view related buffers."
 (kill-buffer "*Newsticker List*")
 (kill-buffer "*Newsticker Item*")
 (kill-buffer "*Newsticker Tree*"))

(advice-add 'newsticker-treeview-quit :after 'my/close-newsticker)

Now when newsticker-treeview-quit function is called the Newsticker buffers getting killed too.

Why use built-in tools

I remember I’ve read, I think in Irreal’s blog about not understanding some people’s obsession to use built-in tools in Emacs.

I can’t quite explain it either but I do feel drawn towards built-in tools too haha.

Naturally I try to track/think about every extension I add to my config to make sure it’s up to date, not abandoned, supports my version of emacs, how much dependencies it has, etc. And even when everything is ok, I still feel like someone can take it from me if let’s say a maintainer will decide to archive the project or something.

It just sits in my brain on the background and slowly drains my energy. It’s maybe silly reason but I just feel more vulnerable with more dependencies.

Anyways, I suggest you to try Newsticker, it’s very interesting mode.

-1:-- Newsticker - Emacs built-in RSS reader (Post Ruslan Bekenev)--L0--C0--2025-04-09T06:47:34.000Z

Jeremy Friesen: Adding Actionable Indicators to my Emacs Mode Line

I’ve long used the Typopunct Emacs package for things like “smart quotes”, em-dashes, and en-dashes. I eschew rendering minor modes in my mode-line, as this is chatty and not very helpful.

However, I often find myself toggling typopunct-mode on and off. Which prompted me to explore adding an indicator to the mode-line.

Before the Change

First, lets look at the current anatomy of my mode-line. It’s rather compact; though the buffer name, branch name, and function name can make for quite a bit of text.

While writing this document, the following is my mode-line text:

Adding Actionable Indicators to my Emacs Mode Line ¶ “Org” blog-posts  main ⨍ := Before the Change

Let’s break this down a bit:

  • Adding Actionable Indicators to my Emacs Mode Line: the buffer name.
  • indicates I’m in a text-mode.
  • ”Org” indicates the major mode is Org-Mode 📖 , and as this is wrapped in quotes means that I’ve activated typopunct-mode for this buffer.
  • blog-posts is the name of the projectile project.
  •  main indicates that the version control branch is main.
  • ⨍ := Before the Change is the name of the “function” that contains point. In Org-Mode , that is the containing headline for point.

My mode-line conveys more conditional information, so lets look to my mode-line-format. Sidenote See Github for more context regarding mode-line-format.

(setq-default mode-line-format
    '("%e" " "
       jf/mode-line-format/org-clock
       jf/mode-line-format/vterm
       jf/mode-line-format/kbd-macro
       jf/mode-line-format/narrow
       jf/mode-line-format/buffer-name-and-status " "
       jf/mode-line-format/major-mode " "
       jf/mode-line-format/project " "
       jf/mode-line-format/vc-branch " "
       jf/mode-line-format/flymake " "
       jf/mode-line-format/eglot
       jf/mode-line-format/which-function
       ))
jf/mode-line-format/org-clock
Show an indicator when I have an active Org-Mode clock.
jf/mode-line-format/vterm
Show when I’m run vterm (and allow me to quickly toggle copy mode).
jf/mode-line-format/kbd-macro
Indicate when I’m recording a keyboard macro.
jf/mode-line-format/narrow
Indicate when I’ve narrowed the buffer.
jf/mode-line-format/buffer-name-and-status
The name of the buffer and whether its read-only or not.
jf/mode-line-format/major-mode
Indicate the major-mode as well as its type (e.g. programming mode, text mode, etc).
jf/mode-line-format/project
What, if any, is the projectile project name.
jf/mode-line-format/vc-branch
What, if any, is the version control branch. I can click on it to see the Magit 📖 status.
jf/mode-line-format/flymake
When Flymake is active, show its information.
jf/mode-line-format/eglot
When eglot is active, show its information.
jf/mode-line-format/which-function
Show the name of the “function” at point.

After the Change

, I set about updating my mode line to show me when typopunct-mode is on or off. I also set about making that indicator actionable (e.g. I can click on it).

Now when typopunct-mode is on, I wrap in smart quotes the major mode name. (e.g. “Org Mode”) and when it is off, the major mode is not wrapped (e.g. Org Mode). Further, I can click on the major mode to now toggle the typopunct-mode state.

I needed a function that would force updating the mode line when the typopunct-mode changed. Sidenote See Github for more context regarding the jf/toggle-typopunct-mode function.

(defun jf/toggle-typopunct-mode ()
  "Toggle `typopunct-mode'."
  (interactive)
  (if typopunct-mode
    (typopunct-mode -1)
    (typopunct-mode 1))
  (force-mode-line-update))

To add a click action to the text, I first needed to define a mode map. Sidenote See Github for more context regarding the jf/mode-line-format/major-mode-map variable.

(defvar jf/mode-line-format/major-mode-map
  (let ((map
          (make-sparse-keymap)))
    (define-key map [mode-line down-mouse-1]
      #'jf/toggle-typopunct-mode)
    map)
  "Keymap to display `typopunct-mode'.")

And then I modified the function that renders the major mode. Sidenote See Github for more context regarding the jf/mode-line-format/major-mode-name function.

(defun jf/mode-line-format/major-mode-name ()
    "Render the major mode as text.

When `typopunct-mode' is active, provide an actionable indicator of its
active nature."
    (let ((fmt
            (if typopunct-mode "“%s”" "%s")))
      (propertize
        (format fmt
          (capitalize
            (string-replace "-mode" "" (symbol-name major-mode))))
        'face
        (if (mode-line-window-selected-p)
          'mode-line 'mode-line-inactive)
        'local-map
        jf/mode-line-format/major-mode-map
        'help-echo
        (concat "mouse-1: #'jf/toggle-typopunct-mode"))))

Conclusion

I want my mode-line to remain compact, yet information rich, actionable, and not chatty. I find this minor tweak to be a quality of life improvement.

-1:-- Adding Actionable Indicators to my Emacs Mode Line (Post Jeremy Friesen)--L0--C0--2025-04-09T02:07:48.000Z

Protesilaos Stavrou: Emacs: dired-preview version 0.5.0

This is a simple package to automatically preview in a window the file at point in Dired buffers. Preview windows are closed when they are no longer relevant, while preview buffers are killed if they have not been used for other purposes beside previewing. The package provides several customisation options to control its behaviour.

Below are the release notes


Version 0.5.0 on 2025-04-09

This version contains a few bug fixes and minor refinements that should improve the behaviour of the package.

Revised how preview windows are deleted

In the past, dired-preview-mode could delete windows that held another buffer, thus undoing the window layout that was present before a preview buffer was displayed. Now dired-preview-mode makes sure to only delete windows that have not had another buffer shown in them, i.e. windows that were created just for preview purposes.

Directories no longer get misunderstood as “large files”

We tweaked how we test the type of the buffer-to-be-preview such that directories are not mistaken for “large files”. This was an issue for Mac computers that Sean Devlin brought to my attention in issue 27: https://github.com/protesilaos/dired-preview/issues/27.

The dired-preview-delay has a 0.1 second minimum to avoid instability

A value of 0 could lead to a noticeably degraded experience while navigating the Dired buffer. Thanks to Yiyu Zhou for reporting the matter in issue 2 on the GitLab mirror: https://gitlab.com/protesilaos/dired-preview/-/issues/2.

Files without an extension can also be ignored

The user option dired-preview-ignored-extensions-regexp will now also match files without an extension, such as the .DS_Store on Mac computers.

Thanks to Sean Devlin for the contribution in pull request 26: https://github.com/protesilaos/dired-preview/pull/26. The change is within the ~15-line limit, meaning that Sean does not need to assign copyright to the Free Software Foundation.

The preview buffer is the “other window” for scrolling purposes

The commands which scroll the other window, such as scroll-other-window now operate on the preview buffer when that is displayed. Thanks to Karthik Chikmagalur for proposing this in issue 24: https://github.com/protesilaos/dired-preview/issues/24.

This is in addition to the commands we already provided for scrolling the preview window, namely, dired-preview-page-up and dired-preview-page-down. Do M-x describe-keymap and then search for dired-preview-mode-map: it is in effect when dired-preview-mode is enabled.

-1:-- Emacs: dired-preview version 0.5.0 (Post Protesilaos Stavrou)--L0--C0--2025-04-09T00:00:00.000Z

Arialdo Martini: Emacs: consult-line-symbol-at-point

I’m addicted to consult.el. It is so convenient that when I found out that isearch-forward-symbol-at-point (M-s .) was a thing, I immediately felt the desire to integrate it with consult.el. Luckily, this was way easier than I thought.

Let’s walk through the steps I took. As often happens with Emacs, along the path of exploring its source code, we will find some random pearls here and there to pick.

TL; DR:

(defun consult-line-symbol-at-point ()
  "Search for a line matching the symbol found near point."
  (interactive)
  (consult-line
   (or (thing-at-point 'symbol))))
   
(global-set-key (kbd "M-s .") #'consult-line-symbol-at-point)
(global-set-key (kbd "M-s M-s .") #'isearch-forward-symbol-at-point)

This issue is based on a lesson I got from Protesilaos.

You have no idea how powerful isearch is!

I was fascinated by the post You have no idea how powerful isearch is! by Bozhidar Batsov. In fact, I really had no idea.

One of the tricks he suggests is:

Type M-s . to search for the symbol at point.
(useful in the context of programming languages)

It’s very convenient: when your point is over a symbol, just hit M-s . to find other occurrences. Emacs will do its best to figure out what symbol means.

M-s . (that is, isearch-forward-symbol-at-point) is so more convenient than what I was used to do: once positioned on a symbol,

  • C-= (er/expand-region) to select it
  • M-w to copy it.
  • C-s to trigger consult-line
  • C-y to use it as the search pattern.

M-s . does the same in one single shot. With the only difference that, alas!, it uses isearch-forward instead of consult-line.
As I mentioned, once tried consult.el, I could not do without it anymore. Unfortunately, consult.el does not provide consult-line-symbol-at-point.

How hard is it to write it?

Edit: it turns out (thank you @Crandel!) that the consult.el README suggests obtaining this result simply with:

(consult-customize
 consult-line
 :add-history (seq-some #'thing-at-point '(region symbol)))

(defalias 'consult-line-thing-at-point 'consult-line)

(consult-customize
 consult-line-thing-at-point
 :initial (thing-at-point 'symbol))

Although next time I should RTFM!, I don’t regret not knowing this: learning it by hacking has been a rewarding experience.

How does isearch-forward-symbol-at-point work?

It makes sense to figure out first how isearch-forward-symbol-at-point works. Which you can do with either:

  • M-x describe-key RET M-s .
  • M-x describe-function isearch-forward-symbol-at-point

(in turn, describe-key and describe-function are conveniently bound respectively to C-h k and C-h f). You will get this documentation:

M-s . runs the command isearch-forward-symbol-at-point (found in
global-map), which is an interactive native-compiled Lisp function in
`isearch.el`.

It is bound to M-s ..

(isearch-forward-symbol-at-point &optional ARG)

From this help page, you can jump to the source code either:

  • hitting s.
  • running M-x help-view-source.
  • hitting Enter while hovering the link isearch.el.

Fine. Here’s the code:

(defun isearch-forward-symbol-at-point (&optional arg)
  (interactive "P")
  (isearch-forward-symbol nil 1)
  (let ((bounds (find-tag-default-bounds))
        (count (and arg (prefix-numeric-value arg))))
    (cond
     (bounds
      (when (< (car bounds) (point))
	(goto-char (car bounds)))
      (isearch-yank-string
       (buffer-substring-no-properties (car bounds) (cdr bounds)))
      (when count
        (isearch-repeat-forward count)))
     (t
      (setq isearch-error "No symbol at point")
      (isearch-push-state)
      (isearch-update)))))

Spitting blood (I’m not that good at Lisp), I could extract the part that identifies the symbol at point with this function:

(defun get-symbol-at-point ()
  (let ((bounds (find-tag-default-bounds)))
    (cond
     (bounds
      (when (< (car bounds) (point))
	(goto-char (car bounds)))
       (buffer-substring-no-properties (car bounds) (cdr bounds)))
     (t ()))))

I’m honest: I cannot understand it completely. However, it’s easy to verify that it actually works:

(defun show-symbol-at-point ()
  (interactive)
  (message "%s" (get-symbol-at-point)))

Move the point on any symbol and execute M-x show-symbol-at-point RET. Cool: sounds like it is a good starting point.

Emacs’ geological layers?

My good friend Prot brought to my attention that in fact a function symbol-at-point is built-in in Emacs. It is part of thingatpt.el and a git blame reveals it has been introduced 19 years ago. Indeed, it is used here and there (check the code of org-open-at-point-global, describe-symbol, imenu--completion-buffer for example). It’s not completely clear to me why it has not been used in isearch-forward-symbol-at-point too. Maybe there are historical reasons. This confuses me, since isearch-forward-symbol-at-point has been introduced after thingatpt.el.

Whatever.
Let’s try it out, replacing my horrible custom get-symbol-at-point with the standard symbol-at-point:

(defun show-symbol-at-point ()
  (interactive)
  (message "%s" (symbol-at-point)))

Works like a charm! Good: we know how to get the symbol at point. We will have to pass it somehow to consult-line.

How does consult-line work?

Now, if we want to build a consult-line-symbol-at-point function, we’d better have a look to consult-line’s source code first.

Run M-x describe-function RET consult-line RET, then get to the code with M-x help-view-source RET (or s).

(defun consult-line (&optional initial start)
  "Search for a matching line.

Depending on the setting `consult-point-placement' the command
jumps to the beginning or the end of the first match on the line
or the line beginning.  The default candidate is the non-empty
line next to point.  This command obeys narrowing.  Optional
INITIAL input can be provided.  The search starting point is
changed if the START prefix argument is set.  The symbol at point
and the last `isearch-string' is added to the future history."

  (interactive (list nil (not (not current-prefix-arg))))
  (let* ((curr-line (line-number-at-pos (point) consult-line-numbers-widen))
         (top (not (eq start consult-line-start-from-top)))
         (candidates (consult--slow-operation "Collecting lines..."
                       (consult--line-candidates top curr-line))))
    (consult--read
     candidates
     :prompt (if top "Go to line from top: " "Go to line: ")
     :annotate (consult--line-prefix curr-line)
     :category 'consult-location
     :sort nil
     :require-match t
     ;; Always add last `isearch-string' to future history
     :add-history (list (thing-at-point 'symbol) isearch-string)
     :history '(:input consult--line-history)
     :lookup #'consult--line-match
     :default (car candidates)
     ;; Add `isearch-string' as initial input if starting from Isearch
     :initial (or initial
                  (and isearch-mode
                       (prog1 isearch-string (isearch-done))))
     :state (consult--location-state candidates))))

That’s it, not so huge, afterall. Basically, consult-line is just a thin wrapper around the internal function consult--read. We found something similar when we wanted to replace completing-read with a consult function, for getting real-time preview in Emacs: let’s zoom.
In the worst hypothesis, we could do the same and use this internal consult--read, passing it the symbol at point. But this is not even necessary. Notice the first consult-line parameter, initial? If it’s not nil, then it will be used as the initial pattern.
Let’s see how it works. Evaluate this with C-x C-e:

(consult-line "whatever")

Cool. It seems that really all we have to do is to feed consult-line with symbol-at-point as the initial value.

(defun consult-line-symbol-at-point ()
  "Search for a line matching the symbol found near point."
  (interactive)
  (consult-line (symbol-at-point)))
   
(global-set-key (kbd "M-s .") #'consult-line-symbol-at-point)

Try it out. Nope. We get an

apply: Wrong type argument: stringp, Try

This is because (symbol-at-point) returns a symbol, whereas consult-line wants a string. Fine: there must be a function to convert symbols to strings, right? I would try with:

M-x describe-function RET symbol

to list the symbol-related functions. symbol-name sounds like a good candidate. Its documentation says:

Return SYMBOL’s name, a string.

Let’t try it out:

(defun consult-line-symbol-at-point ()
  "Search for a line matching the symbol found near point."
  (interactive)
  (consult-line (symbol-name (symbol-at-point))))
   
(global-set-key (kbd "M-s .") #'consult-line-symbol-at-point)

Woah. We are almost there.

nil!

Does this work when there is no symbol at point? symbol-at-point declares:

Return the symbol at point, or nil if none is found.

(consult-line) happily works when the initial pattern is nil — or if it omitted. But the problem in our code is with symbol-name: (symbol-name nil) is "nil", as a string. So consult.el would search for that string. Mumble mumble, shall we raise an error, in that case?

DWIM

There’s a better alternative: to implement a Do What I Mean behavior. That is, if there is no symbol at point, M-s . shall act as a standard consult-line, if it’s over a symbol, it would search for that symbol.

We may use an if clause:

(defun consult-line-symbol-at-point ()
  "Search for a line matching the symbol found near point."
  (interactive)
  
  (let ((symbol (symbol-at-point)))
    (if symbol
        (consult-line (symbol-name symbol))
      (consult-line))))

Now, an if nested in a let can be shortened with a if-let:

(defun consult-line-symbol-at-point ()
  "Search for a line matching the symbol found near point."
  (interactive)
  
  (if-let ((symbol (symbol-at-point)))
        (consult-line (symbol-name symbol))
      (consult-line)))

But we can do better than this. We can remove the duplicated call to consult-line swapping if and consult-line, playing with the fact that:

(if condition
    (function when-true)
  (function when-false))

can be always written as:

(function
 (if condition 
     when-true
   when-false)

Here we go:

(defun consult-line-symbol-at-point ()
  (interactive)
  
  (let ((symbol (symbol-at-point)))
    (consult-line
     (if symbol
         (symbol-name symbol)
       ()))))

Now,

(if condition when-true ()))

can be shortened as:

(when condition when-true)

So:

(defun consult-line-symbol-at-point ()
  (interactive)
  
  (let ((symbol (symbol-at-point)))
    (consult-line (when symbol (symbol-name symbol)))))

Now, if you are curious as I am, it’s impossible not to take a closer look to the code of symbol-at-point. Which reveals a possible further improvement.

How does symbol-at-point work?

It turns out that also symbol-at-point is just a little wrapper, in this case around the more generic thing-at-point:

(defun symbol-at-point ()
  "Return the symbol at point, or nil if none is found."
  (let ((thing (thing-at-point 'symbol)))
    (if thing (intern thing))))

You see that call to intern? If you check intern’s documentation (C-h f intern RET), you will read:

Return the canonical symbol whose name is STRING.

Now we see why we needed to invoke symbol-name! symbol-at-point converts the string to a symbol, so we needed symbol-name to get the string back! It makes sense to directly invoke thing-at-point, then:

(defun consult-line-symbol-at-point ()
  (interactive)
  
  (let ((symbol (thing-at-point 'symbol)))
    (consult-line (when symbol symbol))))

Wait a second: what does thing-at-point return, when there is no thing at point? Move the point in an empty part of the buffer, then invoke it with:

M-x eval-expression RET (thing-at-point 'symbol)

It returns nil! We are lucky: it means that we don’t need all the ceremony about creating the variable symbol and checking its value with when. We can simplify our code as:

(defun consult-line-symbol-at-point ()
  (interactive)
    (consult-line (thing-at-point 'symbol)))

There is no greater pleasure than deleting code, is there?

Have I already told you are curious as a monkey?

I know what you are asking yourself: what is that 'symbol argument we passed to thing-at-point? Sounds like the kind of thing we want to detect.
And what about that part of the thing-at-point documentation that mentions other possible values?

THING should be a symbol specifying a type of syntactic entity. Possibilities include ‘symbol’, ‘list’, ‘sexp’, ‘defun’, ‘filename’, ‘existing-filename’, ‘url’, ‘email’, ‘uuid’, ‘word’, ‘sentence’, ‘whitespace’, ‘line’, ‘number’, ‘face’ and ‘page’.

And, finally, what about this intriguing invitation?

See the file ‘thingatpt.el’ for documentation on how to define a symbol as a valid THING.

I can not resist, can you? We have to try those other syntactic entity types, then we have to challenge ourselves defining our first custom thing-at-point.

You are repetitive, Arialdo!

Let me find out how many times I kept repeating “Try yourself” in his post.

Define:

(defun consult-line-sentence-at-point ()
  (interactive)
  (consult-line (thing-at-point 'sentence)))

Try yourself.

Notice how we are asking thing-at-point to detect a whole sentence, not just the single symbol at point.

consult-line-sentence-at-point in action

Cool. I’m not that repetitive, after all.
Honestly, I have no idea how this would be useful. Maybe detecting sexps could come more in handy:

(defun consult-line-sexp-at-point ()
  (interactive)
  (consult-line (thing-at-point 'sexp)))

consult-line-sexp-at-point in action

You got the idea.

This bears the question: how to define a new custom type of thing? We have no choice but to try.
The documentation is clear:

See the file ‘thingatpt.el’ for documentation on how to define a symbol as a valid THING.

So, let’s read the library code! M-x find-library RET thingatpt RET

Providers

Browsing around, you should stumble upon this variable:

(defvar thing-at-point-provider-alist nil
  "Alist of providers for returning a \"thing\" at point.
This variable can be set globally, or appended to buffer-locally
by modes, to provide functions that will return a \"thing\" at
point.  The first provider for the \"thing\" that returns a
non-nil value wins.

For instance, a major mode could say:

(setq-local thing-at-point-provider-alist
            (append thing-at-point-provider-alist
                    '((url . my-mode--url-at-point))))

to provide a way to get an `url' at point in that mode.  The
provider functions are called with no parameters at the point in
question.

Sounds like our culprit.

If you recap what we discovered until this point, you probably agree that this is a beautiful design trait of Emacs:

  • isearch-forward-symbol-at-point focuses on searching things, seen as strings.
  • thing-at-point focuses on returning that thing. It does that by delegating the job to a customizable collection of thing-providers.
  • Each thing-provider implements some custom, arbitrary logic to detect a thing given the position in a buffer.

Indeed, we were able to easily create our consult-line-symbol-at-point because of this modular nature.

Time to challenge ourselves to define a thing that is not natively supported by isearch-forward-symbol-at-point.

A provider for literal strings, via Tree Sitter

Imagine we want to write consult-line-literal-string-at-point, to search for occurrences of the whole literal string at point, whatever buffer’s programming language defines as a literal string. You could use it to find duplicated strings in code:

consult-line-literal-string-at-point in action

What we should do is to define a new provider, ts-get-literal-string-at-point, a function whose goal is to return the literal string around the point. Notice the ts- in the name? That stands for Tree-sitter. Indeed, we will rely on whatever Tree-sitter grammar is active in the current buffer.

Once defined the provider, we will need to register it in the list of the global or the local thing-at-point providers. At that moment, we will get the chance to associate it to a symbol of our choice (we will use str_lit).

Once done that, thing-at-point will be able to detect literal strings at point (using the symbol we chose).

(Pause a minute to reflect again on the modular design of Emacs: Tree Sitter has been conceived in 2018, independently from Emacs and when Emacs was already 34. What we are really doing here is connecting some prehistoric Emacs machinery with Tree-sitter, the brand new kid on the block. There must be some deep beauty in the Emacs design if this works without any gimmick).

So, here’s the provider:

(defun ts-get-literal-string-at-point ()
  "Return the string node at point using Tree-sitter, or nil if none is found."
  (let ((node (treesit-node-at (point))))
    (when (equal (treesit-node-type node) "str_lit")
          (treesit-node-text node))))

It works like this:

  • It asks Tree-sitter to get the node at point, with (treesit-node-at (point)).
  • It checks the node type is of a literal string, (equal ... "str_lit"),
  • then it returns its content (treesit-node-text node).
  • Otherwise, it just returns nil.

And here is how we can (globally) register it:

(setq thing-at-point-provider-alist
            (append thing-at-point-provider-alist
                    '((str_lit . ts-get-literal-string-at-point))))

In a real case scenario, this would be done by a major mode, so most likely using setq-local instead of setq, inside a hook. If you are curious how major modes and hooks work in Emacs, you might give a read to Emacs: how to activate the functionality X for all files of type Y?.

Finally, let’s have a function connecting thing-at-point against str_lit with consult:

(defun consult-line-literal-string-at-point ()
  (interactive)
  (consult-line (thing-at-point 'str_lit)))


(global-set-key (kbd "M-s C-s .") #'consult-line-literal-string-at-point)

It really works! Try yourself.

(Ouch! I repeated myself)

References

-1:-- Emacs: consult-line-symbol-at-point (Post Arialdo Martini)--L0--C0--2025-04-09T00:00:00.000Z

I want to write more about Journelly, but the problem is that it’s too effective at what it does. Creating notes with it is so easy and streamlined that it’s starting to change my entire workflow for project notes, and that’s a pretty big change.

-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2025-04-08T20:09:00.000Z

Irreal: Emacs Startup Time

Bozhidar Batsov has a post that claims something I’ve often said: Emacs startup time doesn’t matter. No one is saying that your editor taking 30 seconds to start is acceptable but by and large the people you see worrying about startup times are obsessing over sub-second differences. That is, differences that are virtually imperceptible to a human at the keyboard.

Batsov and I say that if you’re using Emacs correctly, you hardly ever restart it. Unlike, say, Vim, you shouldn’t start a new Emacs session for each file you want to edit. When I switched from Vim to Emacs, this was one of the hardest things to get used to. Even if you do want to invoke Emacs for each file, you can simply use Emacsclient and get near instantaneous response.

Judging from the comments on reddit, you’d think Batsov had resurrected Swift’s Modest Proposal. He was accused of being arrogant and not understanding how “real” people use Emacs. If you know anything about Batsov, you’ll find that an amusing notion.

Sure, people do use Emacs in different ways and for different reasons. One commenter, for example, says he develops packages and often has to restart Emacs. John Wiegley does the same but he was a separate Emacs that starts quickly to deal with that: when you’re testing a package you probably want something approaching Emacs -Q anyway.

The TL;DR is that for almost everyone, Emacs startup time really doesn’t matter. I restart Emacs once a week when I upgrade my packages. Batsov says that he goes months without a restart. Even if Emacs did take 30 seconds to start, why would we care. One commenter complains that he has to restart Emacs every morning and, therefore, startup time really does matter. Sorry, but that’s just silly. If you have to spend 5 seconds—or even 30 seconds—starting Emacs in the morning, who cares?

And, by the way, if Emacs really is taking more than a few seconds to start, it probably means that there’s something wrong with your configuration. Mine has plenty of packages, hasn’t been refactored since I started almost 20 years ago, and it still starts within a couple of seconds. It’s hard to get a hard number because it stops to ask me for a password for my .authinfo file. I’m inclined to think of it like booting my machine: it’s not instantaneous but I don’t do it very often.

-1:-- Emacs Startup Time (Post Irreal)--L0--C0--2025-04-08T14:45:10.000Z

Irreal: Setq Versus Setopt

Like most people, I use setq to set user options in my Emacs configuration. It’s the canonical way of setting a variable in just about any Lisp. But, as Bozhidar Batsov says, it’s not always the best way of setting a configuration variable in Emacs. He suggests using setopt instead.

It’s more than just a convention. The setopt macro does a bit more than setq. In particular, it can arrange to call a setter function when you set a configuration variable. This can be useful for checking the prospective value or for performing some system adjustment when a parameter changes.

I’ve known about this for a while but my problem has always been when you need to use setopt instead of setq. Batsov’s recommendation is to use setopt unless the variable is not defined in terms of defcustom. That’s all very well but how do you know which variables are which? The online documentation isn’t much help so the best policy is probably to assume that any variable in your configuration should be set with setopt unless you have reason to believe otherwise.

Batsov says that setopt will work with regular (non-defcustom variables) as well but that it won’t be as efficient as setq. Really, who cares? The setopt macro will almost certainly not be in a loop so any inefficiency won’t matter much. Plus, as I always say, if you’re using Emacs correctly, you won’t be restarting it very often in any event.

Take a look at Batsov’s post to get an idea of the difference between setq and setopt. It probably won’t help you decide which to use but at least you’ll know what each does.

Update [2025-04-07 Mon 13:42]: In the Emacs subreddit, Batsov notes that an easy way to discover which variables are customizable is to check the documentation with describe-variable. Variables that are customizable will have the notation “You can customize this variable.”

-1:-- Setq Versus Setopt (Post Irreal)--L0--C0--2025-04-07T16:05:12.000Z

Sacha Chua: 2025-04-07 Emacs news

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

View org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- 2025-04-07 Emacs news (Post Sacha Chua)--L0--C0--2025-04-07T13:47:18.000Z

Chris Maiorana: Spin up a simple web server from Emacs

Before I converted my website over to WordPress, I was generating the HTML files from Org mode. First of all, this was awesome. And honestly, I would prefer to do it that way, but WordPress has a few nice publishing features that (at the time, at least) I could not emulate in Org.

One of the things that helped tremendously with the local development of my HTML site was being able to spin up a simple HTTP server right in Emacs. That’s what we’re going to be demonstrating here using the simple-httpd package.

How to install simple-httpd

You can download the Lisp code directly from GitHub, or, even more easily, install the package from the Melpa archive. You can use the package-install function in Emacs if you have already included Melpa in your list of package archives.

Here is the use-package declaration I have in my config file:

(use-package simple-httpd :ensure t)

I presume most of my readers already know how to install packages in Emacs, but if anyone would like a post about that, or more information, leave a comment below.

Spin up the server in a directory

If you already have a directory containing HTML files, then you are well ahead of me here, and you are ready to spin up your server.

I’ll use my old website as an example. I had one directory called “website” containing an “org” subdirectory for the Org Mode files, and an “html” directory for the exported HTML files. Pretty simple.

So after using the Org publish function to create the HTML files, I would just use dired to open that HTML directory, and use the httpd-start function, invoked by M-x.

Likewise, you can use the httpd-serve-directory function to be prompted for a filepath. Just put in your file path, and you will be serving that directory instead.

Your website will be served on your localhost, port 8080 by default: http://localhost:8080/


That’s it! You have completed this most important tutorial. If you have any comments or questions, be sure to let me know below. And if you dig this sort of thing, you can subscribe to my site right here:




The post Spin up a simple web server from Emacs appeared first on The Daily Macro.

-1:-- Spin up a simple web server from Emacs (Post Chris Maiorana)--L0--C0--2025-04-07T12:35:37.000Z

Bozhidar Batsov: Emacs Startup Time Doesn’t Matter

Every now and then I see people discussion one of the following:

  • How editor X has faster startup time than Emacs (in a classic apples to oranges comparison style) and Emacs sucks because it doesn’t start “fast enough”
  • How certain config changes or Elisp hacks optimized by 0.5 seconds someone’s Emacs startup time

Here’s one hot take from me - none of this really matters. Emacs startup time doesn’t matter.1 Why so? Because of how people normally use Emacs, compared to some other editors:

  • If you’re the type of person who uses Emacs in the terminal, you’re likely using emacs --daemon
  • If you’re the type of person who uses Emacs’s GUI - you don’t restart it very often
  • If you’re the type of person who uses both - you’re definitely using emacs --daemon (or you’re missing out a lot if you’re not)

In the end of the day Emacs sessions tend to be “long-lived”. By this I mean that often I restart Emacs only when I restart my computer. I recall M-x uptime often showing 3+ months of uptime. So, why then would I care about micro-optimizations to my startup time?

I think those conversations are mostly driven by users coming from other editors (usually vim), where people have pretty different usage patterns - e.g. they’d work on files in one project, exit their editor, start it again, ad infinitum. For them - startup time probably matters a lot…

But they also care a lot about their shell, terminal emulator, terminal multiplexer (e.g. tmux) and in the world of Emacs none of those are really as important, as Emacs is the unifying interface of everything that we need to use.2

Emacs is different. Emacs is special. Emacs startup time doesn’t matter most of the time. Remember this. M-x forever!

Update: This short article sparkled a lively discussion on Reddit.

  1. Unless it’s insanely slow, that is. 

  2. I’ve noticed most Emacs users really struggle to understand the value of something like tmux, as Emacs is the ultimate window multiplexer for pretty much anything. 

-1:-- Emacs Startup Time Doesn’t Matter (Post Bozhidar Batsov)--L0--C0--2025-04-07T07:24:00.000Z

Marcin Borkowski: Back to pomodoros - but only in the morning

So, after ditching pomodoros and creating ketchup.el I noticed a problem with my workflow. Let me start with explaining what it looks like. Warning: a long-ish story follows.
-1:-- Back to pomodoros - but only in the morning (Post Marcin Borkowski)--L0--C0--2025-04-07T06:21:41.000Z

Maryanne Wachter: ERDs in Org Mode

ERDs in Org Mode

In the past few months, I've had a number of contracting and side projects crop up that have required a lot of software architecture work, including developing Entity Relationship Diagrams, which I find helpful when planning out a project (and especially when working with other people not as familiar with the subject matter). While at work, I have a bevvy of SaaS options to do much of this (primarily with Lucidchart), I wanted to see what options there were in OSS.

I've mentioned before that org-mode is my daily multitool for organization, prototyping, and experimentation. As such, I figured there had to be some kind of literate programming solution for ERDs, given that I've used CLI tools like graph viz in the past.

In one of my projects, we're working on integrating Mermaid into our UI, and a quick web search revealed that there was more than just a JS library, there's also a command line tool!

...and reasonable supposition based on the ingenuity of emacs/org-mode users led me to search for an org-mode/Mermaid integration...

which of course there was: ob-mermaid from Alexei Nunez!

The setup in Doom Emacs was straightforward (just follow the Github instructions), and now I can easily generate SVGs of my ERDs turning this code:

#+begin_src mermaid :file blt_erd.svg :background-color #FFFFFF
erDiagram
    BUILDING {
        INTEGER id  PK
        TEXT project_name
        TEXT project_country
        TEXT project_postal_code
        TEXT asset_type
        TEXT building_construction_type
        TEXT building_use_type
        TEXT project_units
        TEXT gfa_measurement_method
        TEXT gross_floor_area
        TEXT enclosed_parking_area
    }
    LCA_METADATA {
        INTEGER id PK
        INTEGER building_id FK
        TEXT tool_lca
        TEXT project_phase_at_time_of_assessment
        INTEGER operational_energy_included
        INTEGER biogenic_carbon_included
        INTEGER substructure_included
        INTEGER shell_superstructure_included
        INTEGER shell_exterior_enclosure_included
        INTEGER interior_construction_included
        INTEGER interior_finishes_included
        INTEGER services_mep_included
        INTEGER sitework_included
        INTEGER equipment_included
        INTEGER furnishings_included
    }
    TALLY_RECORD {
        INTEGER    id PK
        INTEGER lca_id FK
        TEXT revit_design_option
        TEXT revit_general_category
        TEXT revit_category
        TEXT revit_type
        TEXT revit_famly_name
        TEXT revit_material_name
        TEXT revit_building_element
        REAL thickness_of_material
        INTEGER total_instance_count
        TEXT cumulative_material_volume
        REAL cumulative_material_area
        REAL cumulative_instance_volume
        REAL cumulative_instance_area
        REAL cumulative_instance_length
        REAL cumulative_instance_perimeter
        TEXT tally_entry_division
        TEXT tally_entry_category
        TEXT tally_entry_name
        TEXT material_group
        TEXT material_name
        TEXT life_cycle_stage
        INTEGER service_life
        REAL acidification_potential_total
        REAL eutrophication_potential_total
        REAL global_warming_potential_total
        REAL ozone_depletion_potential_total
        REAL smog_formation_potential_total
        REAL primary_energy_demand_total
        REAL nonrenewable_energy_demand_total
        REAL renewable_energy_demand_total
        REAL mass_total
        TEXT timestamp
    }
    BUILDING ||--o{ LCA_METADATA : "has many"
    LCA_METADATA ||--o{ TALLY_RECORD : "contains many"
#+end_src

into this diagram:

ER Diagram

So far ob-mermaid seems to support all the features of Mermaid (at least what I need)!

-1:-- ERDs in Org Mode (Post Maryanne Wachter)--L0--C0--2025-04-07T00:00:00.000Z

Emacs Redux: Goodbye setq, hello setopt!

For many years most Emacs users used setq to set the various configuration options of Emacs and the packages that they were using. This probably wasn’t the best option (read on), but it was the most popular way of doing things. Now, however, it’s finally time for a change!1

Why do we need setopt?

In Emacs 29, a new macro setopt was introduced to provide a more appropriate method for setting user options (variables defined with defcustom).

As mentioned above, traditionally, Emacs users have employed setq to assign values to variables. However, setq does not invoke any custom setter functions associated with user options, which can lead to unexpected behavior. Here’s example of such a setter function from copilot.el:

(defun copilot--lsp-settings-changed (symbol value)
  "Restart the Copilot LSP due to SYMBOL changed to VALUE.

This function will be called by the customization framework when the
`copilot-lsp-settings' is changed.  When changed with `setq', then this function
will not be called."
  (let ((was-bound (boundp symbol)))
    (set-default symbol value)
    (when was-bound
      ;; Notifying the agent with the new value does only work if we include the
      ;; last value (as nil) as well. For example, having the value
      ;; '(:github-enterprise (:uri "https://example2.ghe.com")) and setting it
      ;; to nil would require to send the value '(:github-enterprise (:uri nil))
      ;; to the server. Otherwise, the value is ignored, since sending nil is
      ;; not enough.
      (copilot--start-agent))))

(defcustom copilot-lsp-settings nil
  "Settings for the Copilot LSP server.

This value will always be sent to the server when the server starts or the value
changes.  See
https://github.com/github/copilot-language-server-release?tab=readme-ov-file#configuration-management
for complete documentation.

To change the value of this variable, the customization framework provided by
Emacs must be used.  Either use `setopt' or `customize' to change the value.  If
the value was set without the customization mechanism, then the LSP has to be
manually restarted with `copilot-diagnose'.  Otherwise, the change will not be
applied.

For example to use GitHub Enterprise use the following configuration:
 '(:github-enterprise (:uri \"https://example.ghe.com\"))

Exchange the URI with the correct URI of your organization."
  :set #'copilot--lsp-settings-changed
  :type 'sexp
  :group 'copilot
  :package-version '(copilot . "0.2"))

In case it’s not obvious - the important thing is the :set property of copilot-lsp-settings. Basically, every time this option is changed, a callback function should be invoked, but this won’t happen if you make the change using setq.

The setopt macro addresses this by ensuring that when you set a user option, any associated setter functions are properly called, maintaining the integrity of the option’s behavior.

Even more importantly for me - setopt also checks whether the value is valid for the user option. For instance, using setopt to set a user option defined with a number type to a string will signal an error. I’m pretty sure this will prevent a lot of (weird) configuration issues going forward! (and inspire more package authors to declare their defcustoms properly)

Now let’s update a bit of legacy code to use setopt:

(setq user-full-name "Bozhidar Batsov"
      user-mail-address "bozhidar@emacsninja.com")

;; Always load newest byte code
(setq load-prefer-newer t)

;; reduce the frequency of garbage collection by making it happen on
;; each 50MB of allocated data (the default is on every 0.76MB)
(setq gc-cons-threshold 50000000)

;; warn when opening files bigger than 100MB
(setq large-file-warning-threshold 100000000)

;; quit Emacs directly even if there are running processes
(setq confirm-kill-processes nil)

This will be become:

(setopt user-full-name "Bozhidar Batsov"
        user-mail-address "bozhidar@emacsninja.com")

;; Always load newest byte code
(setopt load-prefer-newer t)

;; reduce the frequency of garbage collection by making it happen on
;; each 50MB of allocated data (the default is on every 0.76MB)
(setopt gc-cons-threshold 50000000)

;; warn when opening files bigger than 100MB
(setopt large-file-warning-threshold 100000000)

;; quit Emacs directly even if there are running processes
(setopt confirm-kill-processes nil)

Pretty shocking, right?

When to Use What?

The introduction of setopt has sparked discussions within the Emacs community regarding the best practices for setting variables. Some users have expressed uncertainty about when to use setq, customize-set-variable, or the new setopt. My take on the subject is pretty simple:

  • Use setopt for user options to ensure that any custom setter functions are invoked.
    • It has shorter name then customize-set-variable and can be used to set multiple options just like setq.
    • Shows a warning when a configuration value does not match its :type specification.
    • Unlike setq, it does not complain when a variable is not declared. (which is quite normal when dealing with a lot of autoloaded packages)
  • Use setq only for variables that are not defined in terms of defcustom.
    • Amusingly, setopt will work with regular variables as well, but it won’t be as efficient as setq. Not to mention using it in such a way will be super confusing!

The way I see it, unless you’re running an older Emacs version, and you’re not using setopt extensively in your Emacs config, you’re missing out!

Further Reading

For more detailed discussions and perspectives on this topic, check out:

Check out the official Emacs docs on setopt as well.

Closing Thoughts

I always knew that setq was flawed, but I kept using it for ages mostly because of inertia. I didn’t like the long name of customize-set-variable and I never use the M-x customize directly. I guess that’s why I rarely bothered to have setter callbacks in the packages that I wrote and maintain. Going forward I’ll certainly reconsider this.

That’s all I have for you today. If you haven’t adopted setopt already, go wild and setopt all the things!

  1. How big of a change? Depends on whether you’re using use-package and how exactly are you using it! :D (in case you’re wondering - :custom settings are handled with customize-set-variable internally) 

-1:-- Goodbye setq, hello setopt! (Post Emacs Redux)--L0--C0--2025-04-06T16:14:00.000Z

ablatedsprocket: Git Links in Org

After looking through Mastodon the other day I stumbled upon an old post by Tony Aldon about making Org hyperlinks to git repositories and opening them in Magit. I have this weird thing where when I see people make things for Magit, I try to do the same thing for VC. I'm giving myself a little slack here, though, because I've been thinking about how nice it would be to link Git commits to my Org todos, but I always tried to do it the other way.

-1:-- Git Links in Org (Post ablatedsprocket)--L0--C0--2025-04-06T05:00:00.000Z

Protesilaos Stavrou: Emacs: stylistic refinements to the “tinted” Modus themes

As part of the current development target of the modus-themes package, I am introducing small changes to the modus-operandi-tinted and modus-vivendi-tinted themes. These concern fine details, some of which most users will probably not even notice. Though the cumulative effect of these changes is obvious once we compare the themes to their main counterparts, namely, modus-operandi and modus-vivendi. I am doing this to improve the consistency of the “tinted” themes. I think existing users will appreciate the attention to detail.

Below are some screen shots with the out-of-the-box design of the themes (remember that they are highly customisable). I also include pictures with spacious-padding-mode enabled (from my spacious-padding package). Notice that in this case the mode lines are just an overline, which is done by customising the user option spacious-padding-subtle-mode-line.

I plan to install those changes to emacs.git and make them available via GNU ELPA as part of modus-themes version 4.7.0, which I hope to publish some time this month or in May.

Modus Operandi VS Modus Operandi Tinted

Sample of modus-operandi theme

Sample of modus-operandi-tinted theme

Sample of modus-operandi theme

Sample of modus-operandi-tinted theme

Modus Vivendi VS Modus Vivendi Tinted

Sample of modus-vivendi theme

Sample of modus-vivendi-tinted theme

Sample of modus-vivendi theme

Sample of modus-vivendi-tinted theme

About the Modus themes

Highly accessible themes, conforming with the highest standard for colour contrast between background and foreground values (WCAG AAA). They also are optimised for users with red-green or blue-yellow colour deficiency.

The themes are very customisable and provide support for a wide range of packages. Their manual is detailed so that new users can get started, while it also provides custom code for all sorts of more advanced customisations.

Since August 2020, the original Modus themes (modus-operandi, modus-vivendi) are built into Emacs version 28 or higher. Emacs 28 ships with modus-themes version 1.6.0. Emacs 29 includes version 3.0.0. Emacs 30 provides version 4.4.0. Version 4 is a major refactoring of how the themes are implemented and customized. Such major versions are not backward-compatible due to the limited resources at my disposal to support multiple versions of Emacs and of the themes across the years.

-1:-- Emacs: stylistic refinements to the “tinted” Modus themes (Post Protesilaos Stavrou)--L0--C0--2025-04-06T00:00:00.000Z

Irreal: New Window Commands For Emacs 31

Being a basically boring guy, I almost always have a full screen frame with two side-by-side windows for Emacs. For some functions—Magit and Elfeed are examples—I temporarily pop up a single full frame window but the previous window configuration is restored when I finish. Some of you, I know, lead less constrained lives and have more complicated window configurations.

People with these more interesting configurations sometimes want to move their windows around. When you have two side-by-side windows, that’s trivial—basically Ctrl+u Ctrl+x o is all you have to know—but if you have different sized windows stacked both vertically and horizontally, things can get more complicated.

Pranshu has a nice post that describes the problem and what he’s done to address it. The changes won’t appear until Emacs 31 but for those of you with complex window configurations they will surely be welcome.

Take a look at his post to see the sorts of transformations that are possible and the commands that implement them. To tell the truth, they make me dizzy and I’m happy to stay with my simple configuration but as always Emacs is able to adjust to everyone’s needs.

-1:-- New Window Commands For Emacs 31 (Post Irreal)--L0--C0--2025-04-05T14:56:22.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!