Does renaming title no longer renames the filename?

Nobiot wrote that they “would not want Emacs to automatically do anything to Link A and Link B” ( Org-Roam V2 rename file or #+TITLE - #4 by nobiot ), and I agree that automatic rewriting of backlinks or link descriptions is not universally desirable.

In contrast I think lots of people might want to change the title of a note: they initially used some dummy or temporary title, then they want to change it, and they want the filename to reflect that change, while keeping the org-roam prefix. Doing this by hand is tedious, and a helper function like the one suggested by quolpr in Does renaming title no longer renames the filename? - #8 by quolpr is both simple to implement and very useful.

I made a few changes on quolpr’s code:

  1. Use rename-visited-file which is the newer shorthand for the common sequence:
(rename-buffer new-file)
(rename-file old-file new-file)
(set-visited-file-name new-file)
  1. Avoid depending on a fresh org-roam DB update
    In my setup, even with org-roam-db-autosync-mode enabled, the org-roam DB was not always updated immediately after editing #+title: and saving. Since org-roam-node-at-point and org-roam-node-slug read from the DB, they can return a stale title/slug unless I force a refresh:
(org-roam-db-update-file (buffer-file-name))

Instead, I chose to read #+title: directly from the current buffer using the org-element API.

Code:

(defun my/org-buffer-title ()
  "Return the value of #+title: in the current buffer, or nil if none."
  (let ((ast (org-element-parse-buffer 'element)))
    (org-element-map ast 'keyword
      (lambda (kw)
        (when (string-equal (org-element-property :key kw) "TITLE")
          (org-element-property :value kw)))
      nil
      t)))

(defun my/org-roam-rename-to-new-title ()
  "Rename the current org-roam file based on the current buffer #+title:.
Keeps an existing 14-digit timestamp prefix like 20240211123456-."
  (interactive)
  (require 'org-roam-node)
  (when-let* ((old-file (buffer-file-name))
              (_ (org-roam-file-p old-file))
              (title (my/org-buffer-title))
              (slug (org-roam-node-slugify title))
              (base (file-name-base old-file))
              (prefix (if (string-match "\\`\\([0-9]\\{14\\}\\)-" base)
                          (concat (match-string 1 base) "-")
                        ""))
              (new-file (expand-file-name (concat prefix slug ".org")
                                          (file-name-directory old-file))))
    (unless (file-equal-p old-file new-file)
      (when (buffer-modified-p)
        (save-buffer))
      (rename-visited-file new-file))))

I didn’t tie it to a hook because I prefer to call it manually.