Emacs Workflow: Dynamically Adding Files to Org Agenda
How to automatically and dynamically build org-agenda-files to include any files with TODO items.
UPDATE Sept. 8 2023
Since I wrote this post, Nicolas Graves has made a GitHub repository that implements this with some improvements using
org-ql and etc. You can check it out here: org-dynamic-agenda.
If you use Emacs org-mode for task management, you have probably wondered if there is a way to dynamically add files with TODO items to org-agenda-files. A Google search will likely get you some initial ideas on how to do it. For example this post: Boris Buliga - Task management with org-roam Vol. 5: Dynamic and fast agenda covers a good approach, but since Boris’s post focuses on using org-roam, it may not be the right solution when you don’t use org-roam. So I had to hack my own solution and in this point I’m going to share that with you in case anyone is interested. You can see a screenshot below.
How does it work
For the main functionalities, I am using Org Element API to parse org-mode buffers and find org TODO items. If there is a
TODO item in the buffer, and it is visiting a file, I add the file to
org-agenda-files. Additionally, I make sure that
org-agenda-files is remembered between different Emacs sessions, I add
Then I define custom functions for and add them as hooks to org-mode to update
org-agenda-files when an org-mode file is opened as well as when an org-mode file is saved. The redundancy helps make sure that nothing is lost if there is a crash.
Check if the file contains a
org-element-parse-bufferto walk the buffer, find all headlines and return true if there is any headline that is a
TODOitem. Note that I am looking for any heading that is a
TODOitem. Alternatively you can check for specific
TODOtypes by looking at
(org-element-property :todo-keyword h)...
(defun ad/agenda-file-p () (org-element-map (org-element-parse-buffer 'headline) 'headline (lambda (h) (eq (org-element-property :todo-type h) 'todo)) nil 'first-match))
- make a custom function to update
org-agenda-filesif the current org-mode file contains a
If the current buffer contains a
TODO item, I use seq-difference to find out if the files are already in org-agenda-files. If it does not contain
TODO item, I make sure to remove it from
org-agenda-files. This is important because when I’m done with a
TODO item and remove it from the file, I would want the file to be removed from
(defun ad/org-agenda-update-files (&rest ARG) ;; check if this is an org file buffer (interactive) (when (and (derived-mode-p 'org-mode) (buffer-file-name)) (message "updating org-agenda-files...") ;; if there is an active TODO task, add this file to agenda files (if (ad/agenda-file-p) (add-to-list 'org-agenda-files (file-truename (buffer-file-name))) ;; if there is no active TODO task, remove the file from agenda files if needed (setq org-agenda-files (seq-difference org-agenda-files (list (buffer-file-name)))) (customize-save-variable 'org-agenda-files org-agenda-files) )) )
- cleaning up
org-agenda-filesand remove files that don’t exist anymore.
When I delete some files, I want to make sure it gets removed from
(defun ad/org-agenda-cleanup-files (&rest ARG) (interactive) (let ((temp/org-agenda-files org-agenda-files)) (dolist (file org-agenda-files) (if (not (file-exists-p file)) (setq temp/org-agenda-files (seq-difference temp/org-agenda-files (list file)))) ()) (setq org-agenda-files temp/org-agenda-files)) )
- Adding hooks
To get my functions to run automatically, I add
org-mode. I make
lambda functions that are added as hooks to
before-save-hook to make sure that
org-agenda-files gets updated whenever I open an org-mode file and then again when I save the file.
;; Add or remove individual file (add-hook 'org-mode-hook (lambda () (add-hook 'find-file-hook #'ad/org-agenda-update-files))) (add-hook 'org-mode-hook (lambda () (add-hook 'before-save-hook #'ad/org-agenda-update-files)))
- Adding advice to functions that use
Before I run
dashboard-get-agenda, or any other function that reads
org-agenda-files to show my
TODO items, I need to make sure to remove non-existing files from
;; remove non-existing files before building agenda (advice-add 'org-agenda :before #'ad/org-agenda-cleanup-files) (advice-add 'org-todo-list :before #'ad/org-agenda-cleanup-files) (advice-add 'dashboard-get-agenda :before #'ad/org-agenda-cleanup-files)
- Make sure
org-agenda-filesis remembered between Emacs sessions.
savehis-additional-variables and make sure that
savehist-mode is enabled.
(add-to-list 'savehist-additional-variables 'org-agenda-files)
Of course adding files dynamically to the
org-agenda-files comes with a cost. Make too many files with org-agenda items and it will be very slow. Personally I keep most of my general
TODO items in one file under my main org directory and only put
TODO items in other files when having it in the context of the specific project is useful.
Here is a screenshot showing it in action. I open an org file and a
TODO item in it and save it and as you can see once I refresh the buffer describing
org-agenda-files variable, the new file is added to the list. I also show that once I remove the
TODO item or mark it as DONE, it is automatically removed from the list. Also, you can see that once I have the new file in the org-agenda list, it automatically shows up on my dashboard.