Dynamic Org Agenda List Based on Denote Keywords
Porting some Functions for Org-Roam to Denote
Porting some Functions for Org-Roam to Denote
I read Boris Buliga’s “Task management with org-roam Vol. 5: Dynamic and fast agenda” post.
Boris provide code and explanation to automatically updating the org-agenda-files list with only the files that have a project tag. The org-agenda-files variable defines the files that Org-Mode 📖
uses to drive it’s agenda feature set. Show todos, run time reports, etc.
If the number of org-agenda-files becomes too large, then it begins to impact performance of the agenda feature set. Thus keeping a pruned list helps with performance.
I really like the idea and appreciate the implementation. However, I don’t use Org-Roam 📖 ; instead I used Denote 📖 . So I spent a bit of time mapping Boris’s code to my reality.
Below is my walk through.
First, I am going to use a different keyword than “project.” I’m favoring the explicit “agenda”. When the note has that keyword it is part of the agenda.
(defvar jf/org-mode/agenda-keyword
"agenda"
"The `denote' keyword that identifies a note as part of `org-mode' agenda.")
Next up is almost a direct copy of vulpea-project-p; it returns non-nil when there’s a “todo” keyword on any of the nodes.
(defun jf/org-mode/agenda-p ()
"Return non-nil if current buffer has any todo entry.
TODO entries marked as done are ignored, meaning the this
function returns nil if current buffer contains only completed
tasks.
From https://d12frosted.io/posts/2021-01-16-task-management-with-roam-vol5.html"
(when (derived-mode-p 'org-mode)
(org-element-
(org-element-parse-buffer 'headline)
'headline
(lambda (h)
(eq (org-element-property :todo-type h)
'todo))
nil 'first-match)))
This is an echo of Boris’s vulpea-project-update-tag function. It’s interweaves with the functions used in Denote to determine a file’s keywords.
(Sidenote:
Aspects of the when-let* function could be compressed into a native Denote
function.)
(add-hook 'before-save-hook
#'jf/org-mode/denote-update-project-update-tag)
(add-hook 'find-file-hook
#'jf/org-mode/denote-update-project-update-tag)
(defun jf/org-mode/denote-update-project-update-tag ()
"Update `jf/org-mode/agenda-keyword' tag in the current buffer."
(when-let* ((_proceed (not (active-minibuffer-window)))
(file (buffer-file-name))
(_proceed (denote-file-is-note-p file))
(file-type (denote-filetype-heuristics file))
(new-keywords (denote-retrieve-keywords-value
file-type))
(keywords new-keywords))
(save-excursion
(goto-char (point-min))
(if (jf/org-mode/agenda-p)
(setq new-keywords (cons
jf/org-mode/agenda-keyword
new-keywords))
(setq new-keywords (remove
jf/org-mode/agenda-keyword
new-keywords)))
;; cleanup duplicates
(setq new-keywords (seq-uniq new-keywords))
;; update tags if changed
(when (or (seq-difference keywords new-keywords)
(seq-difference new-keywords keywords))
(message "Adjusting \"%s\" keyword for %s"
jf/org-mode/agenda-keyword file)
(denote-rewrite-keywords file new-keywords file-type)t))))
Where Org-Roam uses SQLite for storying and accessing metadata (e.g. the tags/keywords), Denote opts instead for front-matter and file name conventions. What I have below uses the fd 📖 program to query the file system for the tags/keywords.
(defvar jf/org-mode/directory-for-agendas
"~/git")
(defun jf/org-mode/agenda-files ()
"Return a list of note files containing 'agenda' tag.
Uses the fd command (see https://github.com/sharkdp/fd)
We want files either begin with the `jf/org-mode/agenda-keyword'
or by `denote' conventions have the keyword. Hence the complex regular
expression."
(let ((default-directory (file-truename
jf/org-mode/directory-for-agendas)))
(s-split "\n"
(s-trim
(shell-command-to-string
(concat "fd --no-ignore --absolute-path --extension org "
"'(^|_)" jf/org-mode/agenda-keyword "[_\\.]'"))))))
Next is similar code to update the org-agenda-files based on their tags.
(defun jf/org-mode/agenda-files-update (&rest _)
"Update the value of `org-agenda-files'."
(setq org-agenda-files (jf/org-mode/agenda-files)))
(advice-add 'org-agenda :before #'jf/org-mode/agenda-files-update)
(advice-add 'org-todo-list :before #'jf/org-mode/agenda-files-update)
Last is the most significant change. Because I’m relying on the file name to encode the keywords, I need to ensure some synchronization. In my experimentation, I ran into problems trying to rename the file during save. With this change, I rename the file when I close/kill it.
(defun jf/org-mode/kill-buffer-hook ()
(when-let* ((_proceed (not (active-minibuffer-window)))
(file (buffer-file-name))
(_proceed (denote-file-is-note-p file)))
(call-interactively #'denote-rename-file-using-front-matter file)))
(add-hook 'kill-buffer-hook #'jf/org-mode/kill-buffer-hook)
I’m rolling this into my workflow; I can see this drifting into the space of a right and proper package. Let me know if there’s interest.